mockaton 8.3.3 → 8.4.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 +31 -28
- 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 +12 -0
- package/src/Mockaton.test.js +105 -7
- package/src/config.js +1 -1
- package/src/mockBrokersCollection.js +36 -17
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,10 +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
|
-
By the way, you don’t need to mock all your APIs.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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:
|
|
20
|
+
|
|
21
|
+
`config.proxyFallback = 'http://mybackend'`
|
|
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
|
+
|
|
20
29
|
|
|
21
30
|
## Multiple Mock Variants
|
|
22
31
|
Each route can have many mocks, which could either be:
|
|
@@ -25,9 +34,9 @@ Each route can have many mocks, which could either be:
|
|
|
25
34
|
- e.g. `api/login(locked out user).POST.423.json`
|
|
26
35
|
|
|
27
36
|
|
|
28
|
-
## Dashboard
|
|
37
|
+
## Dashboard
|
|
29
38
|
|
|
30
|
-
In the dashboard
|
|
39
|
+
In the dashboard you can select a mock variant for a particular
|
|
31
40
|
route, among other options. In addition, there’s a programmatic API,
|
|
32
41
|
which is handy for setting up tests (see **Commander API** below).
|
|
33
42
|
|
|
@@ -61,7 +70,7 @@ node --import=tsx my-mockaton.js
|
|
|
61
70
|
```
|
|
62
71
|
|
|
63
72
|
|
|
64
|
-
## Running the Demo
|
|
73
|
+
## Running the Built-in Demo
|
|
65
74
|
This demo uses the [sample-mocks/](./sample-mocks) of this repository.
|
|
66
75
|
|
|
67
76
|
```sh
|
|
@@ -77,8 +86,7 @@ Experiment with the Dashboard:
|
|
|
77
86
|
- Toggle the 🕓 _Delay Responses_ button, (e.g. for testing spinners)
|
|
78
87
|
- Toggle the _500_ button, which sends and _Internal Server Error_ on that endpoint
|
|
79
88
|
|
|
80
|
-
Finally, edit a mock file in your IDE. You don’t need to restart Mockaton
|
|
81
|
-
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.
|
|
82
90
|
|
|
83
91
|
|
|
84
92
|
## Use Cases
|
|
@@ -180,9 +188,7 @@ export default function listColors() {
|
|
|
180
188
|
```
|
|
181
189
|
</details>
|
|
182
190
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
If you are wondering, what if I need to serve a static `.js`?
|
|
191
|
+
**What if I need to serve a static .js?**
|
|
186
192
|
Put it in your `config.staticDir` without the mock filename convention.
|
|
187
193
|
|
|
188
194
|
---
|
|
@@ -191,8 +197,8 @@ Put it in your `config.staticDir` without the mock filename convention.
|
|
|
191
197
|
|
|
192
198
|
### Extension
|
|
193
199
|
|
|
194
|
-
The last
|
|
195
|
-
Response Status Code, and
|
|
200
|
+
The last three dots are reserved for the HTTP Method,
|
|
201
|
+
Response Status Code, and File Extension.
|
|
196
202
|
|
|
197
203
|
```
|
|
198
204
|
api/user.GET.200.json
|
|
@@ -232,7 +238,7 @@ api/video<b>?limit=[limit]</b>.GET.200.json
|
|
|
232
238
|
</pre>
|
|
233
239
|
|
|
234
240
|
Speaking of which, on Windows filenames containing "?" are [not
|
|
235
|
-
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.
|
|
236
242
|
|
|
237
243
|
|
|
238
244
|
### Index-like routes
|
|
@@ -269,8 +275,10 @@ Defaults to `/(\.DS_Store|~)$/`
|
|
|
269
275
|
|
|
270
276
|
|
|
271
277
|
### `delay?: number`
|
|
272
|
-
|
|
273
|
-
|
|
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.
|
|
274
282
|
|
|
275
283
|
|
|
276
284
|
### `proxyFallback?: string`
|
|
@@ -287,11 +295,7 @@ URL the filename will have `[id]` in their place. For example,
|
|
|
287
295
|
my-mocks-dir/api/user/[id]/likes.GET.200.json
|
|
288
296
|
```
|
|
289
297
|
|
|
290
|
-
|
|
291
|
-
**register them** by reinitializing Mockaton or clicking "Reset".
|
|
292
|
-
|
|
293
|
-
Registered mocks won’t be overwritten (they don’t hit the fallback server).
|
|
294
|
-
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).
|
|
295
299
|
|
|
296
300
|
<details>
|
|
297
301
|
<summary>Extension Details</summary>
|
|
@@ -340,7 +344,7 @@ response in a `Set-Cookie` header. If you need to send more
|
|
|
340
344
|
cookies, inject them globally in `config.extraHeaders`.
|
|
341
345
|
|
|
342
346
|
By the way, the `jwtCookie` helper has a hardcoded header and signature.
|
|
343
|
-
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.
|
|
344
348
|
|
|
345
349
|
|
|
346
350
|
### `extraHeaders?: string[]`
|
|
@@ -420,7 +424,7 @@ function capitalizePlugin(filePath) {
|
|
|
420
424
|
|
|
421
425
|
|
|
422
426
|
### `corsAllowed?: boolean`
|
|
423
|
-
Defaults to `
|
|
427
|
+
Defaults to `true`. When `true`, these are the default options:
|
|
424
428
|
```js
|
|
425
429
|
config.corsOrigins = ['*']
|
|
426
430
|
config.corsMethods = ['GET', 'PUT', 'DELETE', 'POST', 'PATCH', 'HEAD', 'OPTIONS', 'TRACE', 'CONNECT']
|
|
@@ -487,9 +491,8 @@ await mockaton.setProxyFallback('http://example.com')
|
|
|
487
491
|
Pass an empty string to disable it.
|
|
488
492
|
|
|
489
493
|
### Reset
|
|
490
|
-
Re-initialize the collection.
|
|
491
|
-
|
|
492
|
-
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.
|
|
493
496
|
```js
|
|
494
497
|
await mockaton.reset()
|
|
495
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,16 @@ 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
|
+
const broker = mockBrokerCollection.registerMock(filename)
|
|
22
|
+
broker.ensureItHas500()
|
|
23
|
+
}
|
|
24
|
+
else
|
|
25
|
+
mockBrokerCollection.unregisterMock(filename)
|
|
26
|
+
})
|
|
27
|
+
|
|
16
28
|
return createServer(onRequest).listen(config.port, config.host, function (error) {
|
|
17
29
|
const { address, port } = this.address()
|
|
18
30
|
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
|
+
}
|
package/src/config.js
CHANGED
|
@@ -21,39 +21,58 @@ export function init() {
|
|
|
21
21
|
collection = {}
|
|
22
22
|
cookie.init(config.cookies)
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
listFilesRecursively(config.mocksDir)
|
|
25
25
|
.sort()
|
|
26
26
|
.filter(f => !config.ignore.test(f) && filenameIsValid(f))
|
|
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
|
-
}
|
|
27
|
+
.forEach(registerMock)
|
|
36
28
|
|
|
37
29
|
forEachBroker(broker => {
|
|
38
|
-
broker.selectDefaultFile()
|
|
39
30
|
broker.ensureItHas500()
|
|
31
|
+
broker.selectDefaultFile()
|
|
40
32
|
})
|
|
41
33
|
}
|
|
42
34
|
|
|
35
|
+
/** @returns {MockBroker} */
|
|
36
|
+
export function registerMock(file) {
|
|
37
|
+
const { method, urlMask } = parseFilename(file)
|
|
38
|
+
collection[method] ??= {}
|
|
39
|
+
if (!collection[method][urlMask])
|
|
40
|
+
collection[method][urlMask] = new MockBroker(file)
|
|
41
|
+
else
|
|
42
|
+
collection[method][urlMask].register(file)
|
|
43
|
+
return collection[method][urlMask]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function unregisterMock(file) {
|
|
47
|
+
const broker = getBrokerByFilename(file)
|
|
48
|
+
if (!broker)
|
|
49
|
+
return
|
|
50
|
+
const isEmpty = broker.unregister(file)
|
|
51
|
+
if (isEmpty) {
|
|
52
|
+
const { method, urlMask } = parseFilename(file)
|
|
53
|
+
delete collection[method][urlMask]
|
|
54
|
+
if (!Object.keys(collection[method]).length)
|
|
55
|
+
delete collection[method]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
43
58
|
|
|
44
59
|
export const getAll = () => collection
|
|
45
60
|
|
|
46
|
-
|
|
61
|
+
|
|
62
|
+
/** @returns {MockBroker | undefined} */
|
|
63
|
+
export function getBrokerByFilename(file) {
|
|
47
64
|
const { method, urlMask } = parseFilename(file)
|
|
48
65
|
if (collection[method])
|
|
49
66
|
return collection[method][urlMask]
|
|
50
67
|
}
|
|
51
68
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Searching the routes in reverse order so dynamic params (e.g.
|
|
71
|
+
* /user/[id]) don’t take precedence over exact paths (e.g.
|
|
72
|
+
* /user/name). That’s because "[]" chars are lower than alphanumeric ones.
|
|
73
|
+
* BTW, `urlMasks` always start with "/", so there’s no need to
|
|
74
|
+
* worry about the primacy of array-like keys when iterating.
|
|
75
|
+
@returns {MockBroker | undefined} */
|
|
57
76
|
export function getBrokerForUrl(method, url) {
|
|
58
77
|
if (!collection[method])
|
|
59
78
|
return
|