mockaton 8.12.5 → 8.12.7
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 +25 -12
- package/package.json +1 -1
- package/src/Api.js +16 -13
- package/src/ApiConstants.js +2 -2
- package/src/Commander.js +4 -4
- package/src/Dashboard.js +36 -36
- package/src/MockBroker.js +2 -2
- package/src/Watcher.js +8 -8
package/README.md
CHANGED
|
@@ -7,8 +7,9 @@ An HTTP mock server for simulating APIs with minimal setup
|
|
|
7
7
|
— ideal for triggering difficult to reproduce backend states.
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
##
|
|
11
|
-
Mockaton
|
|
10
|
+
## Overview
|
|
11
|
+
With Mockaton, you don’t need to write code for wiring up your mocks. Instead, a
|
|
12
|
+
given directory is scanned for filenames following a convention similar to the URLs.
|
|
12
13
|
|
|
13
14
|
For example, for <code>/<b>api/user</b>/1234</code> the filename would be:
|
|
14
15
|
<pre>
|
|
@@ -158,16 +159,6 @@ putting its built assets in `config.staticDir`. And simulate the flow by Bulk Se
|
|
|
158
159
|
|
|
159
160
|
<br/>
|
|
160
161
|
|
|
161
|
-
## Alternatives
|
|
162
|
-
- Chrome DevTools allows for [overriding responses](https://developer.chrome.com/docs/devtools/overrides)
|
|
163
|
-
- Reverse Proxies such as [Burp](https://portswigger.net/burp) are also handy for overriding responses.
|
|
164
|
-
- [MSW (Mock Server Worker)](https://mswjs.io)
|
|
165
|
-
- [Nock](https://github.com/nock/nock)
|
|
166
|
-
- [Fetch Mock](https://github.com/wheresrhys/fetch-mock)
|
|
167
|
-
- [Mentoss](https://github.com/humanwhocodes/mentoss)
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
<br/>
|
|
171
162
|
|
|
172
163
|
## You can write JSON mocks in JavaScript or TypeScript
|
|
173
164
|
For example, `api/foo.GET.200.js`
|
|
@@ -606,3 +597,25 @@ default, but the `proxyFallback`, `colledProxied`, and `corsAllowed` are not aff
|
|
|
606
597
|
```js
|
|
607
598
|
await mockaton.reset()
|
|
608
599
|
```
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
<br/>
|
|
603
|
+
|
|
604
|
+
## Alternatives worth learning as well
|
|
605
|
+
|
|
606
|
+
### Proxy-like
|
|
607
|
+
These are similar to Mockaton in the sense that you can modify the
|
|
608
|
+
mock response without loosing or risking your frontend code state. For
|
|
609
|
+
example, if you are polling, and you want to test the state change.
|
|
610
|
+
|
|
611
|
+
- Chrome DevTools allows for [overriding responses](https://developer.chrome.com/docs/devtools/overrides)
|
|
612
|
+
- Reverse Proxies such as [Burp](https://portswigger.net/burp) are also handy for overriding responses
|
|
613
|
+
|
|
614
|
+
### Client side
|
|
615
|
+
In contrast to Mockaton, which is an HTTP Server, these programs
|
|
616
|
+
mock the client (e.g., `fetch`) in Node.js and browsers.
|
|
617
|
+
|
|
618
|
+
- [MSW (Mock Server Worker)](https://mswjs.io)
|
|
619
|
+
- [Nock](https://github.com/nock/nock)
|
|
620
|
+
- [Fetch Mock](https://github.com/wheresrhys/fetch-mock)
|
|
621
|
+
- [Mentoss](https://github.com/humanwhocodes/mentoss) Has a server side too
|
package/package.json
CHANGED
package/src/Api.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { join } from 'node:path'
|
|
7
7
|
import { cookie } from './cookie.js'
|
|
8
|
-
import {
|
|
8
|
+
import { uiSyncVersion } from './Watcher.js'
|
|
9
9
|
import { parseJSON } from './utils/http-request.js'
|
|
10
10
|
import { listFilesRecursively } from './utils/fs.js'
|
|
11
11
|
import * as mockBrokersCollection from './mockBrokersCollection.js'
|
|
@@ -31,9 +31,9 @@ export const apiGetRequests = new Map([
|
|
|
31
31
|
[API.mocks, listMockBrokers],
|
|
32
32
|
[API.cookies, listCookies],
|
|
33
33
|
[API.fallback, getProxyFallback],
|
|
34
|
-
[API.arEvents, longPollAR_Events],
|
|
35
34
|
[API.comments, listComments],
|
|
36
35
|
[API.globalDelay, getGlobalDelay],
|
|
36
|
+
[API.syncVersion, longPollClientSyncVersion],
|
|
37
37
|
[API.collectProxied, getCollectProxied]
|
|
38
38
|
])
|
|
39
39
|
|
|
@@ -51,7 +51,7 @@ export const apiPatchRequests = new Map([
|
|
|
51
51
|
])
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
/** # GET */
|
|
55
55
|
|
|
56
56
|
function serveDashboard(_, response) {
|
|
57
57
|
sendFile(response, join(import.meta.dirname, 'Dashboard.html'))
|
|
@@ -76,28 +76,31 @@ function listStaticFiles(req, response) {
|
|
|
76
76
|
: [])
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
sendJSON(response, arEvents.count)
|
|
79
|
+
function longPollClientSyncVersion(req, response) {
|
|
80
|
+
if (uiSyncVersion.version !== Number(req.headers[DF.syncVersion])) {
|
|
81
|
+
// e.g., tab was hidden while new mocks were added or removed
|
|
82
|
+
sendJSON(response, uiSyncVersion.version)
|
|
84
83
|
return
|
|
85
84
|
}
|
|
85
|
+
|
|
86
86
|
function onAddOrRemoveMock() {
|
|
87
|
-
|
|
88
|
-
sendJSON(response,
|
|
87
|
+
uiSyncVersion.unsubscribe(onAddOrRemoveMock)
|
|
88
|
+
sendJSON(response, uiSyncVersion.version)
|
|
89
89
|
}
|
|
90
|
+
|
|
90
91
|
response.setTimeout(LONG_POLL_SERVER_TIMEOUT, onAddOrRemoveMock)
|
|
92
|
+
|
|
91
93
|
req.on('error', () => {
|
|
92
|
-
|
|
94
|
+
uiSyncVersion.unsubscribe(onAddOrRemoveMock)
|
|
93
95
|
response.destroy()
|
|
94
96
|
})
|
|
95
|
-
|
|
97
|
+
|
|
98
|
+
uiSyncVersion.subscribe(onAddOrRemoveMock)
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
|
|
99
102
|
|
|
100
|
-
|
|
103
|
+
/** # PATCH */
|
|
101
104
|
|
|
102
105
|
function reinitialize(_, response) {
|
|
103
106
|
mockBrokersCollection.init()
|
package/src/ApiConstants.js
CHANGED
|
@@ -14,7 +14,7 @@ export const API = {
|
|
|
14
14
|
proxied: MOUNT + '/proxied',
|
|
15
15
|
cors: MOUNT + '/cors',
|
|
16
16
|
static: MOUNT + '/static',
|
|
17
|
-
|
|
17
|
+
syncVersion: MOUNT + '/sync_version'
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export const DF = { // Dashboard Fields (XHR)
|
|
@@ -22,7 +22,7 @@ export const DF = { // Dashboard Fields (XHR)
|
|
|
22
22
|
routeUrlMask: 'route_url_mask',
|
|
23
23
|
delayed: 'delayed',
|
|
24
24
|
proxied: 'proxied',
|
|
25
|
-
|
|
25
|
+
syncVersion: 'last_received_sync_version'
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export const DEFAULT_500_COMMENT = '(Mockaton 500)'
|
package/src/Commander.js
CHANGED
|
@@ -92,11 +92,11 @@ export class Commander {
|
|
|
92
92
|
return this.#patch(API.reset)
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
return fetch(API.
|
|
97
|
-
signal: AbortSignal.timeout(LONG_POLL_SERVER_TIMEOUT + 1000),
|
|
95
|
+
getSyncVersion(currentSyncVersion, abortSignal) {
|
|
96
|
+
return fetch(API.syncVersion, {
|
|
97
|
+
signal: AbortSignal.any([abortSignal, AbortSignal.timeout(LONG_POLL_SERVER_TIMEOUT + 1000)]),
|
|
98
98
|
headers: {
|
|
99
|
-
[DF.
|
|
99
|
+
[DF.syncVersion]: currentSyncVersion
|
|
100
100
|
}
|
|
101
101
|
})
|
|
102
102
|
}
|
package/src/Dashboard.js
CHANGED
|
@@ -68,11 +68,7 @@ let globalDelay = 1200
|
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
init()
|
|
71
|
-
|
|
72
|
-
document.addEventListener('visibilitychange', () => {
|
|
73
|
-
if (!document.hidden)
|
|
74
|
-
pollAR_Events()
|
|
75
|
-
})
|
|
71
|
+
initLongPoll()
|
|
76
72
|
|
|
77
73
|
function init() {
|
|
78
74
|
return Promise.all([
|
|
@@ -97,7 +93,8 @@ function App([brokersByMethod, cookies, comments, delay, collectProxied, fallbac
|
|
|
97
93
|
r(StaticFilesList, { staticFiles })))
|
|
98
94
|
}
|
|
99
95
|
|
|
100
|
-
|
|
96
|
+
|
|
97
|
+
/** # Header */
|
|
101
98
|
|
|
102
99
|
function Header({ cookies, comments, delay, fallbackAddress, collectProxied }) {
|
|
103
100
|
return (
|
|
@@ -240,8 +237,7 @@ function ResetButton() {
|
|
|
240
237
|
}
|
|
241
238
|
|
|
242
239
|
|
|
243
|
-
|
|
244
|
-
// MockList ===============
|
|
240
|
+
/** # MockList */
|
|
245
241
|
|
|
246
242
|
function MockList({ brokersByMethod, canProxy }) {
|
|
247
243
|
const hasMocks = Object.keys(brokersByMethod).length
|
|
@@ -256,7 +252,6 @@ function MockList({ brokersByMethod, canProxy }) {
|
|
|
256
252
|
r(PayloadViewer)))
|
|
257
253
|
}
|
|
258
254
|
|
|
259
|
-
|
|
260
255
|
function SectionByMethod({ method, brokers, canProxy }) {
|
|
261
256
|
return (
|
|
262
257
|
r('tbody', null,
|
|
@@ -273,7 +268,6 @@ function SectionByMethod({ method, brokers, canProxy }) {
|
|
|
273
268
|
r('td', null, r(ProxyToggler, { broker, disabled: !canProxy }))))))
|
|
274
269
|
}
|
|
275
270
|
|
|
276
|
-
|
|
277
271
|
function PreviewLink({ method, urlMask }) {
|
|
278
272
|
async function onClick(event) {
|
|
279
273
|
event.preventDefault()
|
|
@@ -294,7 +288,6 @@ function PreviewLink({ method, urlMask }) {
|
|
|
294
288
|
}, urlMask))
|
|
295
289
|
}
|
|
296
290
|
|
|
297
|
-
|
|
298
291
|
function MockSelector({ broker }) {
|
|
299
292
|
function onChange() {
|
|
300
293
|
const { urlMask, method } = parseFilename(this.value)
|
|
@@ -349,7 +342,6 @@ function DelayRouteToggler({ broker }) {
|
|
|
349
342
|
TimerIcon()))
|
|
350
343
|
}
|
|
351
344
|
|
|
352
|
-
|
|
353
345
|
function InternalServerErrorToggler({ broker }) {
|
|
354
346
|
function onChange() {
|
|
355
347
|
const { urlMask, method } = parseFilename(broker.mocks[0])
|
|
@@ -375,7 +367,6 @@ function InternalServerErrorToggler({ broker }) {
|
|
|
375
367
|
r('span', null, '500')))
|
|
376
368
|
}
|
|
377
369
|
|
|
378
|
-
|
|
379
370
|
function ProxyToggler({ broker, disabled }) {
|
|
380
371
|
function onChange() {
|
|
381
372
|
const { urlMask, method } = parseFilename(broker.mocks[0])
|
|
@@ -399,8 +390,7 @@ function ProxyToggler({ broker, disabled }) {
|
|
|
399
390
|
}
|
|
400
391
|
|
|
401
392
|
|
|
402
|
-
|
|
403
|
-
// Payload Preview ===============
|
|
393
|
+
/** # Payload Preview */
|
|
404
394
|
|
|
405
395
|
const payloadViewerTitleRef = useRef()
|
|
406
396
|
const payloadViewerRef = useRef()
|
|
@@ -497,8 +487,7 @@ function mockSelectorFor(method, urlMask) {
|
|
|
497
487
|
}
|
|
498
488
|
|
|
499
489
|
|
|
500
|
-
|
|
501
|
-
// StaticFilesList ===============
|
|
490
|
+
/** # StaticFilesList */
|
|
502
491
|
|
|
503
492
|
function StaticFilesList({ staticFiles }) {
|
|
504
493
|
if (!staticFiles.length)
|
|
@@ -515,7 +504,7 @@ function StaticFilesList({ staticFiles }) {
|
|
|
515
504
|
}
|
|
516
505
|
|
|
517
506
|
|
|
518
|
-
|
|
507
|
+
/** # Misc */
|
|
519
508
|
|
|
520
509
|
function onError(error) {
|
|
521
510
|
if (error?.message === 'Failed to fetch')
|
|
@@ -537,43 +526,54 @@ function CloudIcon() {
|
|
|
537
526
|
}
|
|
538
527
|
|
|
539
528
|
|
|
540
|
-
|
|
529
|
+
/**
|
|
530
|
+
* # Poll UI Sync Version
|
|
531
|
+
* The version increments when a mock file is added or removed
|
|
532
|
+
*/
|
|
533
|
+
|
|
534
|
+
function initLongPoll() {
|
|
535
|
+
poll.oldSyncVersion = 0
|
|
536
|
+
poll.controller = new AbortController()
|
|
537
|
+
poll()
|
|
538
|
+
document.addEventListener('visibilitychange', () => {
|
|
539
|
+
if (document.hidden) {
|
|
540
|
+
poll.controller.abort('_hidden_tab_')
|
|
541
|
+
poll.controller = new AbortController()
|
|
542
|
+
}
|
|
543
|
+
else
|
|
544
|
+
poll()
|
|
545
|
+
})
|
|
546
|
+
}
|
|
541
547
|
|
|
542
|
-
|
|
543
|
-
pollAR_Events.oldAR_EventsCount = 0
|
|
544
|
-
async function pollAR_Events() {
|
|
545
|
-
if (pollAR_Events.isPolling || document.hidden)
|
|
546
|
-
return
|
|
548
|
+
async function poll() {
|
|
547
549
|
try {
|
|
548
|
-
|
|
549
|
-
const response = await mockaton.getAR_EventsCount(pollAR_Events.oldAR_EventsCount)
|
|
550
|
+
const response = await mockaton.getSyncVersion(poll.oldSyncVersion, poll.controller.signal)
|
|
550
551
|
if (response.ok) {
|
|
551
|
-
const
|
|
552
|
-
if (
|
|
553
|
-
|
|
552
|
+
const syncVersion = await response.json()
|
|
553
|
+
if (poll.oldSyncVersion !== syncVersion) { // because it could be < or >
|
|
554
|
+
poll.oldSyncVersion = syncVersion
|
|
554
555
|
await init()
|
|
555
556
|
}
|
|
556
|
-
|
|
557
|
-
pollAR_Events()
|
|
557
|
+
poll()
|
|
558
558
|
}
|
|
559
559
|
else
|
|
560
560
|
throw response.status
|
|
561
561
|
}
|
|
562
|
-
catch (
|
|
563
|
-
|
|
564
|
-
|
|
562
|
+
catch (error) {
|
|
563
|
+
if (error !== '_hidden_tab_')
|
|
564
|
+
setTimeout(poll, 3000)
|
|
565
565
|
}
|
|
566
566
|
}
|
|
567
567
|
|
|
568
568
|
|
|
569
|
-
|
|
569
|
+
/** # Utils */
|
|
570
570
|
|
|
571
571
|
function cssClass(...args) {
|
|
572
572
|
return args.filter(Boolean).join(' ')
|
|
573
573
|
}
|
|
574
574
|
|
|
575
575
|
|
|
576
|
-
|
|
576
|
+
/** ## React-compatible simplified implementations */
|
|
577
577
|
|
|
578
578
|
function createElement(elem, props = null, ...children) {
|
|
579
579
|
if (typeof elem === 'function')
|
package/src/MockBroker.js
CHANGED
|
@@ -2,8 +2,8 @@ import { includesComment, extractComments, parseFilename } from './Filename.js'
|
|
|
2
2
|
import { DEFAULT_500_COMMENT, DEFAULT_MOCK_COMMENT } from './ApiConstants.js'
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/** MockBroker is a state for a particular route. It knows the available mock files
|
|
6
|
+
* that can be served for the route, the currently selected file, and if it’s delayed. */
|
|
7
7
|
export class MockBroker {
|
|
8
8
|
constructor(file) {
|
|
9
9
|
this.urlMaskMatches = new UrlMatcher(file).urlMaskMatches
|
package/src/Watcher.js
CHANGED
|
@@ -7,16 +7,16 @@ import { isFile } from './utils/fs.js'
|
|
|
7
7
|
import * as mockBrokerCollection from './mockBrokersCollection.js'
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
export const
|
|
12
|
-
|
|
10
|
+
/** # AR = Add or Remove Mock Event */
|
|
11
|
+
export const uiSyncVersion = new class extends EventEmitter {
|
|
12
|
+
version = 0
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
this.
|
|
14
|
+
increment() {
|
|
15
|
+
this.version++
|
|
16
16
|
super.emit('AR')
|
|
17
17
|
}
|
|
18
18
|
subscribe(listener) {
|
|
19
|
-
this.
|
|
19
|
+
this.once('AR', listener)
|
|
20
20
|
}
|
|
21
21
|
unsubscribe(listener) {
|
|
22
22
|
this.removeListener('AR', listener)
|
|
@@ -30,11 +30,11 @@ export function watchMocksDir() {
|
|
|
30
30
|
return
|
|
31
31
|
if (isFile(join(dir, file))) {
|
|
32
32
|
if (mockBrokerCollection.registerMock(file, 'isFromWatcher'))
|
|
33
|
-
|
|
33
|
+
uiSyncVersion.increment()
|
|
34
34
|
}
|
|
35
35
|
else {
|
|
36
36
|
mockBrokerCollection.unregisterMock(file)
|
|
37
|
-
|
|
37
|
+
uiSyncVersion.increment()
|
|
38
38
|
}
|
|
39
39
|
})
|
|
40
40
|
}
|