mockaton 8.22.0 → 8.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +260 -240
- package/index.d.ts +3 -2
- package/index.js +11 -2
- package/package.json +6 -9
- package/src/Api.js +2 -2
- package/src/Dashboard.js +3 -3
- package/src/Mockaton.js +19 -2
- package/src/cli.js +68 -0
- package/src/config.js +25 -12
- /package/src/{Commander.js → ApiCommander.js} +0 -0
- /package/src/{logo.svg → Logo.svg} +0 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<img src="src/
|
|
1
|
+
<img src="src/Logo.svg" alt="Mockaton Logo" width="210" style="margin-top: 30px"/>
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|

|
|
@@ -92,8 +92,9 @@ api/videos.GET.<b>500</b>.txt # Internal Server Error
|
|
|
92
92
|
## Scraping Mocks from your Backend
|
|
93
93
|
|
|
94
94
|
### Option 1: Browser Extension
|
|
95
|
-
With the companion [browser-devtools
|
|
96
|
-
|
|
95
|
+
With the companion [browser-devtools
|
|
96
|
+
extension](https://github.com/ericfortis/download-http-requests-browser-ext)
|
|
97
|
+
you can download all the HTTP responses, and they
|
|
97
98
|
get saved following Mockaton’s filename convention.
|
|
98
99
|
|
|
99
100
|
### Option 2: Fallback to Your Backend
|
|
@@ -117,260 +118,50 @@ They will be saved in your `config.mocksDir` following the filename convention.
|
|
|
117
118
|
|
|
118
119
|
|
|
119
120
|
## Basic Usage
|
|
120
|
-
|
|
121
|
-
npm install mockaton --save-dev
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
Create a `my-mockaton.js` file
|
|
125
|
-
```js
|
|
126
|
-
import { resolve } from 'node:path'
|
|
127
|
-
import { Mockaton } from 'mockaton'
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
Mockaton({
|
|
131
|
-
mocksDir: resolve('my-mocks-dir'), // must exist
|
|
132
|
-
port: 2345
|
|
133
|
-
}) // The Config section below documents more options
|
|
134
|
-
```
|
|
121
|
+
Mockaton is a Node.js program with no dependencies.
|
|
135
122
|
|
|
123
|
+
### Create a Sample Mock
|
|
124
|
+
The default `--mocks-dir` is **mockaton-mocks** in the current working directory.
|
|
136
125
|
```sh
|
|
137
|
-
|
|
126
|
+
mkdir -p mockaton-mocks/api/
|
|
127
|
+
echo "[1,2,3]" > mockaton-mocks/api/foo.GET.200.json
|
|
138
128
|
```
|
|
139
129
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
```shell
|
|
145
|
-
npm install tsx
|
|
146
|
-
node --import=tsx my-mockaton.js
|
|
147
|
-
```
|
|
148
|
-
</details>
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
<br/>
|
|
152
|
-
|
|
153
|
-
## Demo App (Vite + React)
|
|
154
|
-
|
|
155
|
-
```sh
|
|
156
|
-
git clone https://github.com/ericfortis/mockaton.git
|
|
157
|
-
cd mockaton/demo-app-vite
|
|
158
|
-
npm install
|
|
159
|
-
|
|
160
|
-
npm run mockaton
|
|
161
|
-
npm run start # in another terminal
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
The demo app has a list of colors containing all of their possible states. For example,
|
|
165
|
-
permutations for out-of-stock, new-arrival, and discontinued.
|
|
166
|
-
|
|
167
|
-
<img src="./demo-app-vite/pixaton-tests/pic-for-readme.vp740x880.light.gold.png" alt="Mockaton Demo App Screenshot" width="740" />
|
|
168
|
-
|
|
169
|
-
<br/>
|
|
170
|
-
<br/>
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
## Use Cases
|
|
174
|
-
### Testing Backend or Frontend
|
|
175
|
-
- Empty responses
|
|
176
|
-
- Errors such as _Bad Request_ and _Internal Server Error_
|
|
177
|
-
- Mocking third-party APIs
|
|
178
|
-
- Polled resources (for triggering their different states)
|
|
179
|
-
- alerts
|
|
180
|
-
- notifications
|
|
181
|
-
- slow to build resources
|
|
182
|
-
|
|
183
|
-
### Testing Frontend
|
|
184
|
-
- Spinners by delaying responses
|
|
185
|
-
- Setting up UI tests
|
|
186
|
-
|
|
187
|
-
### Demoing complex backend states
|
|
188
|
-
Sometimes, the ideal flow you need is too difficult to reproduce from the actual backend.
|
|
189
|
-
For this, you can **Bulk Select** mocks by comments to simulate the complete states
|
|
190
|
-
you want. For example, by adding `(demo-part1)`, `(demo-part2)` to the filenames.
|
|
191
|
-
|
|
192
|
-
Similarly, you can deploy a **Standalone Demo Server** by compiling the frontend app and
|
|
193
|
-
putting its built assets in `config.staticDir`. And simulate the flow by Bulk Selecting mocks.
|
|
194
|
-
The [aot-fetch-demo repo](https://github.com/ericfortis/aot-fetch-demo) has a working example.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
<br/>
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
## You can write JSON mocks in JavaScript or TypeScript
|
|
201
|
-
For example, `api/foo.GET.200.js`
|
|
202
|
-
|
|
203
|
-
**Option A:** An Object, Array, or String is sent as JSON.
|
|
204
|
-
|
|
205
|
-
```js
|
|
206
|
-
export default { foo: 'bar' }
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
**Option B:** Function
|
|
210
|
-
|
|
211
|
-
Return a `string | Buffer | Uint8Array`, but don’t call `response.end()`
|
|
212
|
-
|
|
213
|
-
```js
|
|
214
|
-
export default (request, response) =>
|
|
215
|
-
JSON.stringify({ foo: 'bar' })
|
|
130
|
+
### Install and Run
|
|
131
|
+
```sh
|
|
132
|
+
npm install mockaton
|
|
133
|
+
npx mockaton --port 2345
|
|
216
134
|
```
|
|
217
135
|
|
|
218
|
-
|
|
219
|
-
you can intercept requests to write to a database.
|
|
220
|
-
|
|
221
|
-
<details>
|
|
222
|
-
<summary><b>See Intercepting Requests Examples</b></summary>
|
|
223
|
-
|
|
224
|
-
Imagine you have an initial list of colors, and
|
|
225
|
-
you want to concatenate newly added colors.
|
|
226
|
-
|
|
227
|
-
`api/colors.POST.201.js`
|
|
228
|
-
```js
|
|
229
|
-
import { parseJSON } from 'mockaton'
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
export default async function insertColor(request, response) {
|
|
233
|
-
const color = await parseJSON(request)
|
|
234
|
-
globalThis.newColorsDatabase ??= []
|
|
235
|
-
globalThis.newColorsDatabase.push(color)
|
|
236
|
-
|
|
237
|
-
// These two lines are not needed but you can change their values
|
|
238
|
-
// response.statusCode = 201 // default derived from filename
|
|
239
|
-
// response.setHeader('Content-Type', 'application/json') // unconditional default
|
|
240
|
-
|
|
241
|
-
return JSON.stringify({ msg: 'CREATED' })
|
|
242
|
-
}
|
|
243
|
-
```
|
|
136
|
+
## CLI Options
|
|
244
137
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
import colorsFixture from './colors.json' with { type: 'json' }
|
|
138
|
+
```txt
|
|
139
|
+
-c, --config <file> (default: ./mockaton.config.js)
|
|
248
140
|
|
|
141
|
+
-m, --mocks-dir <dir> (default: ./mockaton-mocks/)
|
|
142
|
+
-s, --static-dir <dir> (default: ./mockaton-static-mocks/)
|
|
249
143
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
...colorsFixture,
|
|
253
|
-
...(globalThis.newColorsDatabase || [])
|
|
254
|
-
])
|
|
255
|
-
}
|
|
144
|
+
-H, --host <host> (default: 127.0.0.1)
|
|
145
|
+
-p, --port <port> (default: 0) which means auto-assigned
|
|
256
146
|
```
|
|
257
|
-
</details>
|
|
258
|
-
|
|
259
|
-
<br/>
|
|
260
|
-
|
|
261
|
-
**What if I need to serve a static .js or .ts?**
|
|
262
147
|
|
|
263
|
-
**Option A:** Put it in your `config.staticDir` without the `.GET.200.js` extension.
|
|
264
148
|
|
|
265
|
-
|
|
149
|
+
## mockaton.config.js
|
|
150
|
+
Optionally, use a `mockaton.config.js` file
|
|
266
151
|
```js
|
|
267
|
-
|
|
268
|
-
response.setHeader('Content-Type', 'application/javascript')
|
|
269
|
-
return readFileSync('./some-dir/foo.js', 'utf8')
|
|
270
|
-
}
|
|
271
|
-
```
|
|
152
|
+
import { defineConfig } from 'mockaton'
|
|
272
153
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
The last three dots are reserved for the HTTP Method,
|
|
280
|
-
Response Status Code, and File Extension.
|
|
281
|
-
|
|
282
|
-
```
|
|
283
|
-
api/user.GET.200.json
|
|
154
|
+
export default defineConfig({
|
|
155
|
+
port: 2345,
|
|
156
|
+
mocksDir: 'my-mocks-dir',
|
|
157
|
+
delayJitter: 0.5,
|
|
158
|
+
// …
|
|
159
|
+
})
|
|
284
160
|
```
|
|
285
161
|
|
|
286
|
-
You can also use `.empty` or `.unknown` if you don’t
|
|
287
|
-
want a `Content-Type` header in the response.
|
|
288
|
-
|
|
289
162
|
<details>
|
|
290
|
-
<summary>
|
|
291
|
-
<p>From <code>require('node:http').METHODS</code></p>
|
|
292
|
-
<p>
|
|
293
|
-
ACL, BIND, CHECKOUT,
|
|
294
|
-
CONNECT, COPY, DELETE,
|
|
295
|
-
GET, HEAD, LINK,
|
|
296
|
-
LOCK, M-SEARCH, MERGE,
|
|
297
|
-
MKACTIVITY, MKCALENDAR, MKCOL,
|
|
298
|
-
MOVE, NOTIFY, OPTIONS,
|
|
299
|
-
PATCH, POST, PROPFIND,
|
|
300
|
-
PROPPATCH, PURGE, PUT,
|
|
301
|
-
QUERY, REBIND, REPORT,
|
|
302
|
-
SEARCH, SOURCE, SUBSCRIBE,
|
|
303
|
-
TRACE, UNBIND, UNLINK,
|
|
304
|
-
UNLOCK, UNSUBSCRIBE
|
|
305
|
-
</p>
|
|
306
|
-
</details>
|
|
307
|
-
|
|
308
|
-
<br/>
|
|
309
|
-
|
|
310
|
-
### Dynamic parameters
|
|
311
|
-
Anything within square brackets is always matched.
|
|
312
|
-
|
|
313
|
-
For example, for <a href="#">/api/company/<b>123</b>/user/<b>789</b></a>,
|
|
314
|
-
the filename could be:
|
|
315
|
-
|
|
316
|
-
<pre><code>api/company/<b>[id]</b>/user/<b>[uid]</b>.GET.200.json</code></pre>
|
|
317
|
-
|
|
318
|
-
<br/>
|
|
163
|
+
<summary><b>See all config options</b></summary>
|
|
319
164
|
|
|
320
|
-
### Comments
|
|
321
|
-
Comments are anything within parentheses, including them.
|
|
322
|
-
They are ignored for routing purposes, so they have no effect
|
|
323
|
-
on the URL mask. For example, these two are for `/api/foo`
|
|
324
|
-
<pre>
|
|
325
|
-
api/foo<b>(my comment)</b>.GET.200.json
|
|
326
|
-
api/foo.GET.200.json
|
|
327
|
-
</pre>
|
|
328
|
-
|
|
329
|
-
A filename can have many comments.
|
|
330
|
-
|
|
331
|
-
<br/>
|
|
332
|
-
|
|
333
|
-
### Default mock for a route
|
|
334
|
-
You can add the comment: `(default)`.
|
|
335
|
-
Otherwise, the first file in **alphabetical order** wins.
|
|
336
|
-
|
|
337
|
-
<pre>
|
|
338
|
-
api/user<b>(default)</b>.GET.200.json
|
|
339
|
-
</pre>
|
|
340
|
-
|
|
341
|
-
<br/>
|
|
342
|
-
|
|
343
|
-
### Query string params
|
|
344
|
-
The query string is ignored for routing purposes. In other words, it’s only used for
|
|
345
|
-
documenting the URL contract.
|
|
346
|
-
<pre>
|
|
347
|
-
api/video<b>?limit=[limit]</b>.GET.200.json
|
|
348
|
-
</pre>
|
|
349
|
-
|
|
350
|
-
On Windows, filenames containing "?" are [not
|
|
351
|
-
permitted](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file), but since that’s part of the query
|
|
352
|
-
string it’s ignored anyway.
|
|
353
|
-
|
|
354
|
-
<br/>
|
|
355
|
-
|
|
356
|
-
### Index-like routes
|
|
357
|
-
If you have <a href="#">api/foo</a> and <a href="#">api/foo/bar</a>, you have two options:
|
|
358
|
-
|
|
359
|
-
**Option A.** Standard naming:
|
|
360
|
-
```
|
|
361
|
-
api/foo.GET.200.json
|
|
362
|
-
api/foo/bar.GET.200.json
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
**Option B.** Omit the URL on the filename:
|
|
366
|
-
```text
|
|
367
|
-
api/foo/.GET.200.json
|
|
368
|
-
api/foo/bar.GET.200.json
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
<br/>
|
|
372
|
-
|
|
373
|
-
## Config
|
|
374
165
|
### `mocksDir: string`
|
|
375
166
|
This is the only required field. The directory must exist.
|
|
376
167
|
|
|
@@ -598,6 +389,235 @@ config.onReady = () => {}
|
|
|
598
389
|
|
|
599
390
|
At any rate, you can trigger any command besides opening a browser.
|
|
600
391
|
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
</details>
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
<br/>
|
|
400
|
+
|
|
401
|
+
## Demo App (Vite + React)
|
|
402
|
+
|
|
403
|
+
```sh
|
|
404
|
+
git clone https://github.com/ericfortis/mockaton.git
|
|
405
|
+
cd mockaton/demo-app-vite
|
|
406
|
+
npm install
|
|
407
|
+
|
|
408
|
+
npm run mockaton
|
|
409
|
+
npm run start # in another terminal
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
The demo app has a list of colors containing all of their possible states. For example,
|
|
413
|
+
permutations for out-of-stock, new-arrival, and discontinued.
|
|
414
|
+
|
|
415
|
+
<img src="./demo-app-vite/pixaton-tests/pic-for-readme.vp740x880.light.gold.png" alt="Mockaton Demo App Screenshot" width="740" />
|
|
416
|
+
|
|
417
|
+
<br/>
|
|
418
|
+
<br/>
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
## Use Cases
|
|
422
|
+
### Testing Backend or Frontend
|
|
423
|
+
- Empty responses
|
|
424
|
+
- Errors such as _Bad Request_ and _Internal Server Error_
|
|
425
|
+
- Mocking third-party APIs
|
|
426
|
+
- Polled resources (for triggering their different states)
|
|
427
|
+
- alerts
|
|
428
|
+
- notifications
|
|
429
|
+
- slow to build resources
|
|
430
|
+
|
|
431
|
+
### Testing Frontend
|
|
432
|
+
- Spinners by delaying responses
|
|
433
|
+
- Setting up UI tests
|
|
434
|
+
|
|
435
|
+
### Demoing complex backend states
|
|
436
|
+
Sometimes, the ideal flow you need is too difficult to reproduce from the actual backend.
|
|
437
|
+
For this, you can **Bulk Select** mocks by comments to simulate the complete states
|
|
438
|
+
you want. For example, by adding `(demo-part1)`, `(demo-part2)` to the filenames.
|
|
439
|
+
|
|
440
|
+
Similarly, you can deploy a **Standalone Demo Server** by compiling the frontend app and
|
|
441
|
+
putting its built assets in `config.staticDir`. And simulate the flow by Bulk Selecting mocks.
|
|
442
|
+
The [aot-fetch-demo repo](https://github.com/ericfortis/aot-fetch-demo) has a working example.
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
<br/>
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
## You can write JSON mocks in JavaScript or TypeScript
|
|
449
|
+
For example, `api/foo.GET.200.js`
|
|
450
|
+
|
|
451
|
+
**Option A:** An Object, Array, or String is sent as JSON.
|
|
452
|
+
|
|
453
|
+
```js
|
|
454
|
+
export default { foo: 'bar' }
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
**Option B:** Function
|
|
458
|
+
|
|
459
|
+
Return a `string | Buffer | Uint8Array`, but don’t call `response.end()`
|
|
460
|
+
|
|
461
|
+
```js
|
|
462
|
+
export default (request, response) =>
|
|
463
|
+
JSON.stringify({ foo: 'bar' })
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Think of these functions as HTTP handlers. For example,
|
|
467
|
+
you can intercept requests to write to a database.
|
|
468
|
+
|
|
469
|
+
<details>
|
|
470
|
+
<summary><b>See Intercepting Requests Examples</b></summary>
|
|
471
|
+
|
|
472
|
+
Imagine you have an initial list of colors, and
|
|
473
|
+
you want to concatenate newly added colors.
|
|
474
|
+
|
|
475
|
+
`api/colors.POST.201.js`
|
|
476
|
+
```js
|
|
477
|
+
import { parseJSON } from 'mockaton'
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
export default async function insertColor(request, response) {
|
|
481
|
+
const color = await parseJSON(request)
|
|
482
|
+
globalThis.newColorsDatabase ??= []
|
|
483
|
+
globalThis.newColorsDatabase.push(color)
|
|
484
|
+
|
|
485
|
+
// These two lines are not needed but you can change their values
|
|
486
|
+
// response.statusCode = 201 // default derived from filename
|
|
487
|
+
// response.setHeader('Content-Type', 'application/json') // unconditional default
|
|
488
|
+
|
|
489
|
+
return JSON.stringify({ msg: 'CREATED' })
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
`api/colors.GET.200.js`
|
|
494
|
+
```js
|
|
495
|
+
import colorsFixture from './colors.json' with { type: 'json' }
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
export default function listColors() {
|
|
499
|
+
return JSON.stringify([
|
|
500
|
+
...colorsFixture,
|
|
501
|
+
...(globalThis.newColorsDatabase || [])
|
|
502
|
+
])
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
</details>
|
|
506
|
+
|
|
507
|
+
<br/>
|
|
508
|
+
|
|
509
|
+
**What if I need to serve a static .js or .ts?**
|
|
510
|
+
|
|
511
|
+
**Option A:** Put it in your `config.staticDir` without the `.GET.200.js` extension.
|
|
512
|
+
|
|
513
|
+
**Option B:** Read it and return it. For example:
|
|
514
|
+
```js
|
|
515
|
+
export default function (_, response) {
|
|
516
|
+
response.setHeader('Content-Type', 'application/javascript')
|
|
517
|
+
return readFileSync('./some-dir/foo.js', 'utf8')
|
|
518
|
+
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
<br/>
|
|
522
|
+
|
|
523
|
+
## Mock Filename Convention
|
|
524
|
+
|
|
525
|
+
### Extension
|
|
526
|
+
|
|
527
|
+
The last three dots are reserved for the HTTP Method,
|
|
528
|
+
Response Status Code, and File Extension.
|
|
529
|
+
|
|
530
|
+
```
|
|
531
|
+
api/user.GET.200.json
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
You can also use `.empty` or `.unknown` if you don’t
|
|
535
|
+
want a `Content-Type` header in the response.
|
|
536
|
+
|
|
537
|
+
<details>
|
|
538
|
+
<summary>Supported Methods</summary>
|
|
539
|
+
<p>From <code>require('node:http').METHODS</code></p>
|
|
540
|
+
<p>
|
|
541
|
+
ACL, BIND, CHECKOUT,
|
|
542
|
+
CONNECT, COPY, DELETE,
|
|
543
|
+
GET, HEAD, LINK,
|
|
544
|
+
LOCK, M-SEARCH, MERGE,
|
|
545
|
+
MKACTIVITY, MKCALENDAR, MKCOL,
|
|
546
|
+
MOVE, NOTIFY, OPTIONS,
|
|
547
|
+
PATCH, POST, PROPFIND,
|
|
548
|
+
PROPPATCH, PURGE, PUT,
|
|
549
|
+
QUERY, REBIND, REPORT,
|
|
550
|
+
SEARCH, SOURCE, SUBSCRIBE,
|
|
551
|
+
TRACE, UNBIND, UNLINK,
|
|
552
|
+
UNLOCK, UNSUBSCRIBE
|
|
553
|
+
</p>
|
|
554
|
+
</details>
|
|
555
|
+
|
|
556
|
+
<br/>
|
|
557
|
+
|
|
558
|
+
### Dynamic parameters
|
|
559
|
+
Anything within square brackets is always matched.
|
|
560
|
+
|
|
561
|
+
For example, for <a href="#">/api/company/<b>123</b>/user/<b>789</b></a>,
|
|
562
|
+
the filename could be:
|
|
563
|
+
|
|
564
|
+
<pre><code>api/company/<b>[id]</b>/user/<b>[uid]</b>.GET.200.json</code></pre>
|
|
565
|
+
|
|
566
|
+
<br/>
|
|
567
|
+
|
|
568
|
+
### Comments
|
|
569
|
+
Comments are anything within parentheses, including them.
|
|
570
|
+
They are ignored for routing purposes, so they have no effect
|
|
571
|
+
on the URL mask. For example, these two are for `/api/foo`
|
|
572
|
+
<pre>
|
|
573
|
+
api/foo<b>(my comment)</b>.GET.200.json
|
|
574
|
+
api/foo.GET.200.json
|
|
575
|
+
</pre>
|
|
576
|
+
|
|
577
|
+
A filename can have many comments.
|
|
578
|
+
|
|
579
|
+
<br/>
|
|
580
|
+
|
|
581
|
+
### Default mock for a route
|
|
582
|
+
You can add the comment: `(default)`.
|
|
583
|
+
Otherwise, the first file in **alphabetical order** wins.
|
|
584
|
+
|
|
585
|
+
<pre>
|
|
586
|
+
api/user<b>(default)</b>.GET.200.json
|
|
587
|
+
</pre>
|
|
588
|
+
|
|
589
|
+
<br/>
|
|
590
|
+
|
|
591
|
+
### Query string params
|
|
592
|
+
The query string is ignored for routing purposes. In other words, it’s only used for
|
|
593
|
+
documenting the URL contract.
|
|
594
|
+
<pre>
|
|
595
|
+
api/video<b>?limit=[limit]</b>.GET.200.json
|
|
596
|
+
</pre>
|
|
597
|
+
|
|
598
|
+
On Windows, filenames containing "?" are [not
|
|
599
|
+
permitted](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file), but since that’s part of the query
|
|
600
|
+
string it’s ignored anyway.
|
|
601
|
+
|
|
602
|
+
<br/>
|
|
603
|
+
|
|
604
|
+
### Index-like routes
|
|
605
|
+
If you have <a href="#">api/foo</a> and <a href="#">api/foo/bar</a>, you have two options:
|
|
606
|
+
|
|
607
|
+
**Option A.** Standard naming:
|
|
608
|
+
```
|
|
609
|
+
api/foo.GET.200.json
|
|
610
|
+
api/foo/bar.GET.200.json
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
**Option B.** Omit the URL on the filename:
|
|
614
|
+
```text
|
|
615
|
+
api/foo/.GET.200.json
|
|
616
|
+
api/foo/bar.GET.200.json
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
<br/>
|
|
620
|
+
|
|
601
621
|
<br/>
|
|
602
622
|
|
|
603
623
|
## Commander API
|
|
@@ -694,4 +714,4 @@ hijack the client (e.g., `fetch`) in Node.js and browsers.
|
|
|
694
714
|
|
|
695
715
|
---
|
|
696
716
|
|
|
697
|
-

|
package/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ type Plugin = (
|
|
|
10
10
|
}>
|
|
11
11
|
|
|
12
12
|
interface Config {
|
|
13
|
-
mocksDir
|
|
13
|
+
mocksDir?: string
|
|
14
14
|
staticDir?: string
|
|
15
15
|
ignore?: RegExp
|
|
16
16
|
|
|
@@ -44,7 +44,8 @@ interface Config {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
export function Mockaton(options: Config): Server
|
|
47
|
+
export function Mockaton(options: Partial<Config>): Server
|
|
48
|
+
export function defineConfig(options: Partial<Config>): Config
|
|
48
49
|
|
|
49
50
|
export const jsToJsonPlugin: Plugin
|
|
50
51
|
|
package/index.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
import { config } from './src/config.js'
|
|
2
|
+
|
|
1
3
|
export { Mockaton } from './src/Mockaton.js'
|
|
2
|
-
export { Commander } from './src/
|
|
4
|
+
export { Commander } from './src/ApiCommander.js'
|
|
3
5
|
|
|
4
6
|
export { jwtCookie } from './src/utils/jwt.js'
|
|
5
|
-
export {
|
|
7
|
+
export { openInBrowser } from './src/utils/openInBrowser.js'
|
|
6
8
|
export { jsToJsonPlugin } from './src/MockDispatcher.js'
|
|
9
|
+
export { parseJSON, SUPPORTED_METHODS } from './src/utils/http-request.js'
|
|
10
|
+
|
|
11
|
+
/** @param {Partial<Config>} opts */
|
|
12
|
+
export const defineConfig = opts => ({
|
|
13
|
+
...config,
|
|
14
|
+
...opts
|
|
15
|
+
})
|
package/package.json
CHANGED
|
@@ -2,22 +2,19 @@
|
|
|
2
2
|
"name": "mockaton",
|
|
3
3
|
"description": "HTTP Mock Server",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "8.
|
|
5
|
+
"version": "8.23.0",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"types": "index.d.ts",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"repository": "https://github.com/ericfortis/mockaton",
|
|
10
|
-
"keywords": [
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
"api",
|
|
15
|
-
"testing"
|
|
16
|
-
],
|
|
10
|
+
"keywords": ["mock-server", "rest-api", "mock", "api", "testing"],
|
|
11
|
+
"bin": {
|
|
12
|
+
"mockaton": "src/cli.js"
|
|
13
|
+
},
|
|
17
14
|
"scripts": {
|
|
18
15
|
"test": "node --test \"src/**/*.test.js\"",
|
|
19
16
|
"coverage": "node --test --test-reporter=lcov --test-reporter-destination=.coverage/lcov.info --experimental-test-coverage \"src/**/*.test.js\"",
|
|
20
|
-
"start": "node --watch dev
|
|
17
|
+
"start": "node --watch src/cli.js -c dev.config.js",
|
|
21
18
|
"pixaton": "node --test --import=./pixaton-tests/_setup.js --experimental-test-isolation=none \"pixaton-tests/**/*.test.js\"",
|
|
22
19
|
"outdated": "npm outdated --parseable | awk -F: '{ printf \"npm i %-30s ;# %s\\n\", $4, $2 }'"
|
|
23
20
|
},
|
package/src/Api.js
CHANGED
|
@@ -16,11 +16,11 @@ import { sendOK, sendJSON, sendUnprocessableContent, sendFile } from './utils/ht
|
|
|
16
16
|
|
|
17
17
|
const dashboardAssets = [
|
|
18
18
|
'/ApiConstants.js',
|
|
19
|
-
'/
|
|
19
|
+
'/ApiCommander.js',
|
|
20
20
|
'/Dashboard.css',
|
|
21
21
|
'/Dashboard.js',
|
|
22
22
|
'/Filename.js',
|
|
23
|
-
'/
|
|
23
|
+
'/Logo.svg'
|
|
24
24
|
]
|
|
25
25
|
|
|
26
26
|
export const apiGetRequests = new Map([
|
package/src/Dashboard.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_500_COMMENT, HEADER_FOR_502 } from './ApiConstants.js'
|
|
2
2
|
import { parseFilename } from './Filename.js'
|
|
3
|
-
import { Commander } from './
|
|
3
|
+
import { Commander } from './ApiCommander.js'
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
const Strings = {
|
|
@@ -144,7 +144,7 @@ function Header() {
|
|
|
144
144
|
r('header', null,
|
|
145
145
|
r('img', {
|
|
146
146
|
alt: Strings.title,
|
|
147
|
-
src: 'mockaton/
|
|
147
|
+
src: 'mockaton/Logo.svg',
|
|
148
148
|
width: 160
|
|
149
149
|
}),
|
|
150
150
|
r('div', null,
|
|
@@ -885,7 +885,7 @@ function syntaxJSON(json) {
|
|
|
885
885
|
text(json.slice(lastIndex))
|
|
886
886
|
return frag
|
|
887
887
|
}
|
|
888
|
-
syntaxJSON.regex = /("(?:\\u[a-fA-F0-9]{4}|\\[^u]|[^\\"])*")(\s*:)?|([{}\[\]
|
|
888
|
+
syntaxJSON.regex = /("(?:\\u[a-fA-F0-9]{4}|\\[^u]|[^\\"])*")(\s*:)?|([{}\[\],:\s]+)|\S+/g
|
|
889
889
|
// Capture group order: [string, optional colon, punc]
|
|
890
890
|
|
|
891
891
|
|
package/src/Mockaton.js
CHANGED
|
@@ -16,15 +16,24 @@ import { sendNoContent, sendInternalServerError, sendUnprocessableContent } from
|
|
|
16
16
|
process.on('unhandledRejection', error => { throw error })
|
|
17
17
|
|
|
18
18
|
export function Mockaton(options) {
|
|
19
|
-
setup(options)
|
|
19
|
+
const error = setup(options)
|
|
20
|
+
if (error) {
|
|
21
|
+
console.error(error)
|
|
22
|
+
process.exitCode = 1
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
20
26
|
mockBrokerCollection.init()
|
|
21
27
|
staticCollection.init()
|
|
22
28
|
watchMocksDir()
|
|
23
29
|
watchStaticDir()
|
|
24
30
|
|
|
25
|
-
|
|
31
|
+
const server = createServer(onRequest)
|
|
32
|
+
|
|
33
|
+
server.listen(config.port, config.host, function (error) {
|
|
26
34
|
if (error) {
|
|
27
35
|
console.error(error)
|
|
36
|
+
process.exit(1)
|
|
28
37
|
return
|
|
29
38
|
}
|
|
30
39
|
const { address, port } = this.address()
|
|
@@ -33,8 +42,16 @@ export function Mockaton(options) {
|
|
|
33
42
|
console.log('Dashboard', url + API.dashboard)
|
|
34
43
|
config.onReady(url + API.dashboard)
|
|
35
44
|
})
|
|
45
|
+
|
|
46
|
+
server.on('error', error => {
|
|
47
|
+
console.error(error.message)
|
|
48
|
+
process.exit(1)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
return server
|
|
36
52
|
}
|
|
37
53
|
|
|
54
|
+
|
|
38
55
|
async function onRequest(req, response) {
|
|
39
56
|
response.on('error', console.error)
|
|
40
57
|
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
import { parseArgs } from 'node:util'
|
|
5
|
+
|
|
6
|
+
import { isFile } from './utils/fs.js'
|
|
7
|
+
import { Mockaton } from '../index.js'
|
|
8
|
+
import pkgJSON from '../package.json' with { type: 'json' }
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
const args = parseArgs({
|
|
12
|
+
options: {
|
|
13
|
+
config: { short: 'c', type: 'string' },
|
|
14
|
+
|
|
15
|
+
port: { short: 'p', type: 'string' },
|
|
16
|
+
host: { short: 'H', type: 'string' },
|
|
17
|
+
|
|
18
|
+
'mocks-dir': { short: 'm', type: 'string' },
|
|
19
|
+
'static-dir': { short: 's', type: 'string' },
|
|
20
|
+
|
|
21
|
+
help: { short: 'h', type: 'boolean' },
|
|
22
|
+
version: { short: 'v', type: 'boolean' }
|
|
23
|
+
}
|
|
24
|
+
}).values
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if (args.version)
|
|
28
|
+
console.log(pkgJSON.version)
|
|
29
|
+
|
|
30
|
+
else if (args.help)
|
|
31
|
+
console.log(`
|
|
32
|
+
Usage: mockaton [options]
|
|
33
|
+
|
|
34
|
+
Options:
|
|
35
|
+
-c, --config <file> (default: ./mockaton.config.js)
|
|
36
|
+
|
|
37
|
+
-m, --mocks-dir <dir> (default: ./mockaton-mocks/)
|
|
38
|
+
-s, --static-dir <dir> (default: ./mockaton-static-mocks/)
|
|
39
|
+
|
|
40
|
+
-H, --host <host> (default: 127.0.0.1)
|
|
41
|
+
-p, --port <port> (default: 0) which means auto-assigned
|
|
42
|
+
|
|
43
|
+
-h, --help Show this help
|
|
44
|
+
-v, --version Show version
|
|
45
|
+
|
|
46
|
+
Notes:
|
|
47
|
+
* mockaton.config.js supports more options, see:
|
|
48
|
+
https://github.com/ericfortis/mockaton?tab=readme-ov-file#config
|
|
49
|
+
* CLI options override their mockaton.config.js counterparts`)
|
|
50
|
+
|
|
51
|
+
else if (args.config && !isFile(args.config)) {
|
|
52
|
+
console.error(`Invalid config file: ${args.config}`)
|
|
53
|
+
process.exitCode = 1
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const userConf = join(process.cwd(), args.config ?? 'mockaton.config.js')
|
|
57
|
+
const opts = isFile(userConf)
|
|
58
|
+
? (await import(userConf)).default ?? {}
|
|
59
|
+
: {}
|
|
60
|
+
|
|
61
|
+
if (args.host) opts.host = args.host
|
|
62
|
+
if (args.port) opts.port = Number(args.port)
|
|
63
|
+
|
|
64
|
+
if (args['mocks-dir']) opts.mocksDir = args['mocks-dir']
|
|
65
|
+
if (args['static-dir']) opts.staticDir = args['static-dir']
|
|
66
|
+
|
|
67
|
+
Mockaton(opts)
|
|
68
|
+
}
|
package/src/config.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { join, isAbsolute } from 'node:path'
|
|
2
|
+
|
|
2
3
|
import { isDirectory } from './utils/fs.js'
|
|
3
4
|
import { openInBrowser } from './utils/openInBrowser.js'
|
|
4
5
|
import { jsToJsonPlugin } from './MockDispatcher.js'
|
|
@@ -14,8 +15,8 @@ import { validateCorsAllowedMethods, validateCorsAllowedOrigins } from './utils/
|
|
|
14
15
|
* ]
|
|
15
16
|
* }} */
|
|
16
17
|
const schema = {
|
|
17
|
-
mocksDir: ['', isDirectory],
|
|
18
|
-
staticDir: ['', optional(isDirectory)],
|
|
18
|
+
mocksDir: [join(process.cwd(), 'mockaton-mocks'), isDirectory],
|
|
19
|
+
staticDir: [join(process.cwd(), 'mockaton-static-mocks'), optional(isDirectory)],
|
|
19
20
|
ignore: [/(\.DS_Store|~)$/, is(RegExp)],
|
|
20
21
|
|
|
21
22
|
host: ['127.0.0.1', is(String)],
|
|
@@ -65,18 +66,30 @@ export const config = Object.seal(defaults)
|
|
|
65
66
|
export const ConfigValidator = Object.freeze(validators)
|
|
66
67
|
|
|
67
68
|
|
|
69
|
+
/** @param {Partial<Config>} options */
|
|
70
|
+
export function setup(options) {
|
|
71
|
+
if (options.mocksDir && !isAbsolute(options.mocksDir))
|
|
72
|
+
options.mocksDir = join(process.cwd(), options.mocksDir)
|
|
73
|
+
|
|
74
|
+
if (options.staticDir && !isAbsolute(options.staticDir))
|
|
75
|
+
options.staticDir = join(process.cwd(), options.staticDir)
|
|
76
|
+
|
|
77
|
+
if (!options.staticDir && !isDirectory(defaults.staticDir))
|
|
78
|
+
options.staticDir = ''
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
Object.assign(config, options)
|
|
82
|
+
validate(config, ConfigValidator)
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
return err.message
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
68
90
|
export const isFileAllowed = f => !config.ignore.test(f)
|
|
69
91
|
|
|
70
92
|
export const calcDelay = () => config.delayJitter
|
|
71
93
|
? config.delay * (1 + Math.random() * config.delayJitter)
|
|
72
94
|
: config.delay
|
|
73
95
|
|
|
74
|
-
|
|
75
|
-
export function setup(options) {
|
|
76
|
-
Object.assign(config, options)
|
|
77
|
-
validate(config, ConfigValidator)
|
|
78
|
-
|
|
79
|
-
config.mocksDir = realpathSync(config.mocksDir)
|
|
80
|
-
if (config.staticDir)
|
|
81
|
-
config.staticDir = realpathSync(config.staticDir)
|
|
82
|
-
}
|
|
File without changes
|
|
File without changes
|