mockaton 13.11.1 → 13.11.3
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 +11 -8
- package/package.json +1 -1
- package/skills/mockaton/SKILL.md +8 -5
- package/src/client/ApiConstants.js +1 -1
- package/src/client/IndexHtml.js +1 -1
- package/src/client/app-header.css +231 -0
- package/src/client/app-header.js +13 -6
- package/src/client/app-mock-list.css +340 -0
- package/src/client/app-mock-list.js +370 -0
- package/src/client/app-payload-viewer.css +90 -0
- package/src/client/app-payload-viewer.js +4 -4
- package/src/client/app-store.js +1 -0
- package/src/client/app.css +0 -654
- package/src/client/app.js +18 -360
- package/src/client/utils/css.js +7 -0
- package/src/client/utils/watcherDev.js +4 -1
- package/src/server/Api.js +54 -53
- package/src/server/MockDispatcher.js +6 -5
- package/src/server/Mockaton.js +11 -12
- package/src/server/Mockaton.test.js +65 -51
- package/src/server/ProxyRelay.js +3 -2
- package/src/server/UrlParsers.js +1 -1
- package/src/server/UrlParsers.test.js +1 -1
- package/src/server/cli.js +37 -33
- package/src/server/cli.test.js +2 -4
- package/src/server/{Watcher.js → stores/Watcher.js} +18 -9
- package/src/server/{mockBrokersCollection.js → stores/brokers.js} +3 -3
- package/src/server/{config.js → stores/config.js} +28 -14
- package/src/server/utils/HttpServerResponse.test.js +5 -7
- package/src/server/utils/WatcherDevClient.js +1 -1
- package/src/server/utils/openInBrowser.js +15 -10
- package/www/src/assets/openapi.json +1 -1
- /package/src/server/{resolverBypassImportCache.js → ResolverBypassImportCache.js} +0 -0
- /package/src/server/{resolverResolveExtensionless.js → ResolverResolveExtensionless.js} +0 -0
- /package/src/server/{cookie.js → stores/cookies.js} +0 -0
package/src/server/Api.js
CHANGED
|
@@ -3,36 +3,36 @@
|
|
|
3
3
|
* selecting a specific mock-file for a particular route.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { join } from 'node:path'
|
|
7
|
-
import { write, rm, isFile, resolveIn, listFilesRecursively } from './utils/fs.js'
|
|
8
|
-
|
|
9
|
-
import openapi from '../../www/src/assets/openapi.json' with { type: 'json' }
|
|
10
|
-
import pkgJSON from '../../package.json' with { type: 'json' }
|
|
6
|
+
import { join, relative } from 'node:path'
|
|
11
7
|
|
|
8
|
+
import { write, rm, isFile, resolveIn } from './utils/fs.js'
|
|
9
|
+
import { removeQueryStringAndFragment } from './utils/HttpIncomingMessage.js'
|
|
12
10
|
import { sseClientHotReload } from './utils/WatcherDevClient.js'
|
|
13
|
-
|
|
11
|
+
|
|
12
|
+
import pkgJSON from '../../package.json' with { type: 'json' }
|
|
13
|
+
import openapi from '../../www/src/assets/openapi.json' with { type: 'json' }
|
|
14
14
|
|
|
15
15
|
import { API } from '../client/ApiConstants.js'
|
|
16
16
|
import { IndexHtml, CSP } from '../client/IndexHtml.js'
|
|
17
17
|
|
|
18
|
-
import { cookie } from './
|
|
19
|
-
import { config, ConfigValidator } from './config.js'
|
|
20
|
-
import * as
|
|
21
|
-
import
|
|
18
|
+
import { cookie } from './stores/cookies.js'
|
|
19
|
+
import { config, ConfigValidator, reinitConfig } from './stores/config.js'
|
|
20
|
+
import * as brokers from './stores/brokers.js'
|
|
21
|
+
import * as Watcher from './stores/Watcher.js'
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
export const CLIENT_ASSETS = join(import.meta.dirname, '../client')
|
|
25
25
|
|
|
26
|
-
const
|
|
27
|
-
[API.
|
|
26
|
+
const headReqs = new Map([
|
|
27
|
+
[API.health, (_, response) => response.ok()]
|
|
28
|
+
])
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
serveDashboardAsset(f)
|
|
32
|
-
]),
|
|
30
|
+
const getReqs = new Map([
|
|
31
|
+
...headReqs.entries(),
|
|
33
32
|
|
|
33
|
+
[API.root, serveDashboard],
|
|
34
34
|
[API.state, getState],
|
|
35
|
-
[API.syncVersion, sseClientSyncVersion],
|
|
35
|
+
[API.syncVersion, Watcher.sseClientSyncVersion],
|
|
36
36
|
|
|
37
37
|
[API.watchHotReload, onDevWatch],
|
|
38
38
|
[API.openAPI, (_, response) => response.json(openapi)],
|
|
@@ -62,20 +62,25 @@ const patchReqs = new Map([
|
|
|
62
62
|
])
|
|
63
63
|
|
|
64
64
|
export async function handleApiRequest(req, response) {
|
|
65
|
-
|
|
65
|
+
if (!req.url.startsWith(API.root))
|
|
66
|
+
return false
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
response.ok()
|
|
69
|
-
return
|
|
70
|
-
}
|
|
68
|
+
const url = removeQueryStringAndFragment(req.url)
|
|
71
69
|
|
|
72
70
|
const handler = (
|
|
73
71
|
req.method === 'GET' && getReqs.get(url) ||
|
|
72
|
+
req.method === 'HEAD' && headReqs.get(url) ||
|
|
74
73
|
req.method === 'PATCH' && patchReqs.get(url))
|
|
75
74
|
if (handler) {
|
|
76
75
|
await handler(req, response)
|
|
77
76
|
return true
|
|
78
77
|
}
|
|
78
|
+
|
|
79
|
+
if (req.method === 'GET') { // serve static dashboard assets dir
|
|
80
|
+
const f = await resolveIn(CLIENT_ASSETS, relative(API.root, url))
|
|
81
|
+
await response.file(f)
|
|
82
|
+
return true
|
|
83
|
+
}
|
|
79
84
|
}
|
|
80
85
|
|
|
81
86
|
|
|
@@ -85,18 +90,13 @@ function serveDashboard(_, response) {
|
|
|
85
90
|
response.html(IndexHtml(config.hotReload, pkgJSON.version), CSP)
|
|
86
91
|
}
|
|
87
92
|
|
|
88
|
-
function serveDashboardAsset(f) {
|
|
89
|
-
return async (_, response) => {
|
|
90
|
-
await response.file(join(CLIENT_ASSETS, f))
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
93
|
|
|
94
94
|
function getState(_, response) {
|
|
95
95
|
response.json({
|
|
96
96
|
cookies: cookie.list(),
|
|
97
|
-
comments:
|
|
97
|
+
comments: brokers.extractAllComments(),
|
|
98
98
|
|
|
99
|
-
brokersByMethod:
|
|
99
|
+
brokersByMethod: brokers.all(),
|
|
100
100
|
|
|
101
101
|
delay: config.delay,
|
|
102
102
|
delayJitter: config.delayJitter,
|
|
@@ -118,10 +118,11 @@ function onDevWatch(req, response) {
|
|
|
118
118
|
/** # PATCH */
|
|
119
119
|
|
|
120
120
|
function reset(_, response) {
|
|
121
|
-
|
|
121
|
+
reinitConfig()
|
|
122
|
+
brokers.init()
|
|
122
123
|
cookie.init(config.cookies)
|
|
123
124
|
response.ok()
|
|
124
|
-
|
|
125
|
+
Watcher.emitChange()
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
|
|
@@ -133,7 +134,7 @@ async function setCorsAllowed(req, response) {
|
|
|
133
134
|
else {
|
|
134
135
|
config.corsAllowed = corsAllowed
|
|
135
136
|
response.ok()
|
|
136
|
-
|
|
137
|
+
Watcher.emitChange()
|
|
137
138
|
}
|
|
138
139
|
}
|
|
139
140
|
|
|
@@ -146,7 +147,7 @@ async function setGlobalDelay(req, response) {
|
|
|
146
147
|
else {
|
|
147
148
|
config.delay = delay
|
|
148
149
|
response.ok()
|
|
149
|
-
|
|
150
|
+
Watcher.emitChange()
|
|
150
151
|
}
|
|
151
152
|
}
|
|
152
153
|
|
|
@@ -158,7 +159,7 @@ async function setGlobalDelayJitter(req, response) {
|
|
|
158
159
|
else {
|
|
159
160
|
config.delayJitter = jitter
|
|
160
161
|
response.ok()
|
|
161
|
-
|
|
162
|
+
Watcher.emitChange()
|
|
162
163
|
}
|
|
163
164
|
}
|
|
164
165
|
|
|
@@ -170,7 +171,7 @@ async function selectCookie(req, response) {
|
|
|
170
171
|
response.unprocessable(error?.message || error)
|
|
171
172
|
else {
|
|
172
173
|
response.json(cookie.list())
|
|
173
|
-
|
|
174
|
+
Watcher.emitChange()
|
|
174
175
|
}
|
|
175
176
|
}
|
|
176
177
|
|
|
@@ -183,7 +184,7 @@ async function setProxyFallback(req, response) {
|
|
|
183
184
|
else {
|
|
184
185
|
config.proxyFallback = fallback
|
|
185
186
|
response.ok()
|
|
186
|
-
|
|
187
|
+
Watcher.emitChange()
|
|
187
188
|
}
|
|
188
189
|
}
|
|
189
190
|
|
|
@@ -195,7 +196,7 @@ async function setCollectProxied(req, response) {
|
|
|
195
196
|
else {
|
|
196
197
|
config.collectProxied = collectProxied
|
|
197
198
|
response.ok()
|
|
198
|
-
|
|
199
|
+
Watcher.emitChange()
|
|
199
200
|
}
|
|
200
201
|
}
|
|
201
202
|
|
|
@@ -203,41 +204,41 @@ async function setCollectProxied(req, response) {
|
|
|
203
204
|
|
|
204
205
|
async function bulkUpdateBrokersByCommentTag(req, response) {
|
|
205
206
|
const comment = await req.json()
|
|
206
|
-
|
|
207
|
+
brokers.setMocksMatchingComment(comment)
|
|
207
208
|
response.ok()
|
|
208
|
-
|
|
209
|
+
Watcher.emitChange()
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
|
|
212
213
|
async function selectMock(req, response) {
|
|
213
214
|
const file = await req.json()
|
|
214
|
-
const broker =
|
|
215
|
+
const broker = brokers.brokerByFilename(file)
|
|
215
216
|
if (!broker || !broker.hasMock(file))
|
|
216
217
|
response.unprocessable(`Missing Mock: ${file}`)
|
|
217
218
|
else {
|
|
218
219
|
broker.selectFile(file)
|
|
219
220
|
response.json(broker)
|
|
220
|
-
|
|
221
|
+
Watcher.emitChange()
|
|
221
222
|
}
|
|
222
223
|
}
|
|
223
224
|
|
|
224
225
|
|
|
225
226
|
async function toggleRouteStatus(req, response) {
|
|
226
227
|
const [method, urlMask, status] = await req.json()
|
|
227
|
-
const broker =
|
|
228
|
+
const broker = brokers.brokerByRoute(method, urlMask)
|
|
228
229
|
if (!broker)
|
|
229
230
|
response.unprocessable(`Route does not exist: ${method} ${urlMask}`)
|
|
230
231
|
else {
|
|
231
232
|
broker.toggleStatus(status)
|
|
232
233
|
response.json(broker)
|
|
233
|
-
|
|
234
|
+
Watcher.emitChange()
|
|
234
235
|
}
|
|
235
236
|
}
|
|
236
237
|
|
|
237
238
|
|
|
238
239
|
async function setRouteIsDelayed(req, response) {
|
|
239
240
|
const [method, urlMask, delayed] = await req.json()
|
|
240
|
-
const broker =
|
|
241
|
+
const broker = brokers.brokerByRoute(method, urlMask)
|
|
241
242
|
if (!broker)
|
|
242
243
|
response.unprocessable(`Route does not exist: ${method} ${urlMask}`)
|
|
243
244
|
else if (typeof delayed !== 'boolean')
|
|
@@ -245,14 +246,14 @@ async function setRouteIsDelayed(req, response) {
|
|
|
245
246
|
else {
|
|
246
247
|
broker.setDelayed(delayed)
|
|
247
248
|
response.json(broker)
|
|
248
|
-
|
|
249
|
+
Watcher.emitChange()
|
|
249
250
|
}
|
|
250
251
|
}
|
|
251
252
|
|
|
252
253
|
|
|
253
254
|
async function setRouteIsProxied(req, response) {
|
|
254
255
|
const [method, urlMask, proxied] = await req.json()
|
|
255
|
-
const broker =
|
|
256
|
+
const broker = brokers.brokerByRoute(method, urlMask)
|
|
256
257
|
if (!broker)
|
|
257
258
|
response.unprocessable(`Route does not exist: ${method} ${urlMask}`)
|
|
258
259
|
else if (typeof proxied !== 'boolean')
|
|
@@ -262,7 +263,7 @@ async function setRouteIsProxied(req, response) {
|
|
|
262
263
|
else {
|
|
263
264
|
broker.setProxied(proxied)
|
|
264
265
|
response.json(broker)
|
|
265
|
-
|
|
266
|
+
Watcher.emitChange()
|
|
266
267
|
}
|
|
267
268
|
}
|
|
268
269
|
|
|
@@ -282,8 +283,8 @@ async function writeMock(req, response) {
|
|
|
282
283
|
await write(path, content)
|
|
283
284
|
|
|
284
285
|
if (!config.watcherEnabled) {
|
|
285
|
-
|
|
286
|
-
|
|
286
|
+
brokers.registerMock(file, true)
|
|
287
|
+
Watcher.emitChange()
|
|
287
288
|
}
|
|
288
289
|
response.ok()
|
|
289
290
|
}
|
|
@@ -305,8 +306,8 @@ async function deleteMock(req, response) {
|
|
|
305
306
|
await rm(path)
|
|
306
307
|
|
|
307
308
|
if (!config.watcherEnabled) {
|
|
308
|
-
|
|
309
|
-
|
|
309
|
+
brokers.unregisterMock(file)
|
|
310
|
+
Watcher.emitChange()
|
|
310
311
|
}
|
|
311
312
|
response.ok()
|
|
312
313
|
}
|
|
@@ -319,9 +320,9 @@ async function setWatchMocks(req, response) {
|
|
|
319
320
|
response.unprocessable(`Expected boolean for "watchMocks"`)
|
|
320
321
|
else {
|
|
321
322
|
if (enabled)
|
|
322
|
-
watchMocksDir()
|
|
323
|
+
Watcher.watchMocksDir()
|
|
323
324
|
else
|
|
324
|
-
|
|
325
|
+
Watcher.unwatchMocksDir()
|
|
325
326
|
response.ok()
|
|
326
327
|
}
|
|
327
328
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { join } from 'node:path'
|
|
2
2
|
|
|
3
|
-
import { logger } from './utils/logger.js'
|
|
4
3
|
import { proxy } from './ProxyRelay.js'
|
|
5
|
-
import { cookie } from './
|
|
6
|
-
import { parseFilename } from '../client/Filename.js'
|
|
4
|
+
import { cookie } from './stores/cookies.js'
|
|
7
5
|
import { echoFilePlugin } from './MockDispatcherPlugins.js'
|
|
8
|
-
import { brokerByRoute } from './
|
|
9
|
-
import { config, calcDelay } from './config.js'
|
|
6
|
+
import { brokerByRoute } from './stores/brokers.js'
|
|
7
|
+
import { config, calcDelay } from './stores/config.js'
|
|
8
|
+
|
|
9
|
+
import { logger } from './utils/logger.js'
|
|
10
|
+
import { parseFilename } from '../client/Filename.js'
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
export async function dispatchMock(req, response) {
|
package/src/server/Mockaton.js
CHANGED
|
@@ -7,29 +7,28 @@ import { logger } from './utils/logger.js'
|
|
|
7
7
|
import { ServerResponse } from './utils/HttpServerResponse.js'
|
|
8
8
|
import { setCorsHeaders, isPreflight } from './utils/http-cors.js'
|
|
9
9
|
import { IncomingMessage, BodyReaderError, hasControlChars } from './utils/HttpIncomingMessage.js'
|
|
10
|
+
import { watchDevSPA } from './utils/WatcherDevClient.js'
|
|
10
11
|
|
|
11
12
|
import { API } from '../client/ApiConstants.js'
|
|
12
|
-
import { cookie } from './cookie.js'
|
|
13
|
-
import { config, setup } from './config.js'
|
|
14
|
-
import { CLIENT_ASSETS, handleApiRequest } from './Api.js'
|
|
15
13
|
|
|
14
|
+
import { CLIENT_ASSETS, handleApiRequest } from './Api.js'
|
|
15
|
+
import { cookie } from './stores/cookies.js'
|
|
16
|
+
import { config, initConfig } from './stores/config.js'
|
|
17
|
+
import * as brokers from './stores/brokers.js'
|
|
16
18
|
import { dispatchMock } from './MockDispatcher.js'
|
|
17
|
-
import
|
|
18
|
-
|
|
19
|
-
import { watchDevSPA } from './utils/WatcherDevClient.js'
|
|
20
|
-
import { watchMocksDir } from './Watcher.js'
|
|
19
|
+
import { watchMocksDir } from './stores/Watcher.js'
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
export function Mockaton(options) {
|
|
24
23
|
return new Promise((resolve, reject) => {
|
|
25
|
-
|
|
24
|
+
initConfig(options)
|
|
26
25
|
cookie.init(config.cookies)
|
|
27
|
-
|
|
26
|
+
brokers.init()
|
|
28
27
|
|
|
29
|
-
register('./
|
|
28
|
+
register('./ResolverResolveExtensionless.js', import.meta.url)
|
|
30
29
|
|
|
31
30
|
if (config.bypassImportCache)
|
|
32
|
-
register('./
|
|
31
|
+
register('./ResolverBypassImportCache.js', import.meta.url)
|
|
33
32
|
|
|
34
33
|
if (config.watcherEnabled)
|
|
35
34
|
watchMocksDir()
|
|
@@ -41,7 +40,7 @@ export function Mockaton(options) {
|
|
|
41
40
|
server.on('error', reject)
|
|
42
41
|
server.listen(config.port, config.host, () => {
|
|
43
42
|
const url = `http://${server.address().address}:${server.address().port}`
|
|
44
|
-
const dashboardUrl = url + API.
|
|
43
|
+
const dashboardUrl = url + API.root
|
|
45
44
|
logger.info('Listening', url)
|
|
46
45
|
logger.info('Dashboard', dashboardUrl)
|
|
47
46
|
config.onReady(dashboardUrl)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from 'node:fs/promises'
|
|
1
2
|
import { join } from 'node:path'
|
|
2
3
|
import { spawn } from 'node:child_process'
|
|
3
4
|
import { tmpdir } from 'node:os'
|
|
@@ -7,22 +8,32 @@ import { mkdtempSync } from 'node:fs'
|
|
|
7
8
|
import { randomUUID } from 'node:crypto'
|
|
8
9
|
import { equal, deepEqual, match } from 'node:assert/strict'
|
|
9
10
|
import { describe, test, before, beforeEach, after } from 'node:test'
|
|
10
|
-
import { unlink, mkdir, readFile, rename, readdir, writeFile, rm } from 'node:fs/promises'
|
|
11
11
|
|
|
12
|
+
import CONFIG from './Mockaton.test.config.js'
|
|
13
|
+
import { config } from './stores/config.js'
|
|
12
14
|
import { mimeFor } from './utils/mime.js'
|
|
15
|
+
import { API } from '../client/ApiConstants.js'
|
|
16
|
+
import { Commander } from '../client/ApiCommander.js'
|
|
13
17
|
import { parseFilename } from '../client/Filename.js'
|
|
14
|
-
import { API, Commander } from '../../index.js'
|
|
15
18
|
|
|
16
|
-
import CONFIG from './Mockaton.test.config.js'
|
|
17
|
-
import { config } from './config.js'
|
|
18
19
|
|
|
20
|
+
const mocksDir = new class {
|
|
21
|
+
value = mkdtempSync(join(tmpdir(), 'mocks'))
|
|
22
|
+
|
|
23
|
+
rm = f => fs.unlink(join(this.value, f))
|
|
24
|
+
read = f => fs.readFile(join(this.value, f), 'utf8')
|
|
25
|
+
write = (f, data) => fs.writeFile(join(this.value, f), data)
|
|
26
|
+
rename = (src, target) => fs.rename(join(this.value, src), join(this.value, target))
|
|
19
27
|
|
|
20
|
-
|
|
28
|
+
list = d => fs.readdir(join(this.value, d))
|
|
29
|
+
rmdir = d => fs.rm(join(this.value, d), { recursive: true })
|
|
30
|
+
mkdir = d => fs.mkdir(join(this.value, d), { recursive: true })
|
|
31
|
+
}
|
|
21
32
|
|
|
22
33
|
const stdout = []
|
|
23
34
|
const stderr = []
|
|
24
35
|
const proc = spawn(join(import.meta.dirname, 'cli.js'), [
|
|
25
|
-
mocksDir,
|
|
36
|
+
mocksDir.value,
|
|
26
37
|
'--config', join(import.meta.dirname, 'Mockaton.test.config.js'),
|
|
27
38
|
'--no-open'
|
|
28
39
|
])
|
|
@@ -47,17 +58,6 @@ const serverAddr = await new Promise((resolve, reject) => {
|
|
|
47
58
|
|
|
48
59
|
after(() => proc.kill('SIGUSR2'))
|
|
49
60
|
|
|
50
|
-
|
|
51
|
-
const rmFromMocksDir = f => unlink(join(mocksDir, f))
|
|
52
|
-
const readFromMocksDir = f => readFile(join(mocksDir, f), 'utf8')
|
|
53
|
-
const writeInMocksDir = (f, data) => writeFile(join(mocksDir, f), data)
|
|
54
|
-
const renameInMocksDir = (src, target) => rename(join(mocksDir, src), join(mocksDir, target))
|
|
55
|
-
|
|
56
|
-
const listFromMocksDir = d => readdir(join(mocksDir, d))
|
|
57
|
-
const rmDirFromMocks = d => rm(join(mocksDir, d), { recursive: true })
|
|
58
|
-
const makeDirInMocks = dir => mkdir(join(mocksDir, dir), { recursive: true })
|
|
59
|
-
|
|
60
|
-
|
|
61
61
|
const api = new Commander(serverAddr)
|
|
62
62
|
|
|
63
63
|
|
|
@@ -224,17 +224,17 @@ describe('CORS', () => {
|
|
|
224
224
|
|
|
225
225
|
describe('Dashboard', () => {
|
|
226
226
|
test('renders', async () => {
|
|
227
|
-
const r = await request(API.
|
|
227
|
+
const r = await request(API.root)
|
|
228
228
|
match(await r.text(), new RegExp('<!DOCTYPE html>'))
|
|
229
229
|
})
|
|
230
230
|
|
|
231
231
|
test('query string is accepted', async () => {
|
|
232
|
-
const r = await request(API.
|
|
232
|
+
const r = await request(API.root + '?foo=bar')
|
|
233
233
|
match(await r.text(), new RegExp('<!DOCTYPE html>'))
|
|
234
234
|
})
|
|
235
235
|
|
|
236
236
|
test('serves assets', async () => {
|
|
237
|
-
const r = await request(API.
|
|
237
|
+
const r = await request(API.root + '/app.css')
|
|
238
238
|
match(await r.text(), new RegExp(':root {'))
|
|
239
239
|
})
|
|
240
240
|
})
|
|
@@ -303,6 +303,7 @@ describe('Delay', () => {
|
|
|
303
303
|
const r = await api.setGlobalDelayJitter(0.1)
|
|
304
304
|
equal(r.status, 200)
|
|
305
305
|
equal((await fetchState()).delayJitter, 0.1)
|
|
306
|
+
await api.setGlobalDelayJitter(0)
|
|
306
307
|
})
|
|
307
308
|
})
|
|
308
309
|
|
|
@@ -388,14 +389,14 @@ describe('Proxy Fallback', () => {
|
|
|
388
389
|
deepEqual(await r2.json(), BODY_PAYLOAD)
|
|
389
390
|
deepEqual(await r1.json(), BODY_PAYLOAD)
|
|
390
391
|
|
|
391
|
-
const savedMocks = await
|
|
392
|
+
const savedMocks = await mocksDir.list('non-existing-mock')
|
|
392
393
|
equal(savedMocks.length, 2)
|
|
393
394
|
|
|
394
|
-
equal(await
|
|
395
|
+
equal(await mocksDir.read('non-existing-mock/[id].POST.423.json'), expectedBody)
|
|
395
396
|
for (const m of savedMocks) {
|
|
396
397
|
const f = join('non-existing-mock', m)
|
|
397
|
-
equal(await
|
|
398
|
-
await
|
|
398
|
+
equal(await mocksDir.read(f), expectedBody)
|
|
399
|
+
await mocksDir.rm(f)
|
|
399
400
|
}
|
|
400
401
|
})
|
|
401
402
|
})
|
|
@@ -919,7 +920,7 @@ describe('Dynamic Params', () => {
|
|
|
919
920
|
const fx2 = new Fixture('dynamic-params/[id]/suffix/[id].GET.200.txt')
|
|
920
921
|
const fx3 = new Fixture('dynamic-params/exact-route.GET.200.txt')
|
|
921
922
|
before(async () => {
|
|
922
|
-
await
|
|
923
|
+
await mocksDir.mkdir('dynamic-params/[id]/suffix/[id]')
|
|
923
924
|
await fx0.write()
|
|
924
925
|
await fx1.write()
|
|
925
926
|
await fx2.write()
|
|
@@ -955,7 +956,7 @@ describe('Dynamic Params', () => {
|
|
|
955
956
|
|
|
956
957
|
test('Dynamic Params on partial segments', async () => {
|
|
957
958
|
const fx = new Fixture('dynamic-params-partial-[id]/foo.GET.200.txt')
|
|
958
|
-
await
|
|
959
|
+
await mocksDir.mkdir('dynamic-params-partial-[id]')
|
|
959
960
|
await fx.write()
|
|
960
961
|
const r = await request('/dynamic-params-partial-999/foo')
|
|
961
962
|
equal(await r.text(), fx.body)
|
|
@@ -967,7 +968,7 @@ describe('Query String', () => {
|
|
|
967
968
|
const fx0 = new Fixture('query-string?foo=[foo]&bar=[bar].GET.200.json')
|
|
968
969
|
const fx1 = new Fixture('query-string/[id]?limit=[limit].GET.200.json')
|
|
969
970
|
before(async () => {
|
|
970
|
-
await
|
|
971
|
+
await mocksDir.mkdir('query-string')
|
|
971
972
|
await fx0.write()
|
|
972
973
|
await fx1.write()
|
|
973
974
|
await api.reset()
|
|
@@ -1126,10 +1127,10 @@ describe('Registering Mocks', () => {
|
|
|
1126
1127
|
test('when watcher is off, newly added mocks do not get registered', async () => {
|
|
1127
1128
|
await api.setWatchMocks(false)
|
|
1128
1129
|
const fx = new FixtureExternal('non-auto-registered-file.GET.200.json')
|
|
1129
|
-
await
|
|
1130
|
+
await mocksDir.write(fx.file, fx.body)
|
|
1130
1131
|
await sleep(100)
|
|
1131
1132
|
equal(await fx.fetchBroker(), undefined)
|
|
1132
|
-
await
|
|
1133
|
+
await mocksDir.rm(fx.file)
|
|
1133
1134
|
})
|
|
1134
1135
|
|
|
1135
1136
|
test('register', async () => {
|
|
@@ -1175,27 +1176,27 @@ describe('Registering Mocks', () => {
|
|
|
1175
1176
|
const fx0 = new FixtureExternal('reg0/runtime0.GET.200.txt')
|
|
1176
1177
|
let version
|
|
1177
1178
|
before(async () => {
|
|
1178
|
-
await
|
|
1179
|
-
await
|
|
1179
|
+
await mocksDir.mkdir('reg0')
|
|
1180
|
+
await mocksDir.write(fx0.file, fx0.body)
|
|
1180
1181
|
version = await resolveOnNextSyncVersion(-1)
|
|
1181
1182
|
})
|
|
1182
1183
|
|
|
1183
1184
|
const fx = new FixtureExternal('runtime1.GET.200.txt')
|
|
1184
1185
|
test('responds when a file is added', async () => {
|
|
1185
1186
|
const prom = resolveOnNextSyncVersion(version)
|
|
1186
|
-
await
|
|
1187
|
+
await mocksDir.write(fx.file, fx.body)
|
|
1187
1188
|
equal(await prom, version + 1)
|
|
1188
1189
|
})
|
|
1189
1190
|
|
|
1190
1191
|
test('responds when a file is deleted', async () => {
|
|
1191
1192
|
const prom = resolveOnNextSyncVersion(version + 1)
|
|
1192
|
-
await
|
|
1193
|
+
await mocksDir.rm(fx.file)
|
|
1193
1194
|
equal(await prom, version + 2)
|
|
1194
1195
|
})
|
|
1195
1196
|
|
|
1196
1197
|
test('responds when dir is renamed', async () => {
|
|
1197
1198
|
const prom = resolveOnNextSyncVersion(version + 2)
|
|
1198
|
-
await
|
|
1199
|
+
await mocksDir.rename('reg0', 'reg1')
|
|
1199
1200
|
equal(await prom, version + 3)
|
|
1200
1201
|
|
|
1201
1202
|
const s = await fetchState()
|
|
@@ -1208,7 +1209,7 @@ describe('Registering Mocks', () => {
|
|
|
1208
1209
|
await fx.writeExternally()
|
|
1209
1210
|
config.watcherDebounceMs = 100 // Because on macOS rmdir triggers a few events
|
|
1210
1211
|
const nextVerPromise = resolveOnNextSyncVersion()
|
|
1211
|
-
await
|
|
1212
|
+
await mocksDir.rmdir('api/bulk-delete')
|
|
1212
1213
|
await nextVerPromise
|
|
1213
1214
|
equal(await fx.fetchBroker(), undefined)
|
|
1214
1215
|
await sleep(50) // Only for Docker, not sure why we need to delay the server teardown
|
|
@@ -1220,24 +1221,37 @@ describe('Registering Mocks', () => {
|
|
|
1220
1221
|
* This is for listening to real-time updates. It responds when a new mock is added, deleted, or renamed. */
|
|
1221
1222
|
async function resolveOnNextSyncVersion(currSyncVer = undefined) {
|
|
1222
1223
|
let skipFirst = currSyncVer === undefined
|
|
1223
|
-
const
|
|
1224
|
-
|
|
1224
|
+
const reader = (await api.getSyncVersion())
|
|
1225
|
+
.body.pipeThrough(new TextDecoderStream())
|
|
1226
|
+
.getReader()
|
|
1225
1227
|
let buffer = ''
|
|
1226
1228
|
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1229
|
+
try {
|
|
1230
|
+
while (true) {
|
|
1231
|
+
try {
|
|
1232
|
+
const { done, value } = await reader.read()
|
|
1233
|
+
if (done) break
|
|
1234
|
+
buffer += value
|
|
1235
|
+
}
|
|
1236
|
+
catch {
|
|
1237
|
+
break
|
|
1238
|
+
}
|
|
1239
|
+
const parts = buffer.split('\n\n')
|
|
1240
|
+
buffer = parts.pop() || ''
|
|
1241
|
+
|
|
1242
|
+
for (const event of parts)
|
|
1243
|
+
for (const line of event.split(/\r?\n/))
|
|
1244
|
+
if (line.startsWith('data:')) {
|
|
1245
|
+
const v = Number(line.slice(5).trim())
|
|
1246
|
+
if (skipFirst || v === currSyncVer)
|
|
1247
|
+
skipFirst = false
|
|
1248
|
+
else
|
|
1249
|
+
return v
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
finally {
|
|
1254
|
+
reader.cancel().catch(() => {})
|
|
1241
1255
|
}
|
|
1242
1256
|
}
|
|
1243
1257
|
|
package/src/server/ProxyRelay.js
CHANGED
|
@@ -4,12 +4,13 @@ import { randomUUID } from 'node:crypto'
|
|
|
4
4
|
import { extFor } from './utils/mime.js'
|
|
5
5
|
import { write, isFile, resolveIn } from './utils/fs.js'
|
|
6
6
|
import { BodyReaderError } from './utils/HttpIncomingMessage.js'
|
|
7
|
-
|
|
8
|
-
import { config } from './config.js'
|
|
9
7
|
import { logger } from './utils/logger.js'
|
|
8
|
+
|
|
10
9
|
import { makeMockFilename } from '../client/Filename.js'
|
|
11
10
|
import { EXT_EMPTY, EXT_UNKNOWN_MIME } from '../client/ApiConstants.js'
|
|
12
11
|
|
|
12
|
+
import { config } from './stores/config.js'
|
|
13
|
+
|
|
13
14
|
|
|
14
15
|
export async function proxy(req, response, delay) {
|
|
15
16
|
let proxyResponse
|
package/src/server/UrlParsers.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { relative } from 'node:path'
|
|
2
|
-
import { config } from './config.js'
|
|
2
|
+
import { config } from './stores/config.js'
|
|
3
3
|
import { decode, removeQueryStringAndFragment } from './utils/HttpIncomingMessage.js'
|
|
4
4
|
import { parseFilename, removeTrailingSlash } from '../client/Filename.js'
|
|
5
5
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { test, describe } from 'node:test'
|
|
2
2
|
import { deepEqual, equal } from 'node:assert/strict'
|
|
3
3
|
import { parseSegments, parseQueryParams } from './UrlParsers.js'
|
|
4
|
-
import { config } from './config.js'
|
|
4
|
+
import { config } from './stores/config.js'
|
|
5
5
|
|
|
6
6
|
test('parseQueryParams', () => {
|
|
7
7
|
const searchParams = parseQueryParams('/api/foo?limit=123')
|