mockaton 12.7.2 → 13.0.1
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 +53 -17
- package/index.d.ts +2 -13
- package/package.json +4 -9
- package/src/client/ApiCommander.js +3 -7
- package/src/client/ApiConstants.js +1 -4
- package/src/client/Filename.js +20 -20
- package/src/client/app-header.js +6 -6
- package/src/client/app-payload-viewer.js +47 -16
- package/src/client/app-store.js +11 -64
- package/src/client/app-store.test.js +15 -1
- package/src/client/app.css +51 -38
- package/src/client/app.js +28 -100
- package/src/client/{app-icons.js → graphics.js} +1 -1
- package/src/client/watcherDev.js +7 -7
- package/src/server/Api.js +4 -38
- package/src/server/MockBroker.js +16 -13
- package/src/server/MockDispatcher.js +18 -4
- package/src/server/Mockaton.js +1 -8
- package/src/server/Mockaton.test.config.js +1 -3
- package/src/server/Mockaton.test.js +65 -181
- package/src/server/Watcher.js +0 -32
- package/src/server/cli.js +9 -13
- package/src/server/cli.test.js +4 -5
- package/src/server/config.js +0 -7
- package/src/server/mockBrokersCollection.js +11 -13
- package/src/server/StaticDispatcher.js +0 -36
- package/src/server/staticCollection.js +0 -56
package/src/server/MockBroker.js
CHANGED
|
@@ -11,14 +11,15 @@ export class MockBroker {
|
|
|
11
11
|
this.file = '' // selected mock filename
|
|
12
12
|
this.mocks = [] // filenames
|
|
13
13
|
this.status = -1
|
|
14
|
+
this.isStatic = false // doesn’t follow filename convention
|
|
14
15
|
this.delayed = false
|
|
15
16
|
this.proxied = false
|
|
16
|
-
this.
|
|
17
|
+
this.autoStatus = 0
|
|
17
18
|
this.urlMaskMatches = new UrlMatcher(file).urlMaskMatches
|
|
18
19
|
this.register(file)
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
#
|
|
22
|
+
#isStatus = (file, status) => parseFilename(file).status === status
|
|
22
23
|
|
|
23
24
|
#sortMocks() {
|
|
24
25
|
this.mocks.sort()
|
|
@@ -27,7 +28,7 @@ export class MockBroker {
|
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
register(file) {
|
|
30
|
-
if (this.
|
|
31
|
+
if (this.autoStatus && this.#isStatus(file, this.autoStatus))
|
|
31
32
|
this.selectFile(file)
|
|
32
33
|
this.mocks.push(file)
|
|
33
34
|
this.#sortMocks()
|
|
@@ -44,27 +45,29 @@ export class MockBroker {
|
|
|
44
45
|
hasMock = file => this.mocks.includes(file)
|
|
45
46
|
|
|
46
47
|
selectFile(filename) {
|
|
48
|
+
const { status, isStatic } = parseFilename(filename)
|
|
47
49
|
this.file = filename
|
|
50
|
+
this.status = status
|
|
51
|
+
this.isStatic = isStatic
|
|
48
52
|
this.proxied = false
|
|
49
|
-
this.
|
|
50
|
-
this.status = parseFilename(filename).status
|
|
53
|
+
this.autoStatus = 0
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
selectDefaultFile() {
|
|
54
57
|
this.selectFile(this.mocks[0])
|
|
55
58
|
}
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
const shouldUnset = this.
|
|
60
|
+
toggleStatus(status) {
|
|
61
|
+
const shouldUnset = this.autoStatus === status || (!this.autoStatus && this.status === status)
|
|
59
62
|
if (shouldUnset)
|
|
60
63
|
this.selectDefaultFile()
|
|
61
64
|
else {
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
64
|
-
this.selectFile(
|
|
65
|
+
const fStatus = this.mocks.find(f => parseFilename(f).status === status)
|
|
66
|
+
if (fStatus)
|
|
67
|
+
this.selectFile(fStatus)
|
|
65
68
|
else {
|
|
66
|
-
this.
|
|
67
|
-
this.status =
|
|
69
|
+
this.autoStatus = status
|
|
70
|
+
this.status = status
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
73
|
this.proxied = false
|
|
@@ -75,7 +78,7 @@ export class MockBroker {
|
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
setProxied(proxied) {
|
|
78
|
-
this.
|
|
81
|
+
this.autoStatus = 0
|
|
79
82
|
this.proxied = proxied
|
|
80
83
|
}
|
|
81
84
|
|
|
@@ -4,6 +4,7 @@ import { logger } from './utils/logger.js'
|
|
|
4
4
|
|
|
5
5
|
import { proxy } from './ProxyRelay.js'
|
|
6
6
|
import { cookie } from './cookie.js'
|
|
7
|
+
import { parseFilename } from '../client/Filename.js'
|
|
7
8
|
import { echoFilePlugin } from './MockDispatcherPlugins.js'
|
|
8
9
|
import { brokerByRoute } from './mockBrokersCollection.js'
|
|
9
10
|
import { config, calcDelay } from './config.js'
|
|
@@ -29,12 +30,25 @@ export async function dispatchMock(req, response) {
|
|
|
29
30
|
if (cookie.getCurrent())
|
|
30
31
|
response.setHeader('Set-Cookie', cookie.getCurrent())
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
const { isStatic } = parseFilename(broker.file)
|
|
34
|
+
|
|
35
|
+
if (isStatic && req.headers.range && !broker.autoStatus) {
|
|
36
|
+
setTimeout(async () => {
|
|
37
|
+
await response.partialContent(req.headers.range, join(config.mocksDir, broker.file))
|
|
38
|
+
}, Number(broker.delayed && calcDelay()))
|
|
39
|
+
logger.accessMock(req.url, broker.file)
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
response.statusCode = broker.autoStatus
|
|
44
|
+
? broker.autoStatus
|
|
34
45
|
: broker.status
|
|
35
|
-
|
|
46
|
+
|
|
47
|
+
const { mime, body } = broker.autoStatus
|
|
36
48
|
? { mime: '', body: '' }
|
|
37
|
-
:
|
|
49
|
+
: isStatic
|
|
50
|
+
? echoFilePlugin(join(config.mocksDir, broker.file))
|
|
51
|
+
: await applyPlugins(join(config.mocksDir, broker.file), req, response)
|
|
38
52
|
|
|
39
53
|
response.setHeader('Content-Type', mime)
|
|
40
54
|
response.setHeader('Content-Length', length(body))
|
package/src/server/Mockaton.js
CHANGED
|
@@ -15,25 +15,21 @@ import { config, setup } from './config.js'
|
|
|
15
15
|
import { apiPatchReqs, apiGetReqs } from './Api.js'
|
|
16
16
|
|
|
17
17
|
import { dispatchMock } from './MockDispatcher.js'
|
|
18
|
-
import { dispatchStatic } from './StaticDispatcher.js'
|
|
19
18
|
|
|
20
|
-
import * as staticCollection from './staticCollection.js'
|
|
21
19
|
import * as mockBrokerCollection from './mockBrokersCollection.js'
|
|
22
20
|
|
|
23
21
|
import { watchDevSPA } from './WatcherDevClient.js'
|
|
24
|
-
import { watchMocksDir
|
|
22
|
+
import { watchMocksDir } from './Watcher.js'
|
|
25
23
|
|
|
26
24
|
|
|
27
25
|
export function Mockaton(options) {
|
|
28
26
|
return new Promise((resolve, reject) => {
|
|
29
27
|
setup(options)
|
|
30
28
|
mockBrokerCollection.init()
|
|
31
|
-
staticCollection.init()
|
|
32
29
|
|
|
33
30
|
if (config.watcherEnabled) {
|
|
34
31
|
register('./cacheBustResolver.js', import.meta.url)
|
|
35
32
|
watchMocksDir()
|
|
36
|
-
watchStaticDir()
|
|
37
33
|
}
|
|
38
34
|
if (config.hotReload)
|
|
39
35
|
watchDevSPA()
|
|
@@ -84,9 +80,6 @@ async function onRequest(req, response) {
|
|
|
84
80
|
else if (method === 'GET' && apiGetReqs.has(pathname))
|
|
85
81
|
apiGetReqs.get(pathname)(req, response)
|
|
86
82
|
|
|
87
|
-
else if (method === 'GET' && staticCollection.brokerByRoute(pathname))
|
|
88
|
-
await dispatchStatic(req, response)
|
|
89
|
-
|
|
90
83
|
else
|
|
91
84
|
await dispatchMock(req, response)
|
|
92
85
|
}
|
|
@@ -6,9 +6,7 @@ export default {
|
|
|
6
6
|
userB: jwtCookie('CookieB', { email: 'john@example.test' }),
|
|
7
7
|
},
|
|
8
8
|
extraHeaders: ['custom_header_name', 'custom_header_val'],
|
|
9
|
-
extraMimes: {
|
|
10
|
-
['custom_extension']: 'custom_mime'
|
|
11
|
-
},
|
|
9
|
+
extraMimes: { ['custom_extension']: 'custom_mime' },
|
|
12
10
|
logLevel: 'verbose',
|
|
13
11
|
corsOrigins: ['https://example.test'],
|
|
14
12
|
corsExposedHeaders: ['Content-Encoding'],
|
|
@@ -17,14 +17,12 @@ import CONFIG from './Mockaton.test.config.js'
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
const mocksDir = mkdtempSync(join(tmpdir(), 'mocks'))
|
|
20
|
-
const staticDir = mkdtempSync(join(tmpdir(), 'static'))
|
|
21
20
|
|
|
22
21
|
const stdout = []
|
|
23
22
|
const stderr = []
|
|
24
23
|
const proc = spawn(join(import.meta.dirname, 'cli.js'), [
|
|
24
|
+
mocksDir,
|
|
25
25
|
'--config', join(import.meta.dirname, 'Mockaton.test.config.js'),
|
|
26
|
-
'--mocks-dir', mocksDir,
|
|
27
|
-
'--static-dir', staticDir,
|
|
28
26
|
'--no-open'
|
|
29
27
|
])
|
|
30
28
|
|
|
@@ -47,10 +45,8 @@ const listFromMocksDir = d => readdir(join(mocksDir, d))
|
|
|
47
45
|
const readFromMocksDir = f => readFile(join(mocksDir, f), 'utf8')
|
|
48
46
|
|
|
49
47
|
const makeDirInMocks = dir => mkdir(join(mocksDir, dir), { recursive: true })
|
|
50
|
-
const makeDirInStaticMocks = dir => mkdir(join(staticDir, dir), { recursive: true })
|
|
51
48
|
|
|
52
49
|
const renameInMocksDir = (src, target) => rename(join(mocksDir, src), join(mocksDir, target))
|
|
53
|
-
const renameInStaticMocksDir = (src, target) => rename(join(staticDir, src), join(staticDir, target))
|
|
54
50
|
|
|
55
51
|
|
|
56
52
|
const api = new Commander(serverAddr)
|
|
@@ -128,7 +124,7 @@ class Fixture extends BaseFixture {
|
|
|
128
124
|
class FixtureStatic extends BaseFixture {
|
|
129
125
|
constructor(file, body = '') {
|
|
130
126
|
super(file, body)
|
|
131
|
-
this.dir =
|
|
127
|
+
this.dir = mocksDir
|
|
132
128
|
this.urlMask = '/' + file
|
|
133
129
|
this.method = 'GET'
|
|
134
130
|
}
|
|
@@ -149,26 +145,24 @@ describe('Windows', () => {
|
|
|
149
145
|
|
|
150
146
|
describe('Rejects malicious URLs', () => {
|
|
151
147
|
[
|
|
152
|
-
['double-encoded', `/${encodeURIComponent(encodeURIComponent('/'))}user
|
|
153
|
-
['encoded null byte', '/user%00/admin'
|
|
154
|
-
['invalid percent-encoding', '/user%ZZ'
|
|
155
|
-
['encoded CRLF sequence', '/user%0d%0aSet-Cookie:%20x=1'
|
|
156
|
-
['overlong/illegal UTF-8 sequence', '/user%C0%AF'
|
|
157
|
-
['double-double-encoding trick', '/%25252Fuser'
|
|
158
|
-
['zero-width/invisible char', '/user%E2%80%8Binfo'
|
|
159
|
-
['encoded path traversal', '/user/..%2Fadmin'
|
|
160
|
-
['raw path traversal', '/../user'
|
|
161
|
-
|
|
162
|
-
['very long path', '/'.repeat(2048 + 1), 414]
|
|
148
|
+
['double-encoded', 400, `/${encodeURIComponent(encodeURIComponent('/'))}user`],
|
|
149
|
+
['encoded null byte', 400, '/user%00/admin'],
|
|
150
|
+
['invalid percent-encoding', 400, '/user%ZZ'],
|
|
151
|
+
['encoded CRLF sequence', 400, '/user%0d%0aSet-Cookie:%20x=1'],
|
|
152
|
+
['overlong/illegal UTF-8 sequence', 400, '/user%C0%AF'],
|
|
153
|
+
['double-double-encoding trick', 400, '/%25252Fuser'],
|
|
154
|
+
['zero-width/invisible char', 404, '/user%E2%80%8Binfo'],
|
|
155
|
+
['encoded path traversal', 404, '/user/..%2Fadmin'],
|
|
156
|
+
['raw path traversal', 404, '/../user'],
|
|
157
|
+
['very long path', 414, '/'.repeat(2048 + 1)]
|
|
163
158
|
]
|
|
164
|
-
.
|
|
165
|
-
|
|
166
|
-
equal((await request(url)).status, status)))
|
|
159
|
+
.forEach(([title, status, url]) => test(title, async () =>
|
|
160
|
+
equal((await request(url)).status, status)))
|
|
167
161
|
})
|
|
168
162
|
|
|
169
163
|
|
|
170
|
-
describe('
|
|
171
|
-
test('
|
|
164
|
+
describe('Filename Convention', () => {
|
|
165
|
+
test('registers invalid filenames as GET 200', async () => {
|
|
172
166
|
const fx0 = new Fixture('bar.GET._INVALID_STATUS_.json')
|
|
173
167
|
const fx1 = new Fixture('foo._INVALID_METHOD_.202.json')
|
|
174
168
|
const fx2 = new Fixture('missing-method-and-status.json')
|
|
@@ -177,10 +171,10 @@ describe('Warnings', () => {
|
|
|
177
171
|
await fx2.write()
|
|
178
172
|
await api.reset()
|
|
179
173
|
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
174
|
+
const s = await fetchState()
|
|
175
|
+
equal(s.brokersByMethod.GET['/bar.GET._INVALID_STATUS_.json'].file, 'bar.GET._INVALID_STATUS_.json')
|
|
176
|
+
equal(s.brokersByMethod.GET['/foo._INVALID_METHOD_.202.json'].file, 'foo._INVALID_METHOD_.202.json')
|
|
177
|
+
equal(s.brokersByMethod.GET['/missing-method-and-status.json'].file, 'missing-method-and-status.json')
|
|
184
178
|
|
|
185
179
|
await fx0.unlink()
|
|
186
180
|
await fx1.unlink()
|
|
@@ -373,7 +367,7 @@ describe('Proxy Fallback', () => {
|
|
|
373
367
|
const CUSTOM_COOKIES = ['cookieX=x', 'cookieY=y']
|
|
374
368
|
const BODY_PAYLOAD = { a: 'b' }
|
|
375
369
|
const expectedBody = JSON.stringify(BODY_PAYLOAD, null, ' ') // config.formatCollectedJSON=true
|
|
376
|
-
|
|
370
|
+
|
|
377
371
|
before(async () => {
|
|
378
372
|
fallbackServer = createServer(async (req, response) => {
|
|
379
373
|
response.writeHead(423, {
|
|
@@ -499,20 +493,20 @@ describe('Proxy Fallback', () => {
|
|
|
499
493
|
equal((await r.json()).proxied, false)
|
|
500
494
|
})
|
|
501
495
|
|
|
502
|
-
test('unsets
|
|
496
|
+
test('unsets autoStatus', async () => {
|
|
503
497
|
const fx = new Fixture('unset-500-on-proxy.GET.200.txt')
|
|
504
498
|
await fx.sync()
|
|
505
499
|
await api.setProxyFallback('https://example.test')
|
|
506
500
|
|
|
507
|
-
const r0 = await api.
|
|
501
|
+
const r0 = await api.toggleStatus(500, fx.method, fx.urlMask)
|
|
508
502
|
const b0 = await r0.json()
|
|
509
503
|
equal(b0.proxied, false)
|
|
510
|
-
equal(b0.
|
|
504
|
+
equal(b0.autoStatus, 500)
|
|
511
505
|
|
|
512
506
|
const r1 = await api.setRouteIsProxied(fx.method, fx.urlMask, true)
|
|
513
507
|
const b1 = await r1.json()
|
|
514
508
|
equal(b1.proxied, true)
|
|
515
|
-
equal(b1.
|
|
509
|
+
equal(b1.autoStatus, 0)
|
|
516
510
|
|
|
517
511
|
await fx.unlink()
|
|
518
512
|
await api.setProxyFallback('')
|
|
@@ -555,14 +549,6 @@ describe('404', () => {
|
|
|
555
549
|
await fx.unlink()
|
|
556
550
|
})
|
|
557
551
|
|
|
558
|
-
test('404s ignored static files', async () => {
|
|
559
|
-
const fx = new FixtureStatic('static-ignored.js~')
|
|
560
|
-
await fx.write()
|
|
561
|
-
await api.reset()
|
|
562
|
-
const r = await fx.request()
|
|
563
|
-
equal(r.status, 404)
|
|
564
|
-
await fx.unlink()
|
|
565
|
-
})
|
|
566
552
|
})
|
|
567
553
|
|
|
568
554
|
|
|
@@ -662,7 +648,7 @@ describe('Static Files', () => {
|
|
|
662
648
|
await fxsIndex.write()
|
|
663
649
|
await fxsAsset.write()
|
|
664
650
|
await api.reset()
|
|
665
|
-
})
|
|
651
|
+
})
|
|
666
652
|
|
|
667
653
|
describe('Static File Serving', () => {
|
|
668
654
|
test('Defaults to index.html', async () => {
|
|
@@ -680,64 +666,10 @@ describe('Static Files', () => {
|
|
|
680
666
|
})
|
|
681
667
|
})
|
|
682
668
|
|
|
683
|
-
test('
|
|
684
|
-
const
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
fxsIndex.urlMask
|
|
688
|
-
])
|
|
689
|
-
})
|
|
690
|
-
|
|
691
|
-
describe('Set Static Route is Delayed', () => {
|
|
692
|
-
test('422 for non-existing route', async () => {
|
|
693
|
-
const r = await api.setStaticRouteIsDelayed('/non-existing', true)
|
|
694
|
-
equal(r.status, 422)
|
|
695
|
-
equal(await r.text(), `Static route does not exist: /non-existing`)
|
|
696
|
-
})
|
|
697
|
-
|
|
698
|
-
test('422 for invalid delayed value', async () => {
|
|
699
|
-
const r = await api.setStaticRouteIsDelayed(fxsIndex.urlMask, 'not-a-boolean')
|
|
700
|
-
equal(await r.text(), 'Expected boolean for "delayed"')
|
|
701
|
-
})
|
|
702
|
-
|
|
703
|
-
test('200', async () => {
|
|
704
|
-
await api.setStaticRouteIsDelayed(fxsIndex.urlMask, true)
|
|
705
|
-
const { staticBrokers } = await fetchState()
|
|
706
|
-
equal(staticBrokers[fxsIndex.urlMask].delayed, true)
|
|
707
|
-
})
|
|
708
|
-
})
|
|
709
|
-
|
|
710
|
-
describe('Set Static Route Status Code', () => {
|
|
711
|
-
test('422 for non-existing route', async () => {
|
|
712
|
-
const r = await api.setStaticRouteStatus('/non-existing', 200)
|
|
713
|
-
equal(r.status, 422)
|
|
714
|
-
equal(await r.text(), `Static route does not exist: /non-existing`)
|
|
715
|
-
})
|
|
716
|
-
|
|
717
|
-
test('422 for invalid delayed value', async () => {
|
|
718
|
-
const r = await api.setStaticRouteStatus(fxsIndex.urlMask, 'not-200-or-404')
|
|
719
|
-
equal(r.status, 422)
|
|
720
|
-
equal(await r.text(), 'Expected 200 or 404 status code')
|
|
721
|
-
})
|
|
722
|
-
|
|
723
|
-
test('sets 404 and 200', async () => {
|
|
724
|
-
await api.setStaticRouteStatus(fxsIndex.urlMask, 404)
|
|
725
|
-
const r0 = await fxsIndex.request()
|
|
726
|
-
equal(r0.status, 404)
|
|
727
|
-
|
|
728
|
-
await api.setStaticRouteStatus(fxsIndex.urlMask, 200)
|
|
729
|
-
const r1 = await fxsIndex.request()
|
|
730
|
-
equal(r1.status, 200)
|
|
731
|
-
})
|
|
732
|
-
|
|
733
|
-
test('404s on a registered route but its file has been deleted', async () => {
|
|
734
|
-
// Possible: (1) due to watcher delay. (2) or, when not-watching and deleting.
|
|
735
|
-
const fx = new FixtureStatic('to-be-deleted.js')
|
|
736
|
-
await fx.sync()
|
|
737
|
-
await fx.unlink()
|
|
738
|
-
const r = await fx.request()
|
|
739
|
-
equal(r.status, 404)
|
|
740
|
-
})
|
|
669
|
+
test('are part of the normal mocks list', async () => {
|
|
670
|
+
const s = await fetchState()
|
|
671
|
+
equal(s.brokersByMethod.GET[fxsAsset.urlMask].file, fxsAsset.file)
|
|
672
|
+
equal(s.brokersByMethod.GET[fxsIndex.urlMask].file, fxsIndex.file)
|
|
741
673
|
})
|
|
742
674
|
|
|
743
675
|
describe('Static Partial Content', () => {
|
|
@@ -761,27 +693,27 @@ describe('Static Files', () => {
|
|
|
761
693
|
await fxsIndex.unlink()
|
|
762
694
|
await fxsAsset.unlink()
|
|
763
695
|
await api.reset()
|
|
764
|
-
const
|
|
765
|
-
equal(
|
|
766
|
-
equal(
|
|
696
|
+
const s = await fetchState()
|
|
697
|
+
equal(s.brokersByMethod.GET?.[fxsIndex.urlMask], undefined)
|
|
698
|
+
equal(s.brokersByMethod.GET?.[fxsAsset.urlMask], undefined)
|
|
767
699
|
})
|
|
768
700
|
})
|
|
769
701
|
|
|
770
702
|
|
|
771
|
-
describe('
|
|
703
|
+
describe('Auto Status', () => {
|
|
772
704
|
test('toggling ON 500 on a route without 500 auto-generates one', async () => {
|
|
773
705
|
const fx = new Fixture('toggling-500-without-500.GET.200.json')
|
|
774
706
|
await fx.sync()
|
|
775
707
|
equal((await fx.request()).status, fx.status)
|
|
776
708
|
|
|
777
|
-
const bp0 = await api.
|
|
709
|
+
const bp0 = await api.toggleStatus(500, fx.method, fx.urlMask)
|
|
778
710
|
const b0 = await bp0.json()
|
|
779
|
-
equal(b0.
|
|
711
|
+
equal(b0.autoStatus, 500)
|
|
780
712
|
equal(b0.status, 500)
|
|
781
713
|
equal((await fx.request()).status, 500)
|
|
782
714
|
|
|
783
|
-
const r1 = await api.
|
|
784
|
-
equal((await r1.json()).
|
|
715
|
+
const r1 = await api.toggleStatus(500, fx.method, fx.urlMask)
|
|
716
|
+
equal((await r1.json()).autoStatus, 0)
|
|
785
717
|
equal((await fx.request()).status, fx.status)
|
|
786
718
|
})
|
|
787
719
|
|
|
@@ -792,15 +724,15 @@ describe('500', () => {
|
|
|
792
724
|
await fx500.write()
|
|
793
725
|
await api.reset()
|
|
794
726
|
|
|
795
|
-
const bp0 = await api.
|
|
727
|
+
const bp0 = await api.toggleStatus(500, fx200.method, fx200.urlMask)
|
|
796
728
|
const b0 = await bp0.json()
|
|
797
|
-
equal(b0.
|
|
729
|
+
equal(b0.autoStatus, 0)
|
|
798
730
|
equal(b0.status, 500)
|
|
799
731
|
equal(await (await fx200.request()).text(), fx500.body)
|
|
800
732
|
|
|
801
|
-
const bp1 = await api.
|
|
733
|
+
const bp1 = await api.toggleStatus(500, fx200.method, fx200.urlMask)
|
|
802
734
|
const b1 = await bp1.json()
|
|
803
|
-
equal(b0.
|
|
735
|
+
equal(b0.autoStatus, 0)
|
|
804
736
|
equal(b1.status, 200)
|
|
805
737
|
equal(await (await fx200.request()).text(), fx200.body)
|
|
806
738
|
|
|
@@ -813,11 +745,29 @@ describe('500', () => {
|
|
|
813
745
|
await fx.sync()
|
|
814
746
|
await api.setProxyFallback('https://example.test')
|
|
815
747
|
await api.setRouteIsProxied(fx.method, fx.urlMask, true)
|
|
816
|
-
await api.
|
|
748
|
+
await api.toggleStatus(500, fx.method, fx.urlMask)
|
|
817
749
|
equal((await fx.fetchBroker()).proxied, false)
|
|
818
750
|
await fx.unlink()
|
|
819
751
|
await api.setProxyFallback('')
|
|
820
752
|
})
|
|
753
|
+
|
|
754
|
+
test('toggling ON 404 for static routes', async () => {
|
|
755
|
+
const fx = new FixtureStatic('static-404.txt')
|
|
756
|
+
await fx.sync()
|
|
757
|
+
equal((await fx.request()).status, 200)
|
|
758
|
+
|
|
759
|
+
const bp0 = await api.toggleStatus(404, fx.method, fx.urlMask)
|
|
760
|
+
const b0 = await bp0.json()
|
|
761
|
+
equal(b0.autoStatus, 404)
|
|
762
|
+
equal(b0.status, 404)
|
|
763
|
+
equal((await fx.request()).status, 404)
|
|
764
|
+
|
|
765
|
+
const r1 = await api.toggleStatus(404, fx.method, fx.urlMask)
|
|
766
|
+
equal((await r1.json()).autoStatus, 0)
|
|
767
|
+
equal((await fx.request()).status, 200)
|
|
768
|
+
|
|
769
|
+
await fx.unlink()
|
|
770
|
+
})
|
|
821
771
|
})
|
|
822
772
|
|
|
823
773
|
|
|
@@ -1110,16 +1060,16 @@ describe('Registering Mocks', () => {
|
|
|
1110
1060
|
equal(b, undefined)
|
|
1111
1061
|
})
|
|
1112
1062
|
|
|
1113
|
-
test('registering a 500 unsets
|
|
1063
|
+
test('registering a 500 unsets autoStatus', async () => {
|
|
1114
1064
|
const fx200 = new Fixture('reg-error.GET.200.txt')
|
|
1115
1065
|
const fx500 = new Fixture('reg-error.GET.500.txt')
|
|
1116
1066
|
await fx200.register()
|
|
1117
|
-
await api.
|
|
1067
|
+
await api.toggleStatus(500, fx200.method, fx200.urlMask)
|
|
1118
1068
|
const b0 = await fx200.fetchBroker()
|
|
1119
|
-
equal(b0.
|
|
1069
|
+
equal(b0.autoStatus, 500)
|
|
1120
1070
|
await fx500.register()
|
|
1121
1071
|
const b1 = await fx200.fetchBroker()
|
|
1122
|
-
equal(b1.
|
|
1072
|
+
equal(b1.autoStatus, 0)
|
|
1123
1073
|
deepEqual(b1.mocks, [
|
|
1124
1074
|
fx200.file,
|
|
1125
1075
|
fx500.file
|
|
@@ -1162,72 +1112,6 @@ describe('Registering Mocks', () => {
|
|
|
1162
1112
|
})
|
|
1163
1113
|
|
|
1164
1114
|
|
|
1165
|
-
describe('Registering Static Mocks', () => {
|
|
1166
|
-
test('when watcher is off, newly added mocks do not get registered', async () => {
|
|
1167
|
-
await api.setWatchMocks(false)
|
|
1168
|
-
const fx = new FixtureStatic('non-auto-registered-file.txt')
|
|
1169
|
-
await fx.write()
|
|
1170
|
-
await sleep()
|
|
1171
|
-
const { staticBrokers } = await fetchState()
|
|
1172
|
-
equal(staticBrokers['/' + fx.file], undefined)
|
|
1173
|
-
await fx.unlink()
|
|
1174
|
-
})
|
|
1175
|
-
|
|
1176
|
-
const fx = new FixtureStatic('static-register.txt', 'static-body')
|
|
1177
|
-
test('registers static', async () => {
|
|
1178
|
-
await api.setWatchMocks(true)
|
|
1179
|
-
await fx.register()
|
|
1180
|
-
const { staticBrokers } = await fetchState()
|
|
1181
|
-
deepEqual(staticBrokers, {
|
|
1182
|
-
['/' + fx.file]: {
|
|
1183
|
-
route: '/' + fx.file,
|
|
1184
|
-
status: 200,
|
|
1185
|
-
delayed: false
|
|
1186
|
-
}
|
|
1187
|
-
})
|
|
1188
|
-
const response = await fx.request()
|
|
1189
|
-
equal(response.status, 200)
|
|
1190
|
-
equal(await response.text(), fx.body)
|
|
1191
|
-
})
|
|
1192
|
-
|
|
1193
|
-
test('unregisters static', async () => {
|
|
1194
|
-
await fx.unregister()
|
|
1195
|
-
const { staticBrokers } = await fetchState()
|
|
1196
|
-
deepEqual(staticBrokers, {})
|
|
1197
|
-
})
|
|
1198
|
-
|
|
1199
|
-
describe('getSyncVersion', () => {
|
|
1200
|
-
const fx0 = new FixtureStatic('reg0/static0.txt')
|
|
1201
|
-
let version
|
|
1202
|
-
before(async () => {
|
|
1203
|
-
await makeDirInStaticMocks('reg0')
|
|
1204
|
-
await fx0.sync()
|
|
1205
|
-
version = await resolveOnNextSyncVersion(-1)
|
|
1206
|
-
})
|
|
1207
|
-
|
|
1208
|
-
const fx = new FixtureStatic('static1.txt')
|
|
1209
|
-
test('responds when a file is added', async () => {
|
|
1210
|
-
const prom = resolveOnNextSyncVersion(version)
|
|
1211
|
-
await fx.write()
|
|
1212
|
-
equal(await prom, version + 1)
|
|
1213
|
-
})
|
|
1214
|
-
|
|
1215
|
-
test('responds when a file is deleted', async () => {
|
|
1216
|
-
const prom = resolveOnNextSyncVersion(version + 1)
|
|
1217
|
-
await fx.unlink()
|
|
1218
|
-
equal(await prom, version + 2)
|
|
1219
|
-
})
|
|
1220
|
-
|
|
1221
|
-
test('responds when dir is renamed', async () => {
|
|
1222
|
-
const p0 = resolveOnNextSyncVersion(version + 2)
|
|
1223
|
-
await renameInStaticMocksDir('reg0', 'reg1')
|
|
1224
|
-
equal(await p0, version + 3)
|
|
1225
|
-
|
|
1226
|
-
const s = await fetchState()
|
|
1227
|
-
equal(s.staticBrokers['/reg1/static0.txt'].route, '/reg1/static0.txt')
|
|
1228
|
-
})
|
|
1229
|
-
})
|
|
1230
|
-
})
|
|
1231
1115
|
|
|
1232
1116
|
|
|
1233
1117
|
function sleep(ms = 100) {
|
package/src/server/Watcher.js
CHANGED
|
@@ -5,12 +5,10 @@ import { EventEmitter } from 'node:events'
|
|
|
5
5
|
import { config } from './config.js'
|
|
6
6
|
import { isFile, isDirectory } from './utils/fs.js'
|
|
7
7
|
|
|
8
|
-
import * as staticCollection from './staticCollection.js'
|
|
9
8
|
import * as mockBrokerCollection from './mockBrokersCollection.js'
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
let mocksWatcher = null
|
|
13
|
-
let staticWatcher = null
|
|
14
12
|
|
|
15
13
|
|
|
16
14
|
/**
|
|
@@ -67,33 +65,6 @@ export function watchMocksDir() {
|
|
|
67
65
|
}
|
|
68
66
|
|
|
69
67
|
|
|
70
|
-
export function watchStaticDir() {
|
|
71
|
-
const dir = config.staticDir
|
|
72
|
-
if (!dir)
|
|
73
|
-
return
|
|
74
|
-
|
|
75
|
-
staticWatcher = staticWatcher || watch(dir, { recursive: true, persistent: false }, (_, file) => {
|
|
76
|
-
if (!file)
|
|
77
|
-
return
|
|
78
|
-
|
|
79
|
-
if (isDirectory(join(dir, file))) {
|
|
80
|
-
staticCollection.init()
|
|
81
|
-
uiSyncVersion.increment()
|
|
82
|
-
}
|
|
83
|
-
else if (!isFile(join(dir, file))) { // file deleted
|
|
84
|
-
staticCollection.unregisterMock(file)
|
|
85
|
-
uiSyncVersion.increment()
|
|
86
|
-
}
|
|
87
|
-
else if (staticCollection.registerMock(file))
|
|
88
|
-
uiSyncVersion.increment()
|
|
89
|
-
else {
|
|
90
|
-
// ignore file edits
|
|
91
|
-
}
|
|
92
|
-
})
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
68
|
/** Realtime notify ARR Events */
|
|
98
69
|
export function sseClientSyncVersion(req, response) {
|
|
99
70
|
response.writeHead(200, {
|
|
@@ -125,12 +96,9 @@ export function sseClientSyncVersion(req, response) {
|
|
|
125
96
|
|
|
126
97
|
export function startWatchers() {
|
|
127
98
|
watchMocksDir()
|
|
128
|
-
watchStaticDir()
|
|
129
99
|
}
|
|
130
100
|
|
|
131
101
|
export function stopWatchers() {
|
|
132
102
|
mocksWatcher?.close()
|
|
133
|
-
staticWatcher?.close()
|
|
134
103
|
mocksWatcher = null
|
|
135
|
-
staticWatcher = null
|
|
136
104
|
}
|
package/src/server/cli.js
CHANGED
|
@@ -11,25 +11,25 @@ import pkgJSON from '../../package.json' with { type: 'json' }
|
|
|
11
11
|
|
|
12
12
|
process.on('unhandledRejection', error => { throw error })
|
|
13
13
|
|
|
14
|
-
let args
|
|
14
|
+
let args, positionals
|
|
15
15
|
try {
|
|
16
|
-
|
|
16
|
+
const result = parseArgs({
|
|
17
17
|
options: {
|
|
18
18
|
config: { short: 'c', type: 'string' },
|
|
19
19
|
|
|
20
20
|
port: { short: 'p', type: 'string' },
|
|
21
21
|
host: { short: 'H', type: 'string' },
|
|
22
22
|
|
|
23
|
-
'mocks-dir': { short: 'm', type: 'string' },
|
|
24
|
-
'static-dir': { short: 's', type: 'string' },
|
|
25
|
-
|
|
26
23
|
quiet: { short: 'q', type: 'boolean' },
|
|
27
24
|
'no-open': { short: 'n', type: 'boolean' },
|
|
28
25
|
|
|
29
26
|
help: { short: 'h', type: 'boolean' },
|
|
30
27
|
version: { short: 'v', type: 'boolean' }
|
|
31
|
-
}
|
|
32
|
-
|
|
28
|
+
},
|
|
29
|
+
allowPositionals: true
|
|
30
|
+
})
|
|
31
|
+
args = result.values
|
|
32
|
+
positionals = result.positionals
|
|
33
33
|
}
|
|
34
34
|
catch (error) {
|
|
35
35
|
console.error(error.message)
|
|
@@ -45,14 +45,11 @@ if (args.version)
|
|
|
45
45
|
|
|
46
46
|
else if (args.help)
|
|
47
47
|
console.log(`
|
|
48
|
-
Usage: mockaton [options]
|
|
48
|
+
Usage: mockaton [mocks-dir] [options]
|
|
49
49
|
|
|
50
50
|
Options:
|
|
51
51
|
-c, --config <file> (default: ./mockaton.config.js)
|
|
52
52
|
|
|
53
|
-
-m, --mocks-dir <dir> (default: ./mockaton-mocks/)
|
|
54
|
-
-s, --static-dir <dir> (default: ./mockaton-static-mocks/)
|
|
55
|
-
|
|
56
53
|
-H, --host <host> (default: 127.0.0.1)
|
|
57
54
|
-p, --port <port> (default: 0) which means auto-assigned
|
|
58
55
|
|
|
@@ -80,8 +77,7 @@ else {
|
|
|
80
77
|
if (args.host) opts.host = args.host
|
|
81
78
|
if (args.port) opts.port = Number.isNaN(Number(args.port)) ? args.port : Number(args.port)
|
|
82
79
|
|
|
83
|
-
if (
|
|
84
|
-
if (args['static-dir']) opts.staticDir = args['static-dir']
|
|
80
|
+
if (positionals[0]) opts.mocksDir = positionals[0]
|
|
85
81
|
|
|
86
82
|
if (args.quiet) opts.logLevel = 'quiet'
|
|
87
83
|
if (args['no-open']) opts.onReady = () => {}
|