mockaton 12.7.1 → 12.7.2
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 +4 -4
- package/src/client/Filename.js +2 -2
- package/src/client/app-header.js +1 -1
- package/src/client/app-payload-viewer.js +12 -12
- package/src/client/app.css +3 -6
- package/src/server/Mockaton.test.js +34 -14
- package/src/server/ProxyRelay.js +26 -15
- package/src/server/Watcher.js +1 -1
- package/src/server/utils/mime.js +0 -1
package/package.json
CHANGED
|
@@ -57,11 +57,11 @@ export class Commander {
|
|
|
57
57
|
|
|
58
58
|
/** @returns {JsonPromise<State>} */
|
|
59
59
|
getState = () => fetch(this.addr + API.state)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
/**
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
63
|
* SSE - Streams an incremental version when a mock is added, deleted, or renamed
|
|
64
|
-
* @returns {Promise<Response>}
|
|
64
|
+
* @returns {Promise<Response>}
|
|
65
65
|
*/
|
|
66
66
|
getSyncVersion = () => fetch(this.addr + API.syncVersion)
|
|
67
67
|
}
|
package/src/client/Filename.js
CHANGED
|
@@ -61,9 +61,9 @@ function responseStatusIsValid(status) {
|
|
|
61
61
|
&& status <= 599
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
export function makeMockFilename(url, method, status, ext) {
|
|
64
|
+
export function makeMockFilename(url, method, status, ext, comment = '') {
|
|
65
65
|
const urlMask = replaceIds(removeTrailingSlash(url))
|
|
66
|
-
return [urlMask, method, status, ext].join('.')
|
|
66
|
+
return [urlMask + comment, method, status, ext].join('.')
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
const reUuidV4 = /([0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})/gi
|
package/src/client/app-header.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createElement as r, t, defineClassNames } from './dom-utils.js'
|
|
2
|
-
import { store } from './app-store.js'
|
|
3
2
|
import { Logo, HelpIcon } from './app-icons.js'
|
|
3
|
+
import { store } from './app-store.js'
|
|
4
4
|
|
|
5
5
|
import CSS from './app.css' with { type: 'css' }
|
|
6
6
|
defineClassNames(CSS)
|
|
@@ -7,19 +7,19 @@ import CSS from './app.css' with { type: 'css' }
|
|
|
7
7
|
defineClassNames(CSS)
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
const
|
|
11
|
-
const
|
|
10
|
+
const titleRef = {}
|
|
11
|
+
const codeRef = {}
|
|
12
12
|
|
|
13
13
|
export function PayloadViewer() {
|
|
14
14
|
return (
|
|
15
15
|
r('div', { className: CSS.PayloadViewer },
|
|
16
16
|
|
|
17
17
|
r('div', { className: CSS.SubToolbar },
|
|
18
|
-
r('h2', { ref:
|
|
18
|
+
r('h2', { ref: titleRef },
|
|
19
19
|
!store.hasChosenLink && t`Preview`)),
|
|
20
20
|
|
|
21
21
|
r('pre', null,
|
|
22
|
-
r('code', { ref:
|
|
22
|
+
r('code', { ref: codeRef },
|
|
23
23
|
!store.hasChosenLink && t`Click a link to preview it`))))
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -66,8 +66,8 @@ export async function previewMock() {
|
|
|
66
66
|
previewMock.controller = new AbortController
|
|
67
67
|
|
|
68
68
|
const spinnerTimer = setTimeout(() => {
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
titleRef.elem.replaceChildren(t`Fetching…`)
|
|
70
|
+
codeRef.elem.replaceChildren(PayloadViewerProgressBar())
|
|
71
71
|
}, SPINNER_DELAY)
|
|
72
72
|
|
|
73
73
|
try {
|
|
@@ -83,7 +83,7 @@ export async function previewMock() {
|
|
|
83
83
|
catch (error) {
|
|
84
84
|
clearTimeout(spinnerTimer)
|
|
85
85
|
store.onError(error)
|
|
86
|
-
|
|
86
|
+
codeRef.elem.replaceChildren()
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
@@ -91,22 +91,22 @@ export async function previewMock() {
|
|
|
91
91
|
async function updatePayloadViewer(proxied, file, response) {
|
|
92
92
|
const mime = response.headers.get('content-type') || ''
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
titleRef.elem.replaceChildren(proxied
|
|
95
95
|
? PayloadViewerTitleWhenProxied(response)
|
|
96
96
|
: PayloadViewerTitle(file, response.statusText))
|
|
97
97
|
|
|
98
98
|
if (mime.startsWith('image/')) // Naively assumes GET 200
|
|
99
|
-
|
|
99
|
+
codeRef.elem.replaceChildren(r('img', {
|
|
100
100
|
src: URL.createObjectURL(await response.blob())
|
|
101
101
|
}))
|
|
102
102
|
else {
|
|
103
103
|
const body = await response.text() || t`/* Empty Response Body */`
|
|
104
104
|
if (mime === 'application/json')
|
|
105
|
-
|
|
105
|
+
codeRef.elem.replaceChildren(SyntaxJSON(body))
|
|
106
106
|
else if (isXML(mime))
|
|
107
|
-
|
|
107
|
+
codeRef.elem.replaceChildren(SyntaxXML(body))
|
|
108
108
|
else
|
|
109
|
-
|
|
109
|
+
codeRef.elem.textContent = body
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
|
package/src/client/app.css
CHANGED
|
@@ -46,7 +46,7 @@ body {
|
|
|
46
46
|
font-family: inherit;
|
|
47
47
|
font-size: 100%;
|
|
48
48
|
scrollbar-width: thin;
|
|
49
|
-
corner-shape: squircle
|
|
49
|
+
corner-shape: squircle;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
a:focus-visible,
|
|
@@ -306,9 +306,6 @@ header {
|
|
|
306
306
|
}
|
|
307
307
|
|
|
308
308
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
309
|
main {
|
|
313
310
|
display: flex;
|
|
314
311
|
min-width: 0;
|
|
@@ -423,7 +420,7 @@ main {
|
|
|
423
420
|
text-align: left;
|
|
424
421
|
|
|
425
422
|
&:first-of-type {
|
|
426
|
-
border-top: 0
|
|
423
|
+
border-top: 0;
|
|
427
424
|
}
|
|
428
425
|
|
|
429
426
|
&.canProxy {
|
|
@@ -536,7 +533,7 @@ main {
|
|
|
536
533
|
&:focus-visible {
|
|
537
534
|
outline: 0;
|
|
538
535
|
& + .checkboxBody {
|
|
539
|
-
outline: 2px solid var(--colorAccent)
|
|
536
|
+
outline: 2px solid var(--colorAccent);
|
|
540
537
|
}
|
|
541
538
|
}
|
|
542
539
|
|
|
@@ -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 { writeFile, unlink, mkdir, readFile, rename } from 'node:fs/promises'
|
|
10
|
+
import { writeFile, unlink, mkdir, readFile, rename, readdir } from 'node:fs/promises'
|
|
11
11
|
|
|
12
12
|
import { mimeFor } from './utils/mime.js'
|
|
13
13
|
import { parseFilename } from '../client/Filename.js'
|
|
@@ -42,6 +42,8 @@ const serverAddr = await new Promise((resolve, reject) => {
|
|
|
42
42
|
after(() => proc.kill('SIGUSR2'))
|
|
43
43
|
|
|
44
44
|
|
|
45
|
+
const rmFromMocksDir = f => unlink(join(mocksDir, f))
|
|
46
|
+
const listFromMocksDir = d => readdir(join(mocksDir, d))
|
|
45
47
|
const readFromMocksDir = f => readFile(join(mocksDir, f), 'utf8')
|
|
46
48
|
|
|
47
49
|
const makeDirInMocks = dir => mkdir(join(mocksDir, dir), { recursive: true })
|
|
@@ -369,15 +371,17 @@ describe('Proxy Fallback', () => {
|
|
|
369
371
|
describe('Fallback', () => {
|
|
370
372
|
let fallbackServer
|
|
371
373
|
const CUSTOM_COOKIES = ['cookieX=x', 'cookieY=y']
|
|
372
|
-
const BODY_PAYLOAD = '
|
|
374
|
+
const BODY_PAYLOAD = { a: 'b' }
|
|
375
|
+
const expectedBody = JSON.stringify(BODY_PAYLOAD, null, ' ') // config.formatCollectedJSON=true
|
|
376
|
+
|
|
373
377
|
before(async () => {
|
|
374
378
|
fallbackServer = createServer(async (req, response) => {
|
|
375
379
|
response.writeHead(423, {
|
|
376
380
|
'custom_header': 'my_custom_header',
|
|
377
|
-
'content-type': mimeFor('.
|
|
381
|
+
'content-type': mimeFor('.json'),
|
|
378
382
|
'set-cookie': CUSTOM_COOKIES
|
|
379
383
|
})
|
|
380
|
-
response.end(BODY_PAYLOAD)
|
|
384
|
+
response.end(JSON.stringify(BODY_PAYLOAD))
|
|
381
385
|
})
|
|
382
386
|
await promisify(fallbackServer.listen).bind(fallbackServer, 0, '127.0.0.1')()
|
|
383
387
|
await api.setProxyFallback(`http://localhost:${fallbackServer.address().port}`)
|
|
@@ -386,15 +390,31 @@ describe('Proxy Fallback', () => {
|
|
|
386
390
|
|
|
387
391
|
after(() => fallbackServer.close())
|
|
388
392
|
|
|
389
|
-
test('Relays to fallback server and saves the mock', async () => {
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
equal(r.headers.get('custom_header'), 'my_custom_header')
|
|
393
|
-
equal(r.headers.get('set-cookie'), CUSTOM_COOKIES.join(', '))
|
|
394
|
-
equal(await r.text(), BODY_PAYLOAD)
|
|
393
|
+
test('Relays to fallback server and saves the mock (we req twice, so the second one gets a unique comment)', async () => {
|
|
394
|
+
const r1 = await request(`/non-existing-mock/${randomUUID()}`, { method: 'POST' })
|
|
395
|
+
const r2 = await request(`/non-existing-mock/${randomUUID()}`, { method: 'POST' })
|
|
395
396
|
|
|
396
|
-
|
|
397
|
-
equal(
|
|
397
|
+
equal(r1.status, 423)
|
|
398
|
+
equal(r2.status, 423)
|
|
399
|
+
|
|
400
|
+
equal(r1.headers.get('custom_header'), 'my_custom_header')
|
|
401
|
+
equal(r2.headers.get('custom_header'), 'my_custom_header')
|
|
402
|
+
|
|
403
|
+
equal(r1.headers.get('set-cookie'), CUSTOM_COOKIES.join(', '))
|
|
404
|
+
equal(r2.headers.get('set-cookie'), CUSTOM_COOKIES.join(', '))
|
|
405
|
+
|
|
406
|
+
deepEqual(await r2.json(), BODY_PAYLOAD)
|
|
407
|
+
deepEqual(await r1.json(), BODY_PAYLOAD)
|
|
408
|
+
|
|
409
|
+
const savedMocks = await listFromMocksDir('non-existing-mock')
|
|
410
|
+
equal(savedMocks.length, 2)
|
|
411
|
+
|
|
412
|
+
equal(await readFromMocksDir('non-existing-mock/[id].POST.423.json'), expectedBody)
|
|
413
|
+
for (const m of savedMocks) {
|
|
414
|
+
const f = join('non-existing-mock', m)
|
|
415
|
+
equal(await readFromMocksDir(f), expectedBody)
|
|
416
|
+
await rmFromMocksDir(f)
|
|
417
|
+
}
|
|
398
418
|
})
|
|
399
419
|
})
|
|
400
420
|
|
|
@@ -1210,8 +1230,8 @@ describe('Registering Static Mocks', () => {
|
|
|
1210
1230
|
})
|
|
1211
1231
|
|
|
1212
1232
|
|
|
1213
|
-
|
|
1214
|
-
|
|
1233
|
+
function sleep(ms = 100) {
|
|
1234
|
+
return new Promise(resolve => setTimeout(resolve, ms))
|
|
1215
1235
|
}
|
|
1216
1236
|
|
|
1217
1237
|
|
package/src/server/ProxyRelay.js
CHANGED
|
@@ -29,29 +29,40 @@ export async function proxy(req, response, delay) {
|
|
|
29
29
|
return
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
response.writeHead(proxyResponse.status, {
|
|
33
|
+
...Object.fromEntries(proxyResponse.headers),
|
|
34
|
+
'Set-Cookie': proxyResponse.headers.getSetCookie() // parses multiple into an array
|
|
35
|
+
})
|
|
35
36
|
const body = await proxyResponse.text()
|
|
36
37
|
setTimeout(() => response.end(body), delay) // TESTME
|
|
37
38
|
|
|
38
39
|
if (config.collectProxied) {
|
|
39
40
|
const ext = extFor(proxyResponse.headers.get('content-type'))
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
data = JSON.stringify(JSON.parse(body), null, ' ')
|
|
48
|
-
}
|
|
49
|
-
catch {}
|
|
41
|
+
saveMockToDisk(req.url, req.method, proxyResponse.status, ext, body)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function saveMockToDisk(url, method, status, ext, body) {
|
|
46
|
+
if (config.formatCollectedJSON && ext === 'json')
|
|
50
47
|
try {
|
|
51
|
-
|
|
48
|
+
body = JSON.stringify(JSON.parse(body), null, ' ')
|
|
52
49
|
}
|
|
53
50
|
catch (err) {
|
|
54
|
-
logger.warn('
|
|
51
|
+
logger.warn('Invalid JSON response', err)
|
|
55
52
|
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
write(makeUniqueMockFilename(url, method, status, ext), body)
|
|
56
56
|
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
logger.warn('Write access denied', err)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function makeUniqueMockFilename(url, method, status, ext) {
|
|
63
|
+
let file = makeMockFilename(url, method, status, ext)
|
|
64
|
+
if (isFile(join(config.mocksDir, file)))
|
|
65
|
+
file = makeMockFilename(url, method, status, ext, `(${randomUUID()})`)
|
|
66
|
+
return join(config.mocksDir, file)
|
|
57
67
|
}
|
|
68
|
+
|
package/src/server/Watcher.js
CHANGED