mockaton 8.3.4 → 8.4.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 +27 -27
- package/TODO.md +1 -3
- package/package.json +1 -1
- package/src/Dashboard.js +3 -2
- package/src/MockBroker.js +47 -19
- package/src/MockDispatcher.js +1 -1
- package/src/Mockaton.js +10 -0
- package/src/Mockaton.test.js +105 -7
- package/src/mockBrokersCollection.js +40 -18
package/README.md
CHANGED
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|

|
|
4
4
|

|
|
5
5
|
|
|
6
|
+
## Mock your APIs, Enhance your Development Workflow
|
|
6
7
|
|
|
7
|
-
_Mockaton_ is
|
|
8
|
+
_Mockaton_ is an HTTP mock server built for improving the frontend
|
|
9
|
+
development and testing experience.
|
|
8
10
|
|
|
9
11
|
With Mockaton you don’t need to write code for wiring your mocks. Instead, it
|
|
10
12
|
scans a given directory for filenames following a convention similar to the
|
|
@@ -13,13 +15,17 @@ URL paths. For example, the following file will be served on `/api/user/1234`
|
|
|
13
15
|
my-mocks-dir/api/user/[user-id].GET.200.json
|
|
14
16
|
```
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
from your backend the routes you don’t have mocks for. That’s done with:
|
|
18
|
+
By the way, you don’t need to mock all your APIs. You can request from
|
|
19
|
+
your backend the routes you don’t have mocks for. That’s done with:
|
|
19
20
|
|
|
20
21
|
`config.proxyFallback = 'http://mybackend'`
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
## Scraping Mocks
|
|
24
|
+
You can save mocks following the filename convention
|
|
25
|
+
for the routes that reached your `proxyFallback` with:
|
|
26
|
+
|
|
27
|
+
`config.collectProxied = true`
|
|
28
|
+
|
|
23
29
|
|
|
24
30
|
## Multiple Mock Variants
|
|
25
31
|
Each route can have many mocks, which could either be:
|
|
@@ -28,9 +34,9 @@ Each route can have many mocks, which could either be:
|
|
|
28
34
|
- e.g. `api/login(locked out user).POST.423.json`
|
|
29
35
|
|
|
30
36
|
|
|
31
|
-
## Dashboard
|
|
37
|
+
## Dashboard
|
|
32
38
|
|
|
33
|
-
In the dashboard
|
|
39
|
+
In the dashboard you can select a mock variant for a particular
|
|
34
40
|
route, among other options. In addition, there’s a programmatic API,
|
|
35
41
|
which is handy for setting up tests (see **Commander API** below).
|
|
36
42
|
|
|
@@ -64,7 +70,7 @@ node --import=tsx my-mockaton.js
|
|
|
64
70
|
```
|
|
65
71
|
|
|
66
72
|
|
|
67
|
-
## Running the Demo
|
|
73
|
+
## Running the Built-in Demo
|
|
68
74
|
This demo uses the [sample-mocks/](./sample-mocks) of this repository.
|
|
69
75
|
|
|
70
76
|
```sh
|
|
@@ -80,8 +86,7 @@ Experiment with the Dashboard:
|
|
|
80
86
|
- Toggle the 🕓 _Delay Responses_ button, (e.g. for testing spinners)
|
|
81
87
|
- Toggle the _500_ button, which sends and _Internal Server Error_ on that endpoint
|
|
82
88
|
|
|
83
|
-
Finally, edit a mock file in your IDE. You don’t need to restart Mockaton
|
|
84
|
-
The _Reset_ button is for registering newly added, removed, or renamed mocks.
|
|
89
|
+
Finally, edit a mock file in your IDE. You don’t need to restart Mockaton.
|
|
85
90
|
|
|
86
91
|
|
|
87
92
|
## Use Cases
|
|
@@ -183,9 +188,7 @@ export default function listColors() {
|
|
|
183
188
|
```
|
|
184
189
|
</details>
|
|
185
190
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
If you are wondering, what if I need to serve a static `.js`?
|
|
191
|
+
**What if I need to serve a static .js?**
|
|
189
192
|
Put it in your `config.staticDir` without the mock filename convention.
|
|
190
193
|
|
|
191
194
|
---
|
|
@@ -194,8 +197,8 @@ Put it in your `config.staticDir` without the mock filename convention.
|
|
|
194
197
|
|
|
195
198
|
### Extension
|
|
196
199
|
|
|
197
|
-
The last
|
|
198
|
-
Response Status Code, and
|
|
200
|
+
The last three dots are reserved for the HTTP Method,
|
|
201
|
+
Response Status Code, and File Extension.
|
|
199
202
|
|
|
200
203
|
```
|
|
201
204
|
api/user.GET.200.json
|
|
@@ -235,7 +238,7 @@ api/video<b>?limit=[limit]</b>.GET.200.json
|
|
|
235
238
|
</pre>
|
|
236
239
|
|
|
237
240
|
Speaking of which, on Windows filenames containing "?" are [not
|
|
238
|
-
permitted](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file), but since that’s part of the query string
|
|
241
|
+
permitted](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file), but since that’s part of the query string it’s ignored anyway.
|
|
239
242
|
|
|
240
243
|
|
|
241
244
|
### Index-like routes
|
|
@@ -272,8 +275,10 @@ Defaults to `/(\.DS_Store|~)$/`
|
|
|
272
275
|
|
|
273
276
|
|
|
274
277
|
### `delay?: number`
|
|
275
|
-
|
|
276
|
-
|
|
278
|
+
Defaults to `config.delay=1200` milliseconds.
|
|
279
|
+
|
|
280
|
+
Although routes can individually be delayed with the 🕓
|
|
281
|
+
checkbox, delay the amount is globally configurable.
|
|
277
282
|
|
|
278
283
|
|
|
279
284
|
### `proxyFallback?: string`
|
|
@@ -290,11 +295,7 @@ URL the filename will have `[id]` in their place. For example,
|
|
|
290
295
|
my-mocks-dir/api/user/[id]/likes.GET.200.json
|
|
291
296
|
```
|
|
292
297
|
|
|
293
|
-
|
|
294
|
-
**register them** by reinitializing Mockaton or clicking "Reset".
|
|
295
|
-
|
|
296
|
-
Registered mocks won’t be overwritten (they don’t hit the fallback server).
|
|
297
|
-
On the other hand, newly saved mocks get overwritten while they are unregistered.
|
|
298
|
+
Your existing mocks won’t be overwritten (they don’t hit the fallback server).
|
|
298
299
|
|
|
299
300
|
<details>
|
|
300
301
|
<summary>Extension Details</summary>
|
|
@@ -343,7 +344,7 @@ response in a `Set-Cookie` header. If you need to send more
|
|
|
343
344
|
cookies, inject them globally in `config.extraHeaders`.
|
|
344
345
|
|
|
345
346
|
By the way, the `jwtCookie` helper has a hardcoded header and signature.
|
|
346
|
-
In other words, it’s useful only if you care about
|
|
347
|
+
In other words, it’s useful only if you care about its payload.
|
|
347
348
|
|
|
348
349
|
|
|
349
350
|
### `extraHeaders?: string[]`
|
|
@@ -490,9 +491,8 @@ await mockaton.setProxyFallback('http://example.com')
|
|
|
490
491
|
Pass an empty string to disable it.
|
|
491
492
|
|
|
492
493
|
### Reset
|
|
493
|
-
Re-initialize the collection.
|
|
494
|
-
|
|
495
|
-
but `config.proxyFallback` and `config.corsAllowed` are not affected.
|
|
494
|
+
Re-initialize the collection. The selected mocks, cookies, and delays go back to
|
|
495
|
+
default, but `config.proxyFallback` and `config.corsAllowed` are not affected.
|
|
496
496
|
```js
|
|
497
497
|
await mockaton.reset()
|
|
498
498
|
```
|
package/TODO.md
CHANGED
package/package.json
CHANGED
package/src/Dashboard.js
CHANGED
|
@@ -43,7 +43,7 @@ const refPayloadViewerFileTitle = useRef()
|
|
|
43
43
|
|
|
44
44
|
const mockaton = new Commander(window.location.origin)
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
window.onfocus = init
|
|
47
47
|
function init() {
|
|
48
48
|
Promise.all([
|
|
49
49
|
mockaton.listMocks(),
|
|
@@ -204,7 +204,8 @@ function SectionByMethod({ method, brokers }) {
|
|
|
204
204
|
r('tbody', null,
|
|
205
205
|
r('th', null, method),
|
|
206
206
|
Object.entries(brokers)
|
|
207
|
-
.filter(([, broker]) => broker.mocks.length > 1) //
|
|
207
|
+
.filter(([, broker]) => broker.mocks.length > 1) // >1 because of autogen500
|
|
208
|
+
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
208
209
|
.map(([urlMask, broker]) =>
|
|
209
210
|
r('tr', null,
|
|
210
211
|
r('td', null, r(PreviewLink, { method, urlMask })),
|
package/src/MockBroker.js
CHANGED
|
@@ -18,7 +18,37 @@ export class MockBroker {
|
|
|
18
18
|
this.register(file)
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
register(file) {
|
|
21
|
+
register(file) {
|
|
22
|
+
if (this.mockExists(file))
|
|
23
|
+
return
|
|
24
|
+
const { status } = parseFilename(file)
|
|
25
|
+
if (status === 500) {
|
|
26
|
+
this.#deleteTemp500()
|
|
27
|
+
if (this.temp500IsSelected)
|
|
28
|
+
this.updateFile(file)
|
|
29
|
+
}
|
|
30
|
+
this.mocks.push(file)
|
|
31
|
+
this.sortMocks()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#deleteTemp500() {
|
|
35
|
+
this.mocks = this.mocks.filter(file => !this.#isTemp500(file))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#registerTemp500() {
|
|
39
|
+
const { urlMask, method } = parseFilename(this.mocks[0])
|
|
40
|
+
const file = urlMask.replace(/^\//, '') // Removes leading slash
|
|
41
|
+
this.mocks.push(`${file}${DEFAULT_500_COMMENT}.${method}.500.empty`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
unregister(file) {
|
|
45
|
+
this.mocks = this.mocks.filter(f => f !== file)
|
|
46
|
+
const isEmpty = !this.mocks.length
|
|
47
|
+
|| this.mocks.length === 1 && this.#isTemp500(this.mocks[0])
|
|
48
|
+
if (!isEmpty && this.file === file)
|
|
49
|
+
this.selectDefaultFile()
|
|
50
|
+
return isEmpty
|
|
51
|
+
}
|
|
22
52
|
|
|
23
53
|
// Appending a '/' so URLs ending with variables don't match
|
|
24
54
|
// URLs that have a path after that variable. For example,
|
|
@@ -34,21 +64,24 @@ export class MockBroker {
|
|
|
34
64
|
get file() { return this.currentMock.file }
|
|
35
65
|
get delay() { return this.currentMock.delay }
|
|
36
66
|
get status() { return parseFilename(this.file).status }
|
|
37
|
-
get
|
|
67
|
+
get temp500IsSelected() { return this.#isTemp500(this.file) }
|
|
38
68
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
this.
|
|
69
|
+
#isTemp500(file) { return includesComment(file, DEFAULT_500_COMMENT) }
|
|
70
|
+
|
|
71
|
+
sortMocks() {
|
|
72
|
+
this.mocks.sort()
|
|
73
|
+
const defaults = this.mocks.filter(file => includesComment(file, DEFAULT_MOCK_COMMENT))
|
|
74
|
+
const temp500 = this.mocks.filter(file => includesComment(file, DEFAULT_500_COMMENT))
|
|
75
|
+
this.mocks = this.mocks.filter(file => !defaults.includes(file) && !temp500.includes(file))
|
|
76
|
+
this.mocks = [
|
|
77
|
+
...defaults,
|
|
78
|
+
...this.mocks,
|
|
79
|
+
...temp500
|
|
80
|
+
]
|
|
47
81
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return f
|
|
82
|
+
|
|
83
|
+
selectDefaultFile() {
|
|
84
|
+
this.updateFile(this.mocks[0])
|
|
52
85
|
}
|
|
53
86
|
|
|
54
87
|
mockExists(file) { return this.mocks.includes(file) }
|
|
@@ -77,11 +110,6 @@ export class MockBroker {
|
|
|
77
110
|
#has500() {
|
|
78
111
|
return this.mocks.some(mock => parseFilename(mock).status === 500)
|
|
79
112
|
}
|
|
80
|
-
#registerTemp500() {
|
|
81
|
-
const { urlMask, method } = parseFilename(this.mocks[0])
|
|
82
|
-
const file = urlMask.replace(/^\//, '') // Removes leading slash
|
|
83
|
-
this.register(`${file}${DEFAULT_500_COMMENT}.${method}.500.empty`)
|
|
84
|
-
}
|
|
85
113
|
}
|
|
86
114
|
|
|
87
115
|
// Stars out (for regex) all the paths that are in square brackets
|
package/src/MockDispatcher.js
CHANGED
|
@@ -29,7 +29,7 @@ export async function dispatchMock(req, response) {
|
|
|
29
29
|
for (let i = 0; i < config.extraHeaders.length; i += 2)
|
|
30
30
|
response.setHeader(config.extraHeaders[i], config.extraHeaders[i + 1])
|
|
31
31
|
|
|
32
|
-
const { mime, body } = broker.
|
|
32
|
+
const { mime, body } = broker.temp500IsSelected
|
|
33
33
|
? { mime: '', body: '' }
|
|
34
34
|
: await applyPlugins(join(config.mocksDir, broker.file), req, response)
|
|
35
35
|
|
package/src/Mockaton.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { join } from 'node:path'
|
|
1
2
|
import { createServer } from 'node:http'
|
|
3
|
+
import { watch, existsSync } from 'node:fs'
|
|
2
4
|
|
|
3
5
|
import { API } from './ApiConstants.js'
|
|
4
6
|
import { dispatchMock } from './MockDispatcher.js'
|
|
@@ -13,6 +15,14 @@ import { apiPatchRequests, apiGetRequests } from './Api.js'
|
|
|
13
15
|
export function Mockaton(options) {
|
|
14
16
|
setup(options)
|
|
15
17
|
mockBrokerCollection.init()
|
|
18
|
+
|
|
19
|
+
watch(config.mocksDir, { recursive: true, persistent: false }, (_, filename) => {
|
|
20
|
+
if (existsSync(join(config.mocksDir, filename)))
|
|
21
|
+
mockBrokerCollection.registerMock(filename, 'ensureItHas500')
|
|
22
|
+
else
|
|
23
|
+
mockBrokerCollection.unregisterMock(filename)
|
|
24
|
+
})
|
|
25
|
+
|
|
16
26
|
return createServer(onRequest).listen(config.port, config.host, function (error) {
|
|
17
27
|
const { address, port } = this.address()
|
|
18
28
|
const url = `http://${address}:${port}`
|
package/src/Mockaton.test.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { tmpdir } from 'node:os'
|
|
2
|
-
import { dirname, join } from 'node:path'
|
|
3
2
|
import { promisify } from 'node:util'
|
|
4
3
|
import { describe, it } from 'node:test'
|
|
5
4
|
import { createServer } from 'node:http'
|
|
5
|
+
import { dirname, join } from 'node:path'
|
|
6
6
|
import { randomUUID } from 'node:crypto'
|
|
7
7
|
import { equal, deepEqual, match } from 'node:assert/strict'
|
|
8
|
-
import { writeFileSync, mkdtempSync, mkdirSync } from 'node:fs'
|
|
9
|
-
|
|
8
|
+
import { writeFileSync, mkdtempSync, mkdirSync, unlinkSync } from 'node:fs'
|
|
10
9
|
|
|
11
10
|
import { config } from './config.js'
|
|
12
11
|
import { mimeFor } from './utils/mime.js'
|
|
@@ -21,7 +20,6 @@ import { API, DEFAULT_500_COMMENT, DEFAULT_MOCK_COMMENT } from './ApiConstants.j
|
|
|
21
20
|
|
|
22
21
|
const tmpDir = mkdtempSync(tmpdir()) + '/'
|
|
23
22
|
const staticTmpDir = mkdtempSync(tmpdir()) + '/'
|
|
24
|
-
console.log(tmpDir)
|
|
25
23
|
|
|
26
24
|
const fixtureCustomMime = [
|
|
27
25
|
'/api/custom-mime',
|
|
@@ -44,6 +42,29 @@ const fixtureDelayed = [
|
|
|
44
42
|
'Route_To_Be_Delayed'
|
|
45
43
|
]
|
|
46
44
|
|
|
45
|
+
/* Only fixtures with PUT */
|
|
46
|
+
const fixtureForRegisteringPutA = [
|
|
47
|
+
'/api/register',
|
|
48
|
+
'api/register(a).PUT.200.json',
|
|
49
|
+
'fixture_for_registering_a'
|
|
50
|
+
]
|
|
51
|
+
const fixtureForRegisteringPutB = [
|
|
52
|
+
'/api/register',
|
|
53
|
+
'api/register(b).PUT.200.json',
|
|
54
|
+
'fixture_for_registering_b'
|
|
55
|
+
]
|
|
56
|
+
const fixtureForRegisteringPutA500 = [
|
|
57
|
+
'/api/register',
|
|
58
|
+
'api/register.PUT.500.json',
|
|
59
|
+
'fixture_for_registering_500'
|
|
60
|
+
]
|
|
61
|
+
const fixtureForUnregisteringPutC = [
|
|
62
|
+
'/api/unregister',
|
|
63
|
+
'api/unregister.PUT.200.json',
|
|
64
|
+
'fixture_for_unregistering'
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
|
|
47
68
|
const fixtures = [
|
|
48
69
|
[
|
|
49
70
|
'/api',
|
|
@@ -242,6 +263,8 @@ async function runTests() {
|
|
|
242
263
|
await testCorsAllowed()
|
|
243
264
|
testWindowsPaths()
|
|
244
265
|
|
|
266
|
+
await testRegistering()
|
|
267
|
+
|
|
245
268
|
server.close()
|
|
246
269
|
}
|
|
247
270
|
|
|
@@ -291,14 +314,82 @@ async function testMockDispatching(url, file, expectedBody, forcedMime = undefin
|
|
|
291
314
|
async function testDefaultMock() {
|
|
292
315
|
await testMockDispatching(...fixtureDefaultInName)
|
|
293
316
|
await it('sorts mocks list with the user specified default first for dashboard display', async () => {
|
|
294
|
-
const
|
|
295
|
-
const
|
|
296
|
-
const { mocks } = body.GET[fixtureDefaultInName[0]]
|
|
317
|
+
const body = await (await commander.listMocks()).json()
|
|
318
|
+
const { mocks } = body['GET'][fixtureDefaultInName[0]]
|
|
297
319
|
equal(mocks[0], fixtureDefaultInName[1])
|
|
298
320
|
equal(mocks[1], fixtureNonDefaultInName[1])
|
|
299
321
|
})
|
|
300
322
|
}
|
|
301
323
|
|
|
324
|
+
async function testRegistering() {
|
|
325
|
+
await describe('Registering', async () => {
|
|
326
|
+
const temp500 = `api/register${DEFAULT_500_COMMENT}.PUT.500.empty`
|
|
327
|
+
|
|
328
|
+
await it('registering new route creates temp 500 as well and re-registering is a noop', async () => {
|
|
329
|
+
write(fixtureForRegisteringPutA[1], '')
|
|
330
|
+
await sleep()
|
|
331
|
+
write(fixtureForRegisteringPutB[1], '')
|
|
332
|
+
await sleep()
|
|
333
|
+
write(fixtureForRegisteringPutA[1], '')
|
|
334
|
+
await sleep()
|
|
335
|
+
const collection = await (await commander.listMocks()).json()
|
|
336
|
+
deepEqual(collection['PUT'][fixtureForRegisteringPutA[0]].mocks, [
|
|
337
|
+
fixtureForRegisteringPutA[1],
|
|
338
|
+
fixtureForRegisteringPutB[1],
|
|
339
|
+
temp500
|
|
340
|
+
])
|
|
341
|
+
})
|
|
342
|
+
await it('registering a 500 removes the temp 500 (and selects the new 500)', async () => {
|
|
343
|
+
await commander.select(temp500)
|
|
344
|
+
write(fixtureForRegisteringPutA500[1], '')
|
|
345
|
+
await sleep()
|
|
346
|
+
const collection = await (await commander.listMocks()).json()
|
|
347
|
+
const { mocks, currentMock } = collection['PUT'][fixtureForRegisteringPutA[0]]
|
|
348
|
+
deepEqual(mocks, [
|
|
349
|
+
fixtureForRegisteringPutA[1],
|
|
350
|
+
fixtureForRegisteringPutB[1],
|
|
351
|
+
fixtureForRegisteringPutA500[1]
|
|
352
|
+
])
|
|
353
|
+
deepEqual(currentMock, {
|
|
354
|
+
file: fixtureForRegisteringPutA500[1],
|
|
355
|
+
delay: 0
|
|
356
|
+
})
|
|
357
|
+
})
|
|
358
|
+
await it('unregisters selected', async () => {
|
|
359
|
+
await commander.select(fixtureForRegisteringPutA[1])
|
|
360
|
+
remove(fixtureForRegisteringPutA[1])
|
|
361
|
+
await sleep()
|
|
362
|
+
const collection = await (await commander.listMocks()).json()
|
|
363
|
+
const { mocks, currentMock } = collection['PUT'][fixtureForRegisteringPutA[0]]
|
|
364
|
+
deepEqual(mocks, [
|
|
365
|
+
fixtureForRegisteringPutB[1],
|
|
366
|
+
fixtureForRegisteringPutA500[1]
|
|
367
|
+
])
|
|
368
|
+
deepEqual(currentMock, {
|
|
369
|
+
file: fixtureForRegisteringPutB[1],
|
|
370
|
+
delay: 0
|
|
371
|
+
})
|
|
372
|
+
})
|
|
373
|
+
await it('unregistering the last mock removes broker', async () => {
|
|
374
|
+
write(fixtureForUnregisteringPutC[1], '') // Register another PUT so it doesn't delete PUT from collection
|
|
375
|
+
await sleep()
|
|
376
|
+
remove(fixtureForUnregisteringPutC[1])
|
|
377
|
+
await sleep()
|
|
378
|
+
const collection = await (await commander.listMocks()).json()
|
|
379
|
+
equal(collection['PUT'][fixtureForUnregisteringPutC[0]], undefined)
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
await it('unregistering the last PUT mock removes PUT from collection', async () => {
|
|
383
|
+
remove(fixtureForRegisteringPutB[1])
|
|
384
|
+
remove(fixtureForRegisteringPutA500[1])
|
|
385
|
+
await sleep()
|
|
386
|
+
const collection = await (await commander.listMocks()).json()
|
|
387
|
+
equal(collection['PUT'], undefined)
|
|
388
|
+
})
|
|
389
|
+
})
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
|
|
302
393
|
async function testItUpdatesTheCurrentSelectedMock(url, file, expectedStatus, expectedBody) {
|
|
303
394
|
await commander.select(file)
|
|
304
395
|
const res = await request(url)
|
|
@@ -537,6 +628,10 @@ function write(filename, data) {
|
|
|
537
628
|
_write(tmpDir + filename, data)
|
|
538
629
|
}
|
|
539
630
|
|
|
631
|
+
function remove(filename) {
|
|
632
|
+
unlinkSync(tmpDir + filename)
|
|
633
|
+
}
|
|
634
|
+
|
|
540
635
|
function writeStatic(filename, data) {
|
|
541
636
|
_write(staticTmpDir + filename, data)
|
|
542
637
|
}
|
|
@@ -546,3 +641,6 @@ function _write(absPath, data) {
|
|
|
546
641
|
writeFileSync(absPath, data, 'utf8')
|
|
547
642
|
}
|
|
548
643
|
|
|
644
|
+
async function sleep(ms = 50) {
|
|
645
|
+
return new Promise(resolve => setTimeout(resolve, ms))
|
|
646
|
+
}
|
|
@@ -21,39 +21,61 @@ export function init() {
|
|
|
21
21
|
collection = {}
|
|
22
22
|
cookie.init(config.cookies)
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
listFilesRecursively(config.mocksDir)
|
|
25
25
|
.sort()
|
|
26
|
-
.
|
|
27
|
-
|
|
28
|
-
for (const file of files) {
|
|
29
|
-
const { method, urlMask } = parseFilename(file)
|
|
30
|
-
collection[method] ??= {}
|
|
31
|
-
if (!collection[method][urlMask])
|
|
32
|
-
collection[method][urlMask] = new MockBroker(file)
|
|
33
|
-
else
|
|
34
|
-
collection[method][urlMask].register(file)
|
|
35
|
-
}
|
|
26
|
+
.forEach(f => registerMock(f))
|
|
36
27
|
|
|
37
28
|
forEachBroker(broker => {
|
|
38
|
-
broker.selectDefaultFile()
|
|
39
29
|
broker.ensureItHas500()
|
|
30
|
+
broker.selectDefaultFile()
|
|
40
31
|
})
|
|
41
32
|
}
|
|
42
33
|
|
|
34
|
+
export function registerMock(file, shouldEnsure500) {
|
|
35
|
+
if (config.ignore.test(file) || !filenameIsValid(file))
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
const { method, urlMask } = parseFilename(file)
|
|
39
|
+
collection[method] ??= {}
|
|
40
|
+
if (!collection[method][urlMask])
|
|
41
|
+
collection[method][urlMask] = new MockBroker(file)
|
|
42
|
+
else
|
|
43
|
+
collection[method][urlMask].register(file)
|
|
44
|
+
|
|
45
|
+
if (shouldEnsure500)
|
|
46
|
+
collection[method][urlMask].ensureItHas500()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function unregisterMock(file) {
|
|
50
|
+
const broker = getBrokerByFilename(file)
|
|
51
|
+
if (!broker)
|
|
52
|
+
return
|
|
53
|
+
const isEmpty = broker.unregister(file)
|
|
54
|
+
if (isEmpty) {
|
|
55
|
+
const { method, urlMask } = parseFilename(file)
|
|
56
|
+
delete collection[method][urlMask]
|
|
57
|
+
if (!Object.keys(collection[method]).length)
|
|
58
|
+
delete collection[method]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
43
61
|
|
|
44
62
|
export const getAll = () => collection
|
|
45
63
|
|
|
46
|
-
|
|
64
|
+
|
|
65
|
+
/** @returns {MockBroker | undefined} */
|
|
66
|
+
export function getBrokerByFilename(file) {
|
|
47
67
|
const { method, urlMask } = parseFilename(file)
|
|
48
68
|
if (collection[method])
|
|
49
69
|
return collection[method][urlMask]
|
|
50
70
|
}
|
|
51
71
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Searching the routes in reverse order so dynamic params (e.g.
|
|
74
|
+
* /user/[id]) don’t take precedence over exact paths (e.g.
|
|
75
|
+
* /user/name). That’s because "[]" chars are lower than alphanumeric ones.
|
|
76
|
+
* BTW, `urlMasks` always start with "/", so there’s no need to
|
|
77
|
+
* worry about the primacy of array-like keys when iterating.
|
|
78
|
+
@returns {MockBroker | undefined} */
|
|
57
79
|
export function getBrokerForUrl(method, url) {
|
|
58
80
|
if (!collection[method])
|
|
59
81
|
return
|