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 CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "mockaton",
3
3
  "description": "HTTP Mock Server",
4
4
  "type": "module",
5
- "version": "12.7.1",
5
+ "version": "12.7.2",
6
6
  "exports": {
7
7
  ".": {
8
8
  "import": "./index.js",
@@ -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
  }
@@ -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
@@ -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 payloadViewerTitleRef = {}
11
- const payloadViewerCodeRef = {}
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: payloadViewerTitleRef },
18
+ r('h2', { ref: titleRef },
19
19
  !store.hasChosenLink && t`Preview`)),
20
20
 
21
21
  r('pre', null,
22
- r('code', { ref: payloadViewerCodeRef },
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
- payloadViewerTitleRef.elem.replaceChildren(t`Fetching…`)
70
- payloadViewerCodeRef.elem.replaceChildren(PayloadViewerProgressBar())
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
- payloadViewerCodeRef.elem.replaceChildren()
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
- payloadViewerTitleRef.elem.replaceChildren(proxied
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
- payloadViewerCodeRef.elem.replaceChildren(r('img', {
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
- payloadViewerCodeRef.elem.replaceChildren(SyntaxJSON(body))
105
+ codeRef.elem.replaceChildren(SyntaxJSON(body))
106
106
  else if (isXML(mime))
107
- payloadViewerCodeRef.elem.replaceChildren(SyntaxXML(body))
107
+ codeRef.elem.replaceChildren(SyntaxXML(body))
108
108
  else
109
- payloadViewerCodeRef.elem.textContent = body
109
+ codeRef.elem.textContent = body
110
110
  }
111
111
  }
112
112
 
@@ -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 = 'text_req_body'
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('.txt'),
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 r = await request(`/non-existing-mock/${randomUUID()}`, { method: 'POST' })
391
- equal(r.status, 423)
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
- const savedBody = await readFromMocksDir('non-existing-mock/[id].POST.423.txt')
397
- equal(savedBody, BODY_PAYLOAD)
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
- async function sleep(ms = 100) {
1214
- await new Promise(resolve => setTimeout(resolve, ms))
1233
+ function sleep(ms = 100) {
1234
+ return new Promise(resolve => setTimeout(resolve, ms))
1215
1235
  }
1216
1236
 
1217
1237
 
@@ -29,29 +29,40 @@ export async function proxy(req, response, delay) {
29
29
  return
30
30
  }
31
31
 
32
- const headers = Object.fromEntries(proxyResponse.headers)
33
- headers['set-cookie'] = proxyResponse.headers.getSetCookie() // parses multiple into an array
34
- response.writeHead(proxyResponse.status, headers)
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
- let filename = makeMockFilename(req.url, req.method, proxyResponse.status, ext)
41
- if (isFile(join(config.mocksDir, filename))) // TESTME
42
- filename = makeMockFilename(req.url + `(${randomUUID()})`, req.method, proxyResponse.status, ext)
43
-
44
- let data = body
45
- if (config.formatCollectedJSON && ext === 'json') // TESTME
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
- write(join(config.mocksDir, filename), data)
48
+ body = JSON.stringify(JSON.parse(body), null, ' ')
52
49
  }
53
50
  catch (err) {
54
- logger.warn('Write access denied', err)
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
+
@@ -113,7 +113,7 @@ export function sseClientSyncVersion(req, response) {
113
113
  const keepAlive = setInterval(() => {
114
114
  response.write(': ping\n\n')
115
115
  }, 10_000)
116
-
116
+
117
117
  req.on('close', cleanup)
118
118
  req.on('error', cleanup)
119
119
  function cleanup() {
@@ -61,7 +61,6 @@ const extToMime = {
61
61
  jsonld: 'application/ld+json',
62
62
  lz: 'application/x-lzip',
63
63
  m4a: 'audio/mp4',
64
- map: 'application/json',
65
64
  md: 'text/markdown',
66
65
  mid: 'audio/midi',
67
66
  midi: 'audio/midi',