mockaton 13.2.1 → 13.3.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/package.json +1 -1
- package/src/client/ApiCommander.js +1 -1
- package/src/client/app-payload-viewer.js +1 -1
- package/src/client/app-store.js +39 -15
- package/src/client/app.css +84 -3
- package/src/client/app.js +29 -3
- package/src/client/graphics.js +6 -0
- package/src/server/Api.js +1 -2
- package/src/server/MockBroker.js +3 -3
- package/src/server/MockDispatcher.js +2 -5
- package/src/server/Mockaton.js +1 -2
- package/src/server/Mockaton.test.js +35 -42
- package/src/server/mockBrokersCollection.js +17 -7
- package/src/server/utils/HttpServerResponse.js +2 -2
- package/src/server/utils/fs.js +0 -1
- package/www/src/assets/openapi.json +1 -1
package/package.json
CHANGED
|
@@ -42,7 +42,7 @@ export class Commander {
|
|
|
42
42
|
/** @returns {JsonPromise<ClientMockBroker>} */
|
|
43
43
|
toggleStatus = (method, urlMask, status) => this.#patch(API.toggleStatus, [method, urlMask, status])
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
/** @returns {JsonPromise<ClientMockBroker>} */
|
|
47
47
|
setRouteIsProxied = (method, urlMask, proxied) => this.#patch(API.proxied, [method, urlMask, proxied])
|
|
48
48
|
|
|
@@ -132,7 +132,7 @@ async function updatePayloadViewer(proxied, file, response) {
|
|
|
132
132
|
else if (['text/xml', 'application/xml'].includes(mime))
|
|
133
133
|
codeRef.elem.replaceChildren(SyntaxXML(await bodyAsText()))
|
|
134
134
|
|
|
135
|
-
else if (mime.startsWith('text/'))
|
|
135
|
+
else if (mime.startsWith('text/') || mime === 'application/yaml')
|
|
136
136
|
codeRef.elem.textContent = await bodyAsText()
|
|
137
137
|
|
|
138
138
|
else
|
package/src/client/app-store.js
CHANGED
|
@@ -33,6 +33,16 @@ export const store = {
|
|
|
33
33
|
store.render()
|
|
34
34
|
},
|
|
35
35
|
|
|
36
|
+
collapsedFolders: new Set(JSON.parse(globalThis.localStorage?.getItem('collapsedFolders') || '[]')),
|
|
37
|
+
setFolderCollapsed(folder, collapsed) {
|
|
38
|
+
if (collapsed)
|
|
39
|
+
store.collapsedFolders.add(folder)
|
|
40
|
+
else
|
|
41
|
+
store.collapsedFolders.delete(folder)
|
|
42
|
+
|
|
43
|
+
globalThis.localStorage?.setItem('collapsedFolders', JSON.stringify([...store.collapsedFolders]))
|
|
44
|
+
},
|
|
45
|
+
|
|
36
46
|
chosenLink: { method: '', urlMask: '' },
|
|
37
47
|
setChosenLink(method, urlMask) {
|
|
38
48
|
store.chosenLink = { method, urlMask }
|
|
@@ -98,7 +108,6 @@ export const store = {
|
|
|
98
108
|
},
|
|
99
109
|
|
|
100
110
|
setProxyFallback(value) {
|
|
101
|
-
store.skipNextRender = true
|
|
102
111
|
store._request(() => api.setProxyFallback(value), () => {
|
|
103
112
|
store.proxyFallback = value
|
|
104
113
|
})
|
|
@@ -112,27 +121,40 @@ export const store = {
|
|
|
112
121
|
},
|
|
113
122
|
|
|
114
123
|
|
|
124
|
+
_dittoCache: new Map(),
|
|
125
|
+
|
|
115
126
|
brokerFor(method, urlMask) {
|
|
116
127
|
return store.brokersByMethod[method]?.[urlMask]
|
|
117
128
|
},
|
|
118
129
|
|
|
130
|
+
brokerAsRow(method, urlMask) {
|
|
131
|
+
const b = store.brokerFor(method, urlMask)
|
|
132
|
+
const r = new BrokerRowModel(b, store.canProxy)
|
|
133
|
+
r.setUrlMaskDittoed(store._dittoCache.get(r.key))
|
|
134
|
+
return r
|
|
135
|
+
},
|
|
136
|
+
|
|
119
137
|
_setBroker(broker) {
|
|
120
138
|
const { method, urlMask } = parseFilename(broker.file)
|
|
121
139
|
store.brokersByMethod[method] ??= {}
|
|
122
140
|
store.brokersByMethod[method][urlMask] = broker
|
|
123
141
|
},
|
|
124
142
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
143
|
+
folderGroupsByMethod(method) {
|
|
144
|
+
const groups = []
|
|
145
|
+
let g = null
|
|
146
|
+
for (const row of store._brokersAsRowsByMethod(method)) {
|
|
147
|
+
const folder = row.urlMask.substring(0, row.urlMask.lastIndexOf('/') + 1)
|
|
148
|
+
if (!g || g.folder !== folder) {
|
|
149
|
+
g = { folder, children: [] }
|
|
150
|
+
groups.push(g)
|
|
151
|
+
}
|
|
152
|
+
g.children.push(row)
|
|
153
|
+
}
|
|
154
|
+
return groups
|
|
133
155
|
},
|
|
134
156
|
|
|
135
|
-
|
|
157
|
+
_brokersAsRowsByMethod(method) {
|
|
136
158
|
const rows = store._brokersAsArray(method)
|
|
137
159
|
.map(b => new BrokerRowModel(b, store.canProxy))
|
|
138
160
|
.sort((a, b) => a.urlMask.localeCompare(b.urlMask))
|
|
@@ -145,12 +167,14 @@ export const store = {
|
|
|
145
167
|
return rows
|
|
146
168
|
},
|
|
147
169
|
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
170
|
+
_brokersAsArray(byMethod = '*') {
|
|
171
|
+
const arr = []
|
|
172
|
+
for (const [method, brokers] of Object.entries(store.brokersByMethod))
|
|
173
|
+
if (byMethod === '*' || byMethod === method)
|
|
174
|
+
arr.push(...Object.values(brokers))
|
|
175
|
+
return arr
|
|
153
176
|
},
|
|
177
|
+
|
|
154
178
|
|
|
155
179
|
previewLink(method, urlMask) {
|
|
156
180
|
store.setChosenLink(method, urlMask)
|
package/src/client/app.css
CHANGED
|
@@ -423,7 +423,7 @@ main {
|
|
|
423
423
|
padding-bottom: 4px;
|
|
424
424
|
padding-left: 1px;
|
|
425
425
|
border-top: 24px solid transparent;
|
|
426
|
-
margin-left:
|
|
426
|
+
margin-left: 94px;
|
|
427
427
|
font-weight: bold;
|
|
428
428
|
text-align: left;
|
|
429
429
|
|
|
@@ -432,13 +432,13 @@ main {
|
|
|
432
432
|
}
|
|
433
433
|
|
|
434
434
|
&.canProxy {
|
|
435
|
-
margin-left:
|
|
435
|
+
margin-left: 124px;
|
|
436
436
|
}
|
|
437
437
|
}
|
|
438
438
|
|
|
439
439
|
.TableRow {
|
|
440
440
|
display: flex;
|
|
441
|
-
|
|
441
|
+
margin-left: 24px;
|
|
442
442
|
|
|
443
443
|
&.animIn {
|
|
444
444
|
opacity: 0;
|
|
@@ -446,7 +446,86 @@ main {
|
|
|
446
446
|
animation: _kfRowIn 180ms ease-in-out forwards;
|
|
447
447
|
}
|
|
448
448
|
}
|
|
449
|
+
|
|
450
|
+
.FolderGroup {
|
|
451
|
+
position: relative;
|
|
452
|
+
|
|
453
|
+
> summary {
|
|
454
|
+
left: 0;
|
|
455
|
+
display: flex;
|
|
456
|
+
overflow: hidden;
|
|
457
|
+
align-items: center;
|
|
458
|
+
padding: 5px 0;
|
|
459
|
+
font-size: 12px;
|
|
460
|
+
list-style: none;
|
|
461
|
+
cursor: pointer;
|
|
462
|
+
user-select: none;
|
|
463
|
+
color: var(--colorLabel);
|
|
464
|
+
white-space: nowrap;
|
|
465
|
+
text-overflow: ellipsis;
|
|
466
|
+
border-radius: var(--radius);
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
&:active {
|
|
470
|
+
cursor: grabbing;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
&::-webkit-details-marker {
|
|
474
|
+
display: none;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
&:hover {
|
|
478
|
+
color: var(--colorText);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
.FolderName {
|
|
482
|
+
margin-left: 125px;
|
|
483
|
+
&.canProxy {
|
|
484
|
+
margin-left: 155px;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
&.groupedByMethod {
|
|
488
|
+
margin-left: 79px;
|
|
489
|
+
&.canProxy {
|
|
490
|
+
margin-left: 109px;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.FolderChevron {
|
|
496
|
+
display: flex;
|
|
497
|
+
width: 16px;
|
|
498
|
+
height: 16px;
|
|
499
|
+
flex-shrink: 0;
|
|
500
|
+
align-items: center;
|
|
501
|
+
justify-content: center;
|
|
502
|
+
opacity: .7;
|
|
503
|
+
transition: transform cubic-bezier(.2, .7, .8, 1.4) 400ms;
|
|
504
|
+
transform: rotate(-90deg);
|
|
505
|
+
|
|
506
|
+
svg {
|
|
507
|
+
width: 100%;
|
|
508
|
+
height: 100%;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
&[open] > summary {
|
|
514
|
+
position: absolute;
|
|
515
|
+
top: 0;
|
|
516
|
+
left: 0;
|
|
517
|
+
width: 16px;
|
|
518
|
+
|
|
519
|
+
.FolderChevron {
|
|
520
|
+
transform: rotate(0deg);
|
|
521
|
+
}
|
|
522
|
+
.FolderName {
|
|
523
|
+
display: none;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
449
527
|
}
|
|
528
|
+
|
|
450
529
|
@keyframes _kfRowIn {
|
|
451
530
|
to {
|
|
452
531
|
opacity: 1;
|
|
@@ -458,6 +537,7 @@ main {
|
|
|
458
537
|
overflow: hidden;
|
|
459
538
|
min-width: 38px;
|
|
460
539
|
padding: 4px 0;
|
|
540
|
+
margin-top: 3px;
|
|
461
541
|
margin-right: 8px;
|
|
462
542
|
color: var(--colorLabel);
|
|
463
543
|
font-size: 11px;
|
|
@@ -523,6 +603,7 @@ main {
|
|
|
523
603
|
|
|
524
604
|
.Toggler {
|
|
525
605
|
display: flex;
|
|
606
|
+
margin-top: 3px;
|
|
526
607
|
|
|
527
608
|
input {
|
|
528
609
|
/* For click drag target */
|
package/src/client/app.js
CHANGED
|
@@ -3,8 +3,8 @@ import { createElement as r, t, classNames, restoreFocus, Fragment, defineClassN
|
|
|
3
3
|
import { store } from './app-store.js'
|
|
4
4
|
import { API } from './ApiConstants.js'
|
|
5
5
|
import { Header } from './app-header.js'
|
|
6
|
-
import { TimerIcon, CloudIcon } from './graphics.js'
|
|
7
6
|
import { PayloadViewer, previewMock } from './app-payload-viewer.js'
|
|
7
|
+
import { TimerIcon, CloudIcon, ChevronDownIcon } from './graphics.js'
|
|
8
8
|
|
|
9
9
|
import CSS from './app.css' with { type: 'css' }
|
|
10
10
|
document.adoptedStyleSheets.push(CSS)
|
|
@@ -100,9 +100,35 @@ function MockList() {
|
|
|
100
100
|
r('div', {
|
|
101
101
|
className: classNames(CSS.TableHeading, store.canProxy && CSS.canProxy)
|
|
102
102
|
}, method),
|
|
103
|
-
store.
|
|
103
|
+
FolderGroups(store.folderGroupsByMethod(method))))
|
|
104
104
|
|
|
105
|
-
return store.
|
|
105
|
+
return FolderGroups(store.folderGroupsByMethod('*'))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function FolderGroups(groups) {
|
|
109
|
+
return groups.map(({ folder, children }) => {
|
|
110
|
+
if (children.length === 1)
|
|
111
|
+
return Row(children[0], 0)
|
|
112
|
+
|
|
113
|
+
return r('details', {
|
|
114
|
+
className: CSS.FolderGroup,
|
|
115
|
+
open: !store.collapsedFolders.has(folder),
|
|
116
|
+
onToggle() {
|
|
117
|
+
// TODO alt+click exclusive open
|
|
118
|
+
store.setFolderCollapsed(folder, !this.open)
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
r('summary', null,
|
|
122
|
+
r('span', { className: CSS.FolderChevron }, ChevronDownIcon()),
|
|
123
|
+
r('span', {
|
|
124
|
+
className: classNames(
|
|
125
|
+
CSS.FolderName,
|
|
126
|
+
store.groupByMethod && CSS.groupedByMethod,
|
|
127
|
+
store.canProxy && CSS.canProxy)
|
|
128
|
+
},
|
|
129
|
+
folder + '…')),
|
|
130
|
+
children.map(Row))
|
|
131
|
+
})
|
|
106
132
|
}
|
|
107
133
|
|
|
108
134
|
/**
|
package/src/client/graphics.js
CHANGED
|
@@ -27,3 +27,9 @@ export function HelpIcon() {
|
|
|
27
27
|
s('svg', { viewBox: '0 0 24 24' },
|
|
28
28
|
s('path', { d: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m1 17h-2v-2h2zm2.07-7.75-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25' })))
|
|
29
29
|
}
|
|
30
|
+
|
|
31
|
+
export function ChevronDownIcon() {
|
|
32
|
+
return (
|
|
33
|
+
s('svg', { viewBox: '0 0 24 24', stroke: 'currentColor', fill: 'none', 'stroke-width': '2' },
|
|
34
|
+
s('path', { d: 'M6 9l6 6 6-6' })))
|
|
35
|
+
}
|
package/src/server/Api.js
CHANGED
|
@@ -113,7 +113,6 @@ async function setGlobalDelay(req, response) {
|
|
|
113
113
|
response.unprocessable(`Expected non-negative integer for "delay"`)
|
|
114
114
|
else {
|
|
115
115
|
config.delay = delay
|
|
116
|
-
uiSyncVersion.increment()
|
|
117
116
|
response.ok()
|
|
118
117
|
uiSyncVersion.increment()
|
|
119
118
|
}
|
|
@@ -219,7 +218,7 @@ async function setRouteIsDelayed(req, response) {
|
|
|
219
218
|
else {
|
|
220
219
|
broker.setDelayed(delayed)
|
|
221
220
|
response.json(broker)
|
|
222
|
-
uiSyncVersion.increment()
|
|
221
|
+
uiSyncVersion.increment()
|
|
223
222
|
}
|
|
224
223
|
}
|
|
225
224
|
|
package/src/server/MockBroker.js
CHANGED
|
@@ -36,10 +36,10 @@ export class MockBroker {
|
|
|
36
36
|
|
|
37
37
|
unregister(file) {
|
|
38
38
|
this.mocks = this.mocks.filter(f => f !== file)
|
|
39
|
-
const
|
|
40
|
-
if (!
|
|
39
|
+
const brokerIsEmpty = !this.mocks.length
|
|
40
|
+
if (!brokerIsEmpty && this.file === file)
|
|
41
41
|
this.selectDefaultFile()
|
|
42
|
-
return
|
|
42
|
+
return brokerIsEmpty
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
hasMock = file => this.mocks.includes(file)
|
|
@@ -53,11 +53,8 @@ export async function dispatchMock(req, response) {
|
|
|
53
53
|
response.setHeader('Content-Type', mime)
|
|
54
54
|
response.setHeader('Content-Length', length(body))
|
|
55
55
|
|
|
56
|
-
setTimeout(() =>
|
|
57
|
-
|
|
58
|
-
? null
|
|
59
|
-
: body
|
|
60
|
-
), Number(broker.delayed && calcDelay()))
|
|
56
|
+
setTimeout(() => response.end(isHead ? null : body),
|
|
57
|
+
Number(broker.delayed && calcDelay()))
|
|
61
58
|
|
|
62
59
|
logger.accessMock(req.url, broker.file)
|
|
63
60
|
}
|
package/src/server/Mockaton.js
CHANGED
|
@@ -5,9 +5,8 @@ import pkgJSON from '../../package.json' with { type: 'json' }
|
|
|
5
5
|
|
|
6
6
|
import { logger } from './utils/logger.js'
|
|
7
7
|
import { ServerResponse } from './utils/HttpServerResponse.js'
|
|
8
|
-
import { IncomingMessage } from './utils/HttpIncomingMessage.js'
|
|
8
|
+
import { IncomingMessage, BodyReaderError, hasControlChars } from './utils/HttpIncomingMessage.js'
|
|
9
9
|
import { setCorsHeaders, isPreflight } from './utils/http-cors.js'
|
|
10
|
-
import { BodyReaderError, hasControlChars } from './utils/HttpIncomingMessage.js'
|
|
11
10
|
|
|
12
11
|
import { API } from '../client/ApiConstants.js'
|
|
13
12
|
|
|
@@ -7,7 +7,7 @@ import { mkdtempSync } from 'node:fs'
|
|
|
7
7
|
import { randomUUID } from 'node:crypto'
|
|
8
8
|
import { equal, deepEqual, match } from 'node:assert/strict'
|
|
9
9
|
import { describe, test, before, beforeEach, after } from 'node:test'
|
|
10
|
-
import { unlink, mkdir, readFile, rename, readdir, writeFile } from 'node:fs/promises'
|
|
10
|
+
import { unlink, mkdir, readFile, rename, readdir, writeFile, rm } from 'node:fs/promises'
|
|
11
11
|
|
|
12
12
|
import { mimeFor } from './utils/mime.js'
|
|
13
13
|
import { parseFilename } from '../client/Filename.js'
|
|
@@ -26,8 +26,15 @@ const proc = spawn(join(import.meta.dirname, 'cli.js'), [
|
|
|
26
26
|
'--no-open'
|
|
27
27
|
])
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
proc.
|
|
29
|
+
const DEBUG = false
|
|
30
|
+
proc.stdout.on('data', data => {
|
|
31
|
+
stdout.push(data.toString())
|
|
32
|
+
DEBUG && process.stdout.write(stdout.at(-1))
|
|
33
|
+
})
|
|
34
|
+
proc.stderr.on('data', data => {
|
|
35
|
+
stderr.push(data.toString())
|
|
36
|
+
DEBUG && process.stderr.write(stdout.at(-1))
|
|
37
|
+
})
|
|
31
38
|
|
|
32
39
|
const serverAddr = await new Promise((resolve, reject) => {
|
|
33
40
|
proc.stdout.once('data', () => {
|
|
@@ -41,12 +48,14 @@ after(() => proc.kill('SIGUSR2'))
|
|
|
41
48
|
|
|
42
49
|
|
|
43
50
|
const rmFromMocksDir = f => unlink(join(mocksDir, f))
|
|
44
|
-
const listFromMocksDir = d => readdir(join(mocksDir, d))
|
|
45
51
|
const readFromMocksDir = f => readFile(join(mocksDir, f), 'utf8')
|
|
46
52
|
const writeInMocksDir = (f, data) => writeFile(join(mocksDir, f), data)
|
|
47
|
-
const makeDirInMocks = dir => mkdir(join(mocksDir, dir), { recursive: true })
|
|
48
53
|
const renameInMocksDir = (src, target) => rename(join(mocksDir, src), join(mocksDir, target))
|
|
49
54
|
|
|
55
|
+
const listFromMocksDir = d => readdir(join(mocksDir, d))
|
|
56
|
+
const rmDirFromMocks = d => rm(join(mocksDir, d), { recursive: true })
|
|
57
|
+
const makeDirInMocks = dir => mkdir(join(mocksDir, dir), { recursive: true })
|
|
58
|
+
|
|
50
59
|
|
|
51
60
|
const api = new Commander(serverAddr)
|
|
52
61
|
|
|
@@ -61,29 +70,10 @@ function request(path, options = {}) {
|
|
|
61
70
|
}
|
|
62
71
|
|
|
63
72
|
|
|
64
|
-
class
|
|
65
|
-
dir = ''
|
|
66
|
-
urlMask = ''
|
|
67
|
-
method = ''
|
|
68
|
-
|
|
73
|
+
class Fixture {
|
|
69
74
|
constructor(file, body = '') {
|
|
70
75
|
this.file = file
|
|
71
76
|
this.body = body || `Body for ${file}`
|
|
72
|
-
}
|
|
73
|
-
write() { return api.writeMock(this.file, this.body) }
|
|
74
|
-
delete() { return api.deleteMock(this.file) }
|
|
75
|
-
|
|
76
|
-
request(options = {}) {
|
|
77
|
-
options.method ??= this.method
|
|
78
|
-
return request(this.urlMask, options)
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
class Fixture extends BaseFixture {
|
|
84
|
-
constructor(file, body = '') {
|
|
85
|
-
super(file, body)
|
|
86
|
-
this.dir = mocksDir
|
|
87
77
|
const t = parseFilename(file)
|
|
88
78
|
this.urlMask = t.urlMask
|
|
89
79
|
this.method = t.method
|
|
@@ -93,14 +83,12 @@ class Fixture extends BaseFixture {
|
|
|
93
83
|
async fetchBroker() {
|
|
94
84
|
return (await fetchState()).brokersByMethod?.[this.method]?.[this.urlMask]
|
|
95
85
|
}
|
|
96
|
-
}
|
|
86
|
+
write() { return api.writeMock(this.file, this.body) }
|
|
87
|
+
delete() { return api.deleteMock(this.file) }
|
|
97
88
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
this.dir = mocksDir
|
|
102
|
-
this.urlMask = '/' + file
|
|
103
|
-
this.method = 'GET'
|
|
89
|
+
request(options = {}) {
|
|
90
|
+
options.method ??= this.method
|
|
91
|
+
return request(this.urlMask, options)
|
|
104
92
|
}
|
|
105
93
|
}
|
|
106
94
|
|
|
@@ -614,8 +602,8 @@ describe('Dynamic Function Mocks', () => {
|
|
|
614
602
|
|
|
615
603
|
|
|
616
604
|
describe('Static Files', () => {
|
|
617
|
-
const fxsIndex = new
|
|
618
|
-
const fxsAsset = new
|
|
605
|
+
const fxsIndex = new Fixture('index.html', '<h1>Index</h1>')
|
|
606
|
+
const fxsAsset = new Fixture('asset-script.js', 'const a = 1')
|
|
619
607
|
before(async () => {
|
|
620
608
|
await api.reset()
|
|
621
609
|
await fxsIndex.write()
|
|
@@ -724,7 +712,7 @@ describe('Auto Status', () => {
|
|
|
724
712
|
})
|
|
725
713
|
|
|
726
714
|
test('toggling ON 404 for static routes', async () => {
|
|
727
|
-
const fx = new
|
|
715
|
+
const fx = new Fixture('static-404.txt')
|
|
728
716
|
await fx.write()
|
|
729
717
|
equal((await fx.request()).status, 200)
|
|
730
718
|
|
|
@@ -1040,10 +1028,6 @@ describe('Watch mocks API toggler', () => {
|
|
|
1040
1028
|
describe('Registering Mocks', () => {
|
|
1041
1029
|
// simulates user interacting with the file-system directly
|
|
1042
1030
|
class FixtureExternal extends Fixture {
|
|
1043
|
-
constructor(props) {
|
|
1044
|
-
super(props)
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
1031
|
async writeExternally() {
|
|
1048
1032
|
const nextVerPromise = resolveOnNextSyncVersion()
|
|
1049
1033
|
await sleep(0) // next macro task
|
|
@@ -1063,7 +1047,6 @@ describe('Registering Mocks', () => {
|
|
|
1063
1047
|
return new Promise(resolve => setTimeout(resolve, ms))
|
|
1064
1048
|
}
|
|
1065
1049
|
|
|
1066
|
-
|
|
1067
1050
|
const fxA = new FixtureExternal('register(default).GET.200.json')
|
|
1068
1051
|
const fxB = new FixtureExternal('register(alt).GET.200.json')
|
|
1069
1052
|
|
|
@@ -1138,14 +1121,24 @@ describe('Registering Mocks', () => {
|
|
|
1138
1121
|
})
|
|
1139
1122
|
|
|
1140
1123
|
test('responds when dir is renamed', async () => {
|
|
1141
|
-
const
|
|
1124
|
+
const prom = resolveOnNextSyncVersion(version + 2)
|
|
1142
1125
|
await renameInMocksDir('reg0', 'reg1')
|
|
1143
|
-
equal(await
|
|
1126
|
+
equal(await prom, version + 3)
|
|
1144
1127
|
|
|
1145
1128
|
const s = await fetchState()
|
|
1146
1129
|
equal(s.brokersByMethod.GET['/reg1/runtime0'].file, 'reg1/runtime0.GET.200.txt')
|
|
1147
1130
|
})
|
|
1148
1131
|
})
|
|
1132
|
+
|
|
1133
|
+
test('deleting a folder unregisters mocks in it', async () => {
|
|
1134
|
+
const fx = new Fixture('api/bulk-delete/bar.GET.200.json')
|
|
1135
|
+
await fx.write()
|
|
1136
|
+
await sleep(0)
|
|
1137
|
+
const nextVerPromise = resolveOnNextSyncVersion()
|
|
1138
|
+
await rmDirFromMocks('api/bulk-delete')
|
|
1139
|
+
await nextVerPromise
|
|
1140
|
+
equal(await fx.fetchBroker(), undefined)
|
|
1141
|
+
})
|
|
1149
1142
|
})
|
|
1150
1143
|
|
|
1151
1144
|
|
|
@@ -47,7 +47,6 @@ export function registerMock(file, isFromWatcher = false) {
|
|
|
47
47
|
collection[method] ??= {}
|
|
48
48
|
|
|
49
49
|
let broker = collection[method][urlMask]
|
|
50
|
-
|
|
51
50
|
if (!broker)
|
|
52
51
|
broker = collection[method][urlMask] = new MockBroker(file)
|
|
53
52
|
else
|
|
@@ -61,15 +60,26 @@ export function registerMock(file, isFromWatcher = false) {
|
|
|
61
60
|
|
|
62
61
|
export function unregisterMock(file) {
|
|
63
62
|
const broker = brokerByFilename(file)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
63
|
+
if (broker) {
|
|
64
|
+
const brokerIsEmpty = broker.unregister(file)
|
|
65
|
+
if (brokerIsEmpty) {
|
|
66
|
+
const { method, urlMask } = parseFilename(file)
|
|
67
|
+
delete collection[method][urlMask]
|
|
68
|
+
if (!Object.keys(collection[method]).length)
|
|
69
|
+
delete collection[method]
|
|
70
|
+
}
|
|
70
71
|
}
|
|
72
|
+
else for (const f of filesInDir(file)) // maybe it was a dir
|
|
73
|
+
unregisterMock(f)
|
|
71
74
|
}
|
|
72
75
|
|
|
76
|
+
function filesInDir(dir) {
|
|
77
|
+
const files = []
|
|
78
|
+
forEachBroker(b => {
|
|
79
|
+
files.push(...(b.mocks.filter(m => m.startsWith(dir + '/'))))
|
|
80
|
+
})
|
|
81
|
+
return files
|
|
82
|
+
}
|
|
73
83
|
|
|
74
84
|
/** @returns {MockBroker | undefined} */
|
|
75
85
|
export function brokerByFilename(file) {
|
|
@@ -19,14 +19,14 @@ export class ServerResponse extends http.ServerResponse {
|
|
|
19
19
|
|
|
20
20
|
html(html, csp) {
|
|
21
21
|
logger.access(this)
|
|
22
|
-
this.setHeader('Content-Type', mimeFor('html'))
|
|
22
|
+
this.setHeader('Content-Type', mimeFor('.html'))
|
|
23
23
|
this.setHeader('Content-Security-Policy', csp)
|
|
24
24
|
this.end(html)
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
json(payload) {
|
|
28
28
|
logger.access(this)
|
|
29
|
-
this.setHeader('Content-Type', mimeFor('json'))
|
|
29
|
+
this.setHeader('Content-Type', mimeFor('.json'))
|
|
30
30
|
this.end(JSON.stringify(payload))
|
|
31
31
|
}
|
|
32
32
|
|
package/src/server/utils/fs.js
CHANGED
|
@@ -545,7 +545,7 @@
|
|
|
545
545
|
"Filename": {
|
|
546
546
|
"type": "string",
|
|
547
547
|
"description": "Mock filename. The convention is UrlMask.Method.StatusCode.Extension",
|
|
548
|
-
"example": "api/user/
|
|
548
|
+
"example": "api/user/friends.GET.200.json"
|
|
549
549
|
},
|
|
550
550
|
"ClientMockBroker": {
|
|
551
551
|
"type": "object",
|