1 | # Freddo
|
2 |
|
3 | > Minimal assertion testing framework for APIs
|
4 |
|
5 | [![Build Status](https://travis-ci.org/Meeshkan/freddo.svg?branch=master)](https://travis-ci.org/Meeshkan/freddo) [![Install Size](https://packagephobia.now.sh/badge?p=freddo)](https://packagephobia.now.sh/result?p=freddo)
|
6 |
|
7 | ## Install
|
8 |
|
9 | ```
|
10 | ~ ❯❯❯ npm install freddo --save-dev
|
11 | ```
|
12 |
|
13 | ## Usage
|
14 |
|
15 | ```js
|
16 | const { freddo, expr, exists } = require('freddo');
|
17 |
|
18 | (async () => {
|
19 | const isSvg = str => str.trim().startsWith('<svg ')
|
20 | /*
|
21 | <svg width="120.5" height="20" viewBox="0 0 1205 200" xmlns="http://www.w3.org/2000/svg">
|
22 | ...
|
23 | </svg>
|
24 | */
|
25 | await freddo('https://badgen.net/packagephobia/install/sha-regex')
|
26 | .status(200)
|
27 | .header('content-type', 'image/svg+xml;charset=utf-8')
|
28 | .body(isSvg)
|
29 | .ensure()
|
30 | })();
|
31 |
|
32 | (async () => {
|
33 | /*
|
34 | {
|
35 | "hash":"0000000000000538200a48202ca6340e983646ca088c7618ae82d68e0c76ef5a",
|
36 | "time":1325794737,
|
37 | "block_index":841841,
|
38 | "height":160778,
|
39 | "txIndexes":[13950369,13950510,13951472]
|
40 | }
|
41 | */
|
42 | await freddo('https://blockchain.info/latestblock')
|
43 | .status(200)
|
44 | .body(exists, expr('.hash'))
|
45 | .body(exists, expr('.time'))
|
46 | .body(([time]) => {
|
47 | const DAY = 24 * 60 * 60 * 1000
|
48 | return {
|
49 | result: time > Date.now()/1000 - DAY,
|
50 | error: 'Most recent blockchain block is unrealistically old'
|
51 | }
|
52 | }, expr('.time'))
|
53 | .body(exists, expr('.block_index'))
|
54 | .body(([blockHeight]) => {
|
55 | return {
|
56 | result: blockHeight >= 500000,
|
57 | error: 'Block height of blockchain tip is insufficient'
|
58 | }
|
59 | }, expr('.height'))
|
60 | .body(exists, expr('.txIndexes'))
|
61 | .ensure()
|
62 | })();
|
63 |
|
64 | (async () => {
|
65 | /*
|
66 | HTTP/1.1 301 Moved Permanently
|
67 | */
|
68 | await freddo('https://httpstat.us/301')
|
69 | .redirectsTo('https://httpstat.us/301')
|
70 | .ensure()
|
71 | })();
|
72 | ```
|
73 |
|
74 | ### Example uses with testing frameworks
|
75 |
|
76 | #### [AVA](https://github.com/avajs/ava)
|
77 |
|
78 | ```js
|
79 | import * as test from 'ava'
|
80 | import m from '.'
|
81 | import { freddo, expr, exists } from 'freddo'
|
82 | import micro from 'micro'
|
83 | import testListen from 'test-listen'
|
84 | import validator from 'validator'
|
85 |
|
86 | let url
|
87 | test.before(async () => {
|
88 | url = await testListen(micro(m))
|
89 | })
|
90 |
|
91 | test('/ip/json', async t => {
|
92 | t.is(await freddo(url)
|
93 | .status(200)
|
94 | .header('content-type', 'application/json; charset=utf-8')
|
95 | .body(validator.isJSON)
|
96 | .body(exists, expr('.ip')), true)
|
97 | })
|
98 | ```
|
99 |
|
100 | #### [Mocha](https://github.com/mochajs/mocha)
|
101 |
|
102 | ```js
|
103 | import { freddo, expr, exists } from 'freddo'
|
104 | import validator from 'validator'
|
105 | import assert from 'assert'
|
106 |
|
107 | describe('/ip/json', function() {
|
108 | it('should serve a JSON response', async function() {
|
109 | assert.strict.ok(await freddo("https://locate.now.sh/ip/json/")
|
110 | .status(200)
|
111 | .header('content-type', 'application/json; charset=utf-8')
|
112 | .body(validator.isJSON)
|
113 | .body(exists, expr('.ip')))
|
114 | })
|
115 | })
|
116 | ```
|
117 |
|
118 | ## API
|
119 |
|
120 | ### freddo(url[, options])
|
121 |
|
122 | Returns a `Promise` for a modified [`got`](https://github.com/sindresorhus/got) request object.
|
123 |
|
124 | #### url
|
125 |
|
126 | Type: `string | object`
|
127 |
|
128 | The URL to request, as a string, a [`https.request` options object](https://nodejs.org/api/https.html#https_https_request_options_callback), or a [WHATWG `URL`](https://nodejs.org/api/url.html#url_class_url). (Adapted from [`got`'s documentation](https://github.com/sindresorhus/got))
|
129 |
|
130 | #### options
|
131 |
|
132 | Type: `object`
|
133 |
|
134 | Any of the [`https.request`](https://nodejs.org/api/https.html#https_https_request_options_callback) options. (Adapted from [`got`'s documentation](https://github.com/sindresorhus/got))
|
135 |
|
136 | ### freddo.status(expected)
|
137 |
|
138 | Compares equality of `status-code` with `expected`, and returns a `boolean`.
|
139 |
|
140 | #### expected
|
141 |
|
142 | Type: `number | function`
|
143 |
|
144 | ### freddo.header(entity, expected)
|
145 |
|
146 | Compares equality of `entity` `header` with `expected`, and returns a `boolean`.
|
147 |
|
148 | #### entity
|
149 |
|
150 | Type: `string`
|
151 |
|
152 | e.g. `content-type`, `content-length`, `origin`, etc.
|
153 |
|
154 | #### expected
|
155 |
|
156 | Type: `string | number | function`
|
157 |
|
158 | ### freddo.body(expected[, expression])
|
159 |
|
160 | Compares equality of `body` with `expected`, and returns a `boolean`.
|
161 |
|
162 | #### expected
|
163 |
|
164 | Type: `string | function`
|
165 |
|
166 | #### expression
|
167 |
|
168 | Type: `Expression object`
|
169 |
|
170 | *Note*: When an `expression` is given, an array containing the matched values is returned. Therefore, in this case, an `expected` parameter function should treat its argument as an array and destructure it accordingly (e.g. `([x]) => x == 'bar'`).
|
171 |
|
172 | ### freddo.redirectsTo(url)
|
173 |
|
174 | Checks whether `status` code contains a redirection code (i.e. `301`, `302`, `303`, `307`, or `308`) and whether there exists a `location` `header` entity containing `url`.
|
175 |
|
176 | #### url
|
177 |
|
178 | Type: `string`
|
179 |
|
180 | ### freddo.expect(key, expected)
|
181 |
|
182 | Compares equality of `response.key` (where `response` is [`got`'s `response` object](https://github.com/sindresorhus/got#response)) with `expected`, and returns a `boolean`.
|
183 |
|
184 | *Note*: `freddo.ensure('body', expected)` is the same as `freddo.body(expected)` (and the same applies for `freddo.status`).
|
185 |
|
186 | #### key
|
187 |
|
188 | Type: `string`
|
189 |
|
190 | Any of [`got`'s `response` object](https://github.com/sindresorhus/got#response) keys.
|
191 |
|
192 | #### expected
|
193 |
|
194 | Type: `string | function`
|
195 |
|
196 | ### freddo.ensure()
|
197 |
|
198 | Asserts the `boolean` response of the preceding functions.
|
199 |
|
200 | ### expr(pattern)
|
201 |
|
202 | Returns an `Expression object` that can be passed as an `expression` parameter to the `freddo.body` function (see above) so as to find a value matching the `pattern`.
|
203 |
|
204 | #### pattern
|
205 |
|
206 | Type: [`JSPath path expression`](https://github.com/dfilatov/jspath#documentation)
|
207 |
|
208 | ### exists()
|
209 |
|
210 | Returns a function that can be passed as an `expected` parameter to the `freddo.body` function to check whether a `pattern` match is found.
|
211 |
|
212 |
|
213 | ## Contributing
|
214 |
|
215 | Thanks for wanting to contribute! We will soon have a contributing page
|
216 | detailing how to contribute. Meanwhile, feel free to star this repository, open issues,
|
217 | and ask for more features and support.
|
218 |
|
219 | Please note that this project is governed by the [Meeshkan Community Code of Conduct](https://github.com/unmock/code-of-conduct). By participating in this project, you agree to abide by its terms.
|
220 |
|
221 | ## License
|
222 |
|
223 | MIT © [Meeshkan](https://meeshkan.com/)
|