mockaton 11.3.0 → 11.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/README.md CHANGED
@@ -7,7 +7,8 @@
7
7
  An HTTP mock server for simulating APIs with minimal setup — ideal
8
8
  for testing difficult to reproduce backend states.
9
9
 
10
- ## https://mockaton.com
10
+ ## [mockaton.com ↗](https://mockaton.com)
11
+ ## [Changelog ↗](https://mockaton.com/changelog)
11
12
 
12
13
  ## Overview
13
14
  With Mockaton, you don’t need to write code for wiring up your
@@ -64,7 +65,5 @@ Nonetheless, there’s a programmatic API, which is handy for
64
65
  setting up tests (see **Commander API** section below).
65
66
 
66
67
 
67
- ## [Changelog ↗](https://mockaton.com/changelog)
68
-
69
68
  ## License
70
69
  MIT
package/index.d.ts CHANGED
@@ -14,6 +14,7 @@ export interface Config {
14
14
  staticDir?: string
15
15
  ignore?: RegExp
16
16
  watcherEnabled?: boolean
17
+ watcherDebounceMs?: number
17
18
 
18
19
  host?: string,
19
20
  port?: number
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "mockaton",
3
3
  "description": "HTTP Mock Server",
4
4
  "type": "module",
5
- "version": "11.3.0",
5
+ "version": "11.3.1",
6
6
  "exports": {
7
7
  ".": {
8
8
  "import": "./index.js",
package/src/client/app.js CHANGED
@@ -22,7 +22,6 @@ const CSS = {
22
22
  Logo: null,
23
23
  MenuTrigger: null,
24
24
  Method: null,
25
- MockList: null,
26
25
  MockSelector: null,
27
26
  NotFoundToggler: null,
28
27
  PayloadViewer: null,
@@ -110,7 +109,7 @@ function Header() {
110
109
  r('header', null,
111
110
  r('a', {
112
111
  className: CSS.Logo,
113
- href: 'https://mockaton.com',
112
+ href: 'https://mockaton.com'
114
113
  },
115
114
  r('object', {
116
115
  data: 'logo.svg',
@@ -281,7 +280,7 @@ function SettingsMenu(id) {
281
280
  target: '_blank',
282
281
  rel: 'noopener noreferrer'
283
282
  }, t`Website`),
284
-
283
+
285
284
  r('a', {
286
285
  href: 'https://github.com/ericfortis/mockaton',
287
286
  target: '_blank',
@@ -77,3 +77,4 @@ function selectorFor(elem) {
77
77
  }
78
78
  return path.reverse().join('>')
79
79
  }
80
+
@@ -7,6 +7,7 @@ export const CSP = [
7
7
  ].join(';')
8
8
 
9
9
 
10
+ // language=html
10
11
  export const IndexHtml = hotReloadEnabled => `
11
12
  <!DOCTYPE html>
12
13
  <html lang="en-US">
@@ -39,7 +39,7 @@ const CUSTOM_EXT = 'custom_extension'
39
39
  const CUSTOM_MIME = 'custom_mime'
40
40
  const CUSTOM_HEADER_NAME = 'custom_header_name'
41
41
  const CUSTOM_HEADER_VAL = 'custom_header_val'
42
- const ALLOWED_ORIGIN = 'http://example.com'
42
+ const ALLOWED_ORIGIN = 'https://example.test'
43
43
 
44
44
  const server = await Mockaton({
45
45
  mocksDir,
@@ -51,7 +51,8 @@ const server = await Mockaton({
51
51
  logLevel: 'verbose',
52
52
  corsOrigins: [ALLOWED_ORIGIN],
53
53
  corsExposedHeaders: ['Content-Encoding'],
54
- watcherEnabled: false,
54
+ watcherEnabled: false, // But we enable it at run-time
55
+ watcherDebounceMs: 0
55
56
  })
56
57
  after(() => server?.close())
57
58
 
@@ -404,9 +405,9 @@ describe('Proxy Fallback', () => {
404
405
  })
405
406
 
406
407
  test('sets fallback', async () => {
407
- const r = await api.setProxyFallback('http://example.com')
408
+ const r = await api.setProxyFallback('https://example.test')
408
409
  equal(r.status, 200)
409
- equal((await fetchState()).proxyFallback, 'http://example.com')
410
+ equal((await fetchState()).proxyFallback, 'https://example.test')
410
411
  })
411
412
 
412
413
  test('unsets fallback', async () => {
@@ -461,7 +462,7 @@ describe('Proxy Fallback', () => {
461
462
  })
462
463
 
463
464
  test('200 when setting', async () => {
464
- await api.setProxyFallback('https://example.com')
465
+ await api.setProxyFallback('https://example.test')
465
466
  const r0 = await api.setRouteIsProxied(fx.method, fx.urlMask, true)
466
467
  equal(r0.status, 200)
467
468
  equal((await r0.json()).proxied, true)
@@ -480,7 +481,7 @@ describe('Proxy Fallback', () => {
480
481
  test('unsets auto500', async () => {
481
482
  const fx = new Fixture('unset-500-on-proxy.GET.200.txt')
482
483
  await fx.sync()
483
- await api.setProxyFallback('https://example.com')
484
+ await api.setProxyFallback('https://example.test')
484
485
 
485
486
  const r0 = await api.toggle500(fx.method, fx.urlMask)
486
487
  const b0 = await r0.json()
@@ -500,7 +501,7 @@ describe('Proxy Fallback', () => {
500
501
  test('updating selected mock resets proxied flag', async () => {
501
502
  const fx = new Fixture('select-resets-proxied.GET.200.txt')
502
503
  await fx.sync()
503
- await api.setProxyFallback('http://example.com')
504
+ await api.setProxyFallback('https://example.test')
504
505
  const r0 = await api.setRouteIsProxied(fx.method, fx.urlMask, true)
505
506
  equal((await r0.json()).proxied, true)
506
507
 
@@ -789,7 +790,7 @@ describe('500', () => {
789
790
  test('toggling ON 500 unsets `proxied` flag', async () => {
790
791
  const fx = new Fixture('proxied-to-500.GET.200.txt')
791
792
  await fx.sync()
792
- await api.setProxyFallback('http://example.com')
793
+ await api.setProxyFallback('https://example.test')
793
794
  await api.setRouteIsProxied(fx.method, fx.urlMask, true)
794
795
  await api.toggle500(fx.method, fx.urlMask)
795
796
  equal((await fx.fetchBroker()).proxied, false)
@@ -21,7 +21,6 @@ import * as mockBrokerCollection from './mockBrokersCollection.js'
21
21
  * and also renames, which are two events (delete + add).
22
22
  */
23
23
  const uiSyncVersion = new class extends EventEmitter {
24
- delay = Number(process.env.MOCKATON_WATCHER_DEBOUNCE_MS ?? 80)
25
24
  version = 0
26
25
 
27
26
  increment = /** @type {function} */ this.#debounce(() => {
@@ -40,7 +39,7 @@ const uiSyncVersion = new class extends EventEmitter {
40
39
  let timer
41
40
  return () => {
42
41
  clearTimeout(timer)
43
- timer = setTimeout(fn, this.delay)
42
+ timer = setTimeout(fn, config.watcherDebounceMs)
44
43
  }
45
44
  }
46
45
  }
@@ -1,11 +1,10 @@
1
1
  import { join } from 'node:path'
2
2
  import { EventEmitter } from 'node:events'
3
3
  import { watch, readdirSync } from 'node:fs'
4
+ import { config } from './config.js'
4
5
  import { LONG_POLL_SERVER_TIMEOUT } from './ApiConstants.js'
5
6
 
6
7
 
7
- const DEV = process.env.NODE_ENV === 'development'
8
-
9
8
  export const CLIENT_DIR = join(import.meta.dirname, '../client')
10
9
  export const DASHBOARD_ASSETS = readdirSync(CLIENT_DIR)
11
10
 
@@ -28,7 +27,7 @@ export function watchDevSPA() {
28
27
 
29
28
  /** Realtime notify Dev UI changes */
30
29
  export function longPollDevClientHotReload(req, response) {
31
- if (!DEV) {
30
+ if (!config.hotReload) {
32
31
  response.notFound()
33
32
  return
34
33
  }
@@ -21,6 +21,7 @@ const schema = {
21
21
  staticDir: [resolve('mockaton-static-mocks'), optional(isDirectory)],
22
22
  ignore: [/(\.DS_Store|~)$/, is(RegExp)],
23
23
  watcherEnabled: [true, is(Boolean)],
24
+ watcherDebounceMs: [80, ms => Number.isInteger(ms) && ms >= 0],
24
25
 
25
26
  host: ['127.0.0.1', is(String)],
26
27
  port: [0, port => Number.isInteger(port) && port >= 0 && port < 2 ** 16], // 0 means auto-assigned
@@ -73,9 +74,6 @@ export const ConfigValidator = Object.freeze(validators)
73
74
 
74
75
  /** @param {Partial<Config>} opts */
75
76
  export function setup(opts) {
76
- if (process.env.NODE_ENV !== 'development')
77
- opts.hotReload = false
78
-
79
77
  if (opts.mocksDir)
80
78
  opts.mocksDir = resolve(opts.mocksDir)
81
79
 
@@ -9,9 +9,9 @@ function headerIs(response, header, value) {
9
9
  equal(response.headers.get(header), value)
10
10
  }
11
11
 
12
- const FooDotCom = 'http://foo.com'
13
- const AllowedDotCom = 'http://allowed.com'
14
- const NotAllowedDotCom = 'http://not-allowed.com'
12
+ const FooDotTest = 'https://foo.test'
13
+ const AllowedDotTest = 'https://allowed.test'
14
+ const NotAllowedDotTest = 'https://not-allowed.test'
15
15
 
16
16
  await describe('CORS', async () => {
17
17
  let corsConfig = {}
@@ -87,7 +87,7 @@ await describe('CORS', async () => {
87
87
  corsMethods: ['GET']
88
88
  }
89
89
  const p = await preflight({
90
- [CH.Origin]: FooDotCom,
90
+ [CH.Origin]: FooDotTest,
91
91
  [CH.AcRequestMethod]: 'GET'
92
92
  })
93
93
  headerIs(p, CH.AcAllowOrigin, null)
@@ -99,11 +99,11 @@ await describe('CORS', async () => {
99
99
 
100
100
  await test('not in allowed origins', async () => {
101
101
  corsConfig = {
102
- corsOrigins: [AllowedDotCom],
102
+ corsOrigins: [AllowedDotTest],
103
103
  corsMethods: ['GET']
104
104
  }
105
105
  const p = await preflight({
106
- [CH.Origin]: NotAllowedDotCom,
106
+ [CH.Origin]: NotAllowedDotTest,
107
107
  [CH.AcRequestMethod]: 'GET'
108
108
  })
109
109
  headerIs(p, CH.AcAllowOrigin, null)
@@ -114,14 +114,14 @@ await describe('CORS', async () => {
114
114
 
115
115
  await test('origin and method match', async () => {
116
116
  corsConfig = {
117
- corsOrigins: [AllowedDotCom],
117
+ corsOrigins: [AllowedDotTest],
118
118
  corsMethods: ['GET']
119
119
  }
120
120
  const p = await preflight({
121
- [CH.Origin]: AllowedDotCom,
121
+ [CH.Origin]: AllowedDotTest,
122
122
  [CH.AcRequestMethod]: 'GET'
123
123
  })
124
- headerIs(p, CH.AcAllowOrigin, AllowedDotCom)
124
+ headerIs(p, CH.AcAllowOrigin, AllowedDotTest)
125
125
  headerIs(p, CH.AcAllowMethods, 'GET')
126
126
  headerIs(p, CH.AcAllowCredentials, null)
127
127
  headerIs(p, CH.AcAllowHeaders, null)
@@ -129,14 +129,14 @@ await describe('CORS', async () => {
129
129
 
130
130
  await test('origin matches from multiple', async () => {
131
131
  corsConfig = {
132
- corsOrigins: [AllowedDotCom, FooDotCom],
132
+ corsOrigins: [AllowedDotTest, FooDotTest],
133
133
  corsMethods: ['GET']
134
134
  }
135
135
  const p = await preflight({
136
- [CH.Origin]: AllowedDotCom,
136
+ [CH.Origin]: AllowedDotTest,
137
137
  [CH.AcRequestMethod]: 'GET'
138
138
  })
139
- headerIs(p, CH.AcAllowOrigin, AllowedDotCom)
139
+ headerIs(p, CH.AcAllowOrigin, AllowedDotTest)
140
140
  headerIs(p, CH.AcAllowMethods, 'GET')
141
141
  headerIs(p, CH.AcAllowCredentials, null)
142
142
  headerIs(p, CH.AcAllowHeaders, null)
@@ -148,10 +148,10 @@ await describe('CORS', async () => {
148
148
  corsMethods: ['GET']
149
149
  }
150
150
  const p = await preflight({
151
- [CH.Origin]: FooDotCom,
151
+ [CH.Origin]: FooDotTest,
152
152
  [CH.AcRequestMethod]: 'GET'
153
153
  })
154
- headerIs(p, CH.AcAllowOrigin, FooDotCom)
154
+ headerIs(p, CH.AcAllowOrigin, FooDotTest)
155
155
  headerIs(p, CH.AcAllowMethods, 'GET')
156
156
  headerIs(p, CH.AcAllowCredentials, null)
157
157
  headerIs(p, CH.AcAllowHeaders, null)
@@ -164,10 +164,10 @@ await describe('CORS', async () => {
164
164
  corsCredentials: true
165
165
  }
166
166
  const p = await preflight({
167
- [CH.Origin]: FooDotCom,
167
+ [CH.Origin]: FooDotTest,
168
168
  [CH.AcRequestMethod]: 'GET'
169
169
  })
170
- headerIs(p, CH.AcAllowOrigin, FooDotCom)
170
+ headerIs(p, CH.AcAllowOrigin, FooDotTest)
171
171
  headerIs(p, CH.AcAllowMethods, 'GET')
172
172
  headerIs(p, CH.AcAllowCredentials, 'true')
173
173
  headerIs(p, CH.AcAllowHeaders, null)
@@ -181,10 +181,10 @@ await describe('CORS', async () => {
181
181
  corsHeaders: ['content-type', 'my-header']
182
182
  }
183
183
  const p = await preflight({
184
- [CH.Origin]: FooDotCom,
184
+ [CH.Origin]: FooDotTest,
185
185
  [CH.AcRequestMethod]: 'GET'
186
186
  })
187
- headerIs(p, CH.AcAllowOrigin, FooDotCom)
187
+ headerIs(p, CH.AcAllowOrigin, FooDotTest)
188
188
  headerIs(p, CH.AcAllowMethods, 'GET')
189
189
  headerIs(p, CH.AcAllowCredentials, 'true')
190
190
  headerIs(p, CH.AcAllowHeaders, 'content-type,my-header')
@@ -198,7 +198,7 @@ await describe('CORS', async () => {
198
198
  corsMethods: ['GET']
199
199
  }
200
200
  const p = await request({
201
- [CH.Origin]: NotAllowedDotCom
201
+ [CH.Origin]: NotAllowedDotTest
202
202
  })
203
203
  equal(p.status, 200)
204
204
  headerIs(p, CH.AcAllowOrigin, null)
@@ -208,16 +208,16 @@ await describe('CORS', async () => {
208
208
 
209
209
  await test('origin allowed', async () => {
210
210
  corsConfig = {
211
- corsOrigins: [AllowedDotCom],
211
+ corsOrigins: [AllowedDotTest],
212
212
  corsMethods: ['GET'],
213
213
  corsCredentials: true,
214
214
  corsExposedHeaders: ['x-h1', 'x-h2']
215
215
  }
216
216
  const p = await request({
217
- [CH.Origin]: AllowedDotCom
217
+ [CH.Origin]: AllowedDotTest
218
218
  })
219
219
  equal(p.status, 200)
220
- headerIs(p, CH.AcAllowOrigin, AllowedDotCom)
220
+ headerIs(p, CH.AcAllowOrigin, AllowedDotTest)
221
221
  headerIs(p, CH.AcAllowCredentials, 'true')
222
222
  headerIs(p, CH.AcExposeHeaders, 'x-h1,x-h2')
223
223
  })
@@ -9,13 +9,8 @@ export function jwtCookie(cookieName, payload, path = '/') {
9
9
  function jwt(payload) {
10
10
  return [
11
11
  'Header_Not_In_Use',
12
- toBase64Url(payload),
12
+ Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url'),
13
13
  'Signature_Not_In_Use'
14
14
  ].join('.')
15
15
  }
16
16
 
17
- function toBase64Url(obj) {
18
- return btoa(JSON.stringify(obj))
19
- .replace('+', '-')
20
- .replace('/', '_')
21
- }
@@ -11,9 +11,13 @@ import { UNKNOWN_MIME_EXT } from '../ApiConstants.js'
11
11
  const extToMime = {
12
12
  '3g2': 'video/3gpp2',
13
13
  '3gp': 'video/3gpp',
14
+ '3mf': 'model/3mf',
14
15
  '7z': 'application/x-7z-compressed',
15
16
  aac: 'audio/aac',
16
17
  abw: 'application/x-abiword',
18
+ aif: 'audio/aiff',
19
+ aifc: 'audio/aiff',
20
+ aiff: 'audio/aiff',
17
21
  apng: 'image/apng',
18
22
  arc: 'application/x-freearc',
19
23
  avi: 'video/x-msvideo',
@@ -28,12 +32,22 @@ const extToMime = {
28
32
  csh: 'application/x-csh',
29
33
  css: 'text/css',
30
34
  csv: 'text/csv',
35
+ dae: 'model/vnd.collada+xml',
31
36
  doc: 'application/msword',
32
37
  docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
38
+ drc: 'model/vnd.draco',
39
+ eml: 'message/rfc822',
33
40
  eot: 'application/vnd.ms-fontobject',
34
41
  epub: 'application/epub+zip',
42
+ exe: 'application/vnd.microsoft.portable-executable',
43
+ fbx: 'application/octet-stream',
44
+ flac: 'audio/flac',
35
45
  gif: 'image/gif',
46
+ glb: 'model/gltf-binary',
47
+ gltf: 'model/gltf+json',
36
48
  gz: 'application/gzip',
49
+ heic: 'image/heic',
50
+ heif: 'image/heif',
37
51
  htm: 'text/html',
38
52
  html: 'text/html',
39
53
  ico: 'image/vnd.microsoft.icon',
@@ -44,13 +58,21 @@ const extToMime = {
44
58
  js: 'application/javascript',
45
59
  json: 'application/json',
46
60
  jsonld: 'application/ld+json',
61
+ lz: 'application/x-lzip',
62
+ m4a: 'audio/mp4',
63
+ map: 'application/json',
64
+ md: 'text/markdown',
47
65
  mid: 'audio/midi',
48
66
  midi: 'audio/midi',
49
67
  mjs: 'text/javascript',
68
+ mkv: 'video/x-matroska',
69
+ mov: 'video/quicktime',
50
70
  mp3: 'audio/mpeg',
51
71
  mp4: 'video/mp4',
52
72
  mpeg: 'video/mpeg',
53
73
  mpkg: 'application/vnd.apple.installer+xml',
74
+ mtl: 'text/plain',
75
+ obj: 'text/plain',
54
76
  odp: 'application/vnd.oasis.opendocument.presentation',
55
77
  ods: 'application/vnd.oasis.opendocument.spreadsheet',
56
78
  odt: 'application/vnd.oasis.opendocument.text',
@@ -61,19 +83,24 @@ const extToMime = {
61
83
  otf: 'font/otf',
62
84
  pdf: 'application/pdf',
63
85
  php: 'application/x-httpd-php',
86
+ ply: 'application/octet-stream',
64
87
  png: 'image/png',
65
88
  ppt: 'application/vnd.ms-powerpoint',
66
89
  pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
67
90
  rar: 'application/vnd.rar',
68
91
  rtf: 'application/rtf',
69
92
  sh: 'application/x-sh',
93
+ stl: 'model/stl',
70
94
  svg: 'image/svg+xml',
71
95
  tar: 'application/x-tar',
72
96
  tif: 'image/tiff',
73
97
  ts: 'video/mp2t',
74
98
  ttf: 'font/ttf',
75
99
  txt: 'text/plain',
100
+ usd: 'model/vnd.usd',
101
+ usdz: 'model/vnd.usdz+zip',
76
102
  vsd: 'application/vnd.visio',
103
+ wasm: 'application/wasm',
77
104
  wav: 'audio/wav',
78
105
  weba: 'audio/webm',
79
106
  webm: 'video/webm',
@@ -85,9 +112,11 @@ const extToMime = {
85
112
  xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
86
113
  xml: 'application/xml',
87
114
  xul: 'application/vnd.mozilla.xul+xml',
115
+ xz: 'application/x-xz',
88
116
  yaml: 'application/yaml',
89
117
  yml: 'application/yaml',
90
- zip: 'application/zip'
118
+ zip: 'application/zip',
119
+ zst: 'application/zstd'
91
120
  }
92
121
 
93
122
  const mimeToExt = mapMimeToExt(extToMime)
@@ -105,8 +134,8 @@ export function mimeFor(filename) {
105
134
  }
106
135
  function extname(filename) {
107
136
  const i = filename.lastIndexOf('.')
108
- return i === -1
109
- ? ''
137
+ return i === -1
138
+ ? ''
110
139
  : filename.slice(i + 1)
111
140
  }
112
141