mockaton 8.12.9 → 8.13.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 +36 -30
- package/package.json +2 -3
- package/src/ApiConstants.js +6 -6
- package/src/Dashboard.css +11 -0
- package/src/Dashboard.js +63 -17
- package/src/Watcher.js +1 -1
- package/src/mockBrokersCollection.js +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|

|
|
5
|
+
[](https://github.com/ericfortis/mockaton/actions/workflows/test.yml)
|
|
6
|
+
[](https://github.com/ericfortis/mockaton/actions/workflows/github-code-scanning/codeql)
|
|
5
7
|
|
|
6
8
|
An HTTP mock server for simulating APIs with minimal setup
|
|
7
9
|
— ideal for triggering difficult to reproduce backend states.
|
|
@@ -27,9 +29,9 @@ Nonetheless, there’s a programmatic API, which is handy
|
|
|
27
29
|
for setting up tests (see **Commander API** section).
|
|
28
30
|
|
|
29
31
|
<picture>
|
|
30
|
-
<source media="(prefers-color-scheme: light)" srcset="
|
|
31
|
-
<source media="(prefers-color-scheme: dark)" srcset="
|
|
32
|
-
<img alt="Mockaton Dashboard" src="
|
|
32
|
+
<source media="(prefers-color-scheme: light)" srcset="pixaton-tests/macos/pic-for-readme.vp840x800.light.gold.png">
|
|
33
|
+
<source media="(prefers-color-scheme: dark)" srcset="pixaton-tests/macos/pic-for-readme.vp840x800.dark.gold.png">
|
|
34
|
+
<img alt="Mockaton Dashboard" src="pixaton-tests/macos/pic-for-readme.vp840x800.light.gold.png">
|
|
33
35
|
</picture>
|
|
34
36
|
|
|
35
37
|
|
|
@@ -74,12 +76,10 @@ They will be saved in your `config.mocksDir` following the filename convention.
|
|
|
74
76
|
|
|
75
77
|
|
|
76
78
|
## Basic Usage
|
|
77
|
-
Mockaton is a Node.js program
|
|
78
|
-
|
|
79
|
-
`tsx` is only needed if you want to write mocks in TypeScript.
|
|
79
|
+
Mockaton is a Node.js program.
|
|
80
80
|
|
|
81
81
|
```sh
|
|
82
|
-
npm install mockaton
|
|
82
|
+
npm install mockaton --save-dev
|
|
83
83
|
```
|
|
84
84
|
|
|
85
85
|
Create a `my-mockaton.js` file
|
|
@@ -95,7 +95,7 @@ Mockaton({
|
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
```sh
|
|
98
|
-
node
|
|
98
|
+
node my-mockaton.js
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
<br/>
|
|
@@ -121,7 +121,7 @@ npm run start # in another terminal
|
|
|
121
121
|
|
|
122
122
|
The app looks like this:
|
|
123
123
|
|
|
124
|
-
<img src="./demo-app-vite/pixaton-tests/pic-for-readme.
|
|
124
|
+
<img src="./demo-app-vite/pixaton-tests/pic-for-readme.vp740x880.light.gold.png" alt="Mockaton Demo App Screenshot" width="740" />
|
|
125
125
|
|
|
126
126
|
<br/>
|
|
127
127
|
|
|
@@ -366,17 +366,16 @@ or that you manually picked with the ☁️ **Cloud Checkbox**.
|
|
|
366
366
|
|
|
367
367
|
### `collectProxied?: boolean`
|
|
368
368
|
Defaults to `false`. With this flag you can save mocks that hit
|
|
369
|
-
your proxy fallback to `config.mocksDir`. If
|
|
370
|
-
|
|
369
|
+
your proxy fallback to `config.mocksDir`. If the URL has v4 UUIDs,
|
|
370
|
+
the filename will have `[id]` in their place. For example:
|
|
371
371
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
my-mocks-dir
|
|
375
|
-
|
|
372
|
+
<pre>
|
|
373
|
+
<b>/api/user/</b>d14e09c8-d970-4b07-be42-b2f4ee22f0a6<b>/likes</b> =>
|
|
374
|
+
my-mocks-dir<b>/api/user/</b>[id]<b>/likes</b>.GET.200.json
|
|
375
|
+
</pre>
|
|
376
376
|
|
|
377
|
-
Your existing mocks won’t be overwritten.
|
|
378
|
-
|
|
379
|
-
a unique filename comment.
|
|
377
|
+
Your existing mocks won’t be overwritten. In other words, responses of routes with
|
|
378
|
+
the ☁️ **Cloud Checkbox** selected will be saved with unique filename-comments.
|
|
380
379
|
|
|
381
380
|
|
|
382
381
|
<details>
|
|
@@ -394,8 +393,8 @@ the predefined list. For that, you can add it to <code>config.extraMimes</code>
|
|
|
394
393
|
|
|
395
394
|
|
|
396
395
|
### `formatCollectedJSON?: boolean`
|
|
397
|
-
Defaults to `true`. Saves the mock with
|
|
398
|
-
of `JSON.stringify(data, null, ' ')`
|
|
396
|
+
Defaults to `true`. Saves the mock with two spaces indentation —
|
|
397
|
+
the formatting output of `JSON.stringify(data, null, ' ')`
|
|
399
398
|
|
|
400
399
|
|
|
401
400
|
<br/>
|
|
@@ -412,14 +411,16 @@ config.cookies = {
|
|
|
412
411
|
'My JWT': jwtCookie('my-cookie', {
|
|
413
412
|
name: 'John Doe',
|
|
414
413
|
picture: 'https://cdn.auth0.com/avatars/jd.png'
|
|
415
|
-
})
|
|
414
|
+
}),
|
|
415
|
+
'None': ''
|
|
416
416
|
}
|
|
417
417
|
```
|
|
418
|
-
The selected cookie, which is the first one by default, is sent in every
|
|
419
|
-
|
|
418
|
+
The selected cookie, which is the first one by default, is sent in every response in a
|
|
419
|
+
`Set-Cookie` header (as long as its value is not an empty string). The object key is just
|
|
420
|
+
a label for UI display purposes, and also for selecting a cookie via the Commander API.
|
|
420
421
|
|
|
421
|
-
If you need to send more
|
|
422
|
-
in `config.extraHeaders`, or in function `.js` or `.ts` mock.
|
|
422
|
+
If you need to send more than one cookie, you can inject them globally
|
|
423
|
+
in `config.extraHeaders`, or individually in a function `.js` or `.ts` mock.
|
|
423
424
|
|
|
424
425
|
By the way, the `jwtCookie` helper has a hardcoded header and signature.
|
|
425
426
|
In other words, it’s useful only if you care about its payload.
|
|
@@ -480,8 +481,8 @@ import { jsToJsonPlugin } from 'mockaton'
|
|
|
480
481
|
|
|
481
482
|
config.plugins = [
|
|
482
483
|
|
|
483
|
-
// Although `jsToJsonPlugin` is set by default, you need to
|
|
484
|
-
//
|
|
484
|
+
// Although `jsToJsonPlugin` is set by default, you need to include it if you need it.
|
|
485
|
+
// IOW, your plugins array overwrites the default list. This way you can remove it.
|
|
485
486
|
[/\.(js|ts)$/, jsToJsonPlugin],
|
|
486
487
|
|
|
487
488
|
[/\.yml$/, yamlToJsonPlugin],
|
|
@@ -508,6 +509,10 @@ function capitalizePlugin(filePath) {
|
|
|
508
509
|
|
|
509
510
|
### `corsAllowed?: boolean`
|
|
510
511
|
Defaults to `true`. When `true`, these are the default options:
|
|
512
|
+
|
|
513
|
+
<details>
|
|
514
|
+
<summary>CORS Options</summary>
|
|
515
|
+
|
|
511
516
|
```js
|
|
512
517
|
config.corsOrigins = ['*']
|
|
513
518
|
config.corsMethods = require('node:http').METHODS
|
|
@@ -516,6 +521,7 @@ config.corsCredentials = true
|
|
|
516
521
|
config.corsMaxAge = 0 // seconds to cache the preflight req
|
|
517
522
|
config.corsExposedHeaders = [] // headers you need to access in client-side JS
|
|
518
523
|
```
|
|
524
|
+
</details>
|
|
519
525
|
|
|
520
526
|
<br/>
|
|
521
527
|
|
|
@@ -554,9 +560,9 @@ await mockaton.select('api/foo.200.GET.json')
|
|
|
554
560
|
```js
|
|
555
561
|
await mockaton.bulkSelectByComment('(demo-a)')
|
|
556
562
|
```
|
|
557
|
-
Parentheses are optional, so you can pass a partial match.
|
|
558
|
-
|
|
559
|
-
first mock in alphabetical order
|
|
563
|
+
Parentheses are optional, so you can pass a partial match. For example,
|
|
564
|
+
passing `'demo-'` (without the final `a`) works too. On routes
|
|
565
|
+
with many partial matches, their first mock in alphabetical order wins.
|
|
560
566
|
|
|
561
567
|
<br/>
|
|
562
568
|
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "mockaton",
|
|
3
3
|
"description": "HTTP Mock Server",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "8.
|
|
5
|
+
"version": "8.13.0",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"types": "index.d.ts",
|
|
8
8
|
"license": "MIT",
|
|
@@ -19,13 +19,12 @@
|
|
|
19
19
|
"test": "node --test \"src/**/*.test.js\"",
|
|
20
20
|
"coverage": "node --test --test-reporter=lcov --test-reporter-destination=.coverage/lcov.info --experimental-test-coverage \"src/**/*.test.js\"",
|
|
21
21
|
"start": "node dev-mockaton.js",
|
|
22
|
-
"start:ts": "node --import=tsx dev-mockaton.js",
|
|
23
22
|
"pixaton": "node --test --import=./pixaton-tests/_setup.js --experimental-test-isolation=none \"pixaton-tests/**/*.test.js\"",
|
|
24
23
|
"outdated": "npm outdated --parseable | awk -F: '{ printf \"npm i %-30s ;# %s\\n\", $4, $2 }'"
|
|
25
24
|
},
|
|
26
25
|
"optionalDependencies": {
|
|
27
26
|
"open": "^10.0.0",
|
|
28
|
-
"pixaton": ">=1.
|
|
27
|
+
"pixaton": ">=1.1.1",
|
|
29
28
|
"puppeteer": ">=24.1.1"
|
|
30
29
|
}
|
|
31
30
|
}
|
package/src/ApiConstants.js
CHANGED
|
@@ -2,17 +2,17 @@ const MOUNT = '/mockaton'
|
|
|
2
2
|
export const API = {
|
|
3
3
|
dashboard: MOUNT,
|
|
4
4
|
bulkSelect: MOUNT + '/bulk-select-by-comment',
|
|
5
|
+
collectProxied: MOUNT + '/collect-proxied',
|
|
5
6
|
comments: MOUNT + '/comments',
|
|
6
|
-
|
|
7
|
+
cookies: MOUNT + '/cookies',
|
|
8
|
+
cors: MOUNT + '/cors',
|
|
7
9
|
delay: MOUNT + '/delay',
|
|
10
|
+
fallback: MOUNT + '/fallback',
|
|
8
11
|
globalDelay: MOUNT + '/global-delay',
|
|
9
12
|
mocks: MOUNT + '/mocks',
|
|
10
|
-
reset: MOUNT + '/reset',
|
|
11
|
-
cookies: MOUNT + '/cookies',
|
|
12
|
-
fallback: MOUNT + '/fallback',
|
|
13
|
-
collectProxied: MOUNT + '/collect-proxied',
|
|
14
13
|
proxied: MOUNT + '/proxied',
|
|
15
|
-
|
|
14
|
+
reset: MOUNT + '/reset',
|
|
15
|
+
select: MOUNT + '/select',
|
|
16
16
|
static: MOUNT + '/static',
|
|
17
17
|
syncVersion: MOUNT + '/sync_version'
|
|
18
18
|
}
|
package/src/Dashboard.css
CHANGED
|
@@ -288,6 +288,12 @@ select {
|
|
|
288
288
|
border-radius: var(--radius);
|
|
289
289
|
color: var(--colorAccent);
|
|
290
290
|
text-decoration: none;
|
|
291
|
+
word-break: break-word;
|
|
292
|
+
|
|
293
|
+
span {
|
|
294
|
+
opacity: 0.5;
|
|
295
|
+
filter: saturate(0.5);
|
|
296
|
+
}
|
|
291
297
|
|
|
292
298
|
&:hover {
|
|
293
299
|
background: var(--colorHover);
|
|
@@ -510,6 +516,11 @@ select {
|
|
|
510
516
|
&:hover {
|
|
511
517
|
text-decoration: underline;
|
|
512
518
|
}
|
|
519
|
+
|
|
520
|
+
span {
|
|
521
|
+
opacity: 0.5;
|
|
522
|
+
filter: saturate(0.5);
|
|
523
|
+
}
|
|
513
524
|
}
|
|
514
525
|
}
|
|
515
526
|
|
package/src/Dashboard.js
CHANGED
|
@@ -253,22 +253,25 @@ function MockList({ brokersByMethod, canProxy }) {
|
|
|
253
253
|
}
|
|
254
254
|
|
|
255
255
|
function SectionByMethod({ method, brokers, canProxy }) {
|
|
256
|
+
const brokersSorted = Object.entries(brokers)
|
|
257
|
+
.filter(([, broker]) => broker.mocks.length > 1) // >1 because of autogen500
|
|
258
|
+
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
259
|
+
|
|
260
|
+
const urlMasks = brokersSorted.map(([urlMask]) => urlMask)
|
|
261
|
+
const urlMasksHighlighted = highlightCommonPathPrefixes(urlMasks)
|
|
256
262
|
return (
|
|
257
263
|
r('tbody', null,
|
|
258
264
|
r('th', null, method),
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
r('
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function PreviewLink({ method, urlMask }) {
|
|
265
|
+
brokersSorted.map(([urlMask, broker], i) =>
|
|
266
|
+
r('tr', { 'data-method': method, 'data-urlMask': urlMask },
|
|
267
|
+
r('td', null, r(PreviewLink, { method, urlMask, urlMaskHighlighted: urlMasksHighlighted[i] })),
|
|
268
|
+
r('td', null, r(MockSelector, { broker })),
|
|
269
|
+
r('td', null, r(InternalServerErrorToggler, { broker })),
|
|
270
|
+
r('td', null, r(DelayRouteToggler, { broker })),
|
|
271
|
+
r('td', null, r(ProxyToggler, { broker, disabled: !canProxy }))))))
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function PreviewLink({ method, urlMask, urlMaskHighlighted }) {
|
|
272
275
|
async function onClick(event) {
|
|
273
276
|
event.preventDefault()
|
|
274
277
|
try {
|
|
@@ -284,8 +287,9 @@ function PreviewLink({ method, urlMask }) {
|
|
|
284
287
|
r('a', {
|
|
285
288
|
className: CSS.PreviewLink,
|
|
286
289
|
href: urlMask,
|
|
287
|
-
onClick
|
|
288
|
-
|
|
290
|
+
onClick,
|
|
291
|
+
innerHTML: urlMaskHighlighted
|
|
292
|
+
}))
|
|
289
293
|
}
|
|
290
294
|
|
|
291
295
|
function MockSelector({ broker }) {
|
|
@@ -492,15 +496,20 @@ function mockSelectorFor(method, urlMask) {
|
|
|
492
496
|
function StaticFilesList({ staticFiles }) {
|
|
493
497
|
if (!staticFiles.length)
|
|
494
498
|
return null
|
|
499
|
+
const highlighted = highlightCommonPathPrefixes(staticFiles)
|
|
495
500
|
return (
|
|
496
501
|
r('section', {
|
|
497
502
|
open: true,
|
|
498
503
|
className: CSS.StaticFilesList
|
|
499
504
|
},
|
|
500
505
|
r('h2', null, Strings.static_get),
|
|
501
|
-
r('ul', null, staticFiles.map(f =>
|
|
506
|
+
r('ul', null, staticFiles.map((f, i) =>
|
|
502
507
|
r('li', null,
|
|
503
|
-
r('a', {
|
|
508
|
+
r('a', {
|
|
509
|
+
href: f,
|
|
510
|
+
target: '_blank',
|
|
511
|
+
innerHTML: highlighted[i]
|
|
512
|
+
}))))))
|
|
504
513
|
}
|
|
505
514
|
|
|
506
515
|
|
|
@@ -607,3 +616,40 @@ function createSvgElement(tagName, props, ...children) {
|
|
|
607
616
|
function useRef() {
|
|
608
617
|
return { current: null }
|
|
609
618
|
}
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
/** This is for styling the repeated paths with a faint style */
|
|
624
|
+
function highlightCommonPathPrefixes(paths) {
|
|
625
|
+
const seen = []
|
|
626
|
+
const result = []
|
|
627
|
+
for (const path of paths) {
|
|
628
|
+
let longestPrefixSegments = []
|
|
629
|
+
|
|
630
|
+
for (const prev of seen) {
|
|
631
|
+
const currSegs = path.split('/')
|
|
632
|
+
const prevSegs = prev.split('/')
|
|
633
|
+
|
|
634
|
+
let i = 0
|
|
635
|
+
while (i < currSegs.length && i < prevSegs.length && currSegs[i] === prevSegs[i])
|
|
636
|
+
i++
|
|
637
|
+
|
|
638
|
+
if (i > longestPrefixSegments.length)
|
|
639
|
+
longestPrefixSegments = currSegs.slice(0, i)
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (longestPrefixSegments.length > 0) {
|
|
643
|
+
let prefix = longestPrefixSegments.join('/')
|
|
644
|
+
if (!prefix.endsWith('/'))
|
|
645
|
+
prefix += '/' // always end with slash for dirs
|
|
646
|
+
const suffix = path.slice(prefix.length)
|
|
647
|
+
result.push(`<span>${prefix}</span>${suffix}`)
|
|
648
|
+
}
|
|
649
|
+
else
|
|
650
|
+
result.push(path)
|
|
651
|
+
|
|
652
|
+
seen.push(path)
|
|
653
|
+
}
|
|
654
|
+
return result
|
|
655
|
+
}
|
package/src/Watcher.js
CHANGED
|
@@ -29,7 +29,7 @@ export function watchMocksDir() {
|
|
|
29
29
|
if (!file)
|
|
30
30
|
return
|
|
31
31
|
if (isFile(join(dir, file))) {
|
|
32
|
-
if (mockBrokerCollection.registerMock(file, 'isFromWatcher'))
|
|
32
|
+
if (mockBrokerCollection.registerMock(file, Boolean('isFromWatcher')))
|
|
33
33
|
uiSyncVersion.increment()
|
|
34
34
|
}
|
|
35
35
|
else {
|
|
@@ -36,7 +36,7 @@ export function init() {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/** @returns {boolean} registered */
|
|
39
|
-
export function registerMock(file, isFromWatcher) {
|
|
39
|
+
export function registerMock(file, isFromWatcher = false) {
|
|
40
40
|
if (findBrokerByFilename(file)?.hasMock(file)
|
|
41
41
|
|| !isFileAllowed(file)
|
|
42
42
|
|| !filenameIsValid(file))
|