mockaton 6.0.0 → 6.3.0

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
@@ -48,10 +48,11 @@ interface Config {
48
48
  host?: string, // defaults to 'localhost'
49
49
  port?: number // defaults to 0, which means auto-assigned
50
50
  delay?: number // defaults to 1200 (ms)
51
+ ignore?: RegExp // defaults to /(.DS_Store|~)$/
51
52
  onReady?: (dashboardUrl: string) => void // defaults to trying to open macOS default browser. pass a noop to prevent opening the dashboard
52
53
  cookies?: object
53
54
  proxyFallback?: string // e.g. http://localhost:9999 Target for relaying routes without mocks
54
- allowedExt?: RegExp // /\.(json|js|txt)$/ Just for excluding temporary editor files (e.g. JetBrains appends a ~)
55
+ extraMimes?: object
55
56
  extraHeaders?: []
56
57
  }
57
58
  ```
@@ -116,10 +117,6 @@ The delay is globally configurable via `Config.delay = 1200` (milliseconds).
116
117
  ### Extension
117
118
  `.Method.ResponseStatusCode.FileExt`
118
119
 
119
- The **file extension** can be anything, but `.md` is reserved for documentation.
120
-
121
- The `Config.allowedExt` regex defaults to: `/\.(md|json|txt|js)$/`
122
-
123
120
 
124
121
  ### Dynamic Parameters
125
122
  Anything within square brackets. For example:
@@ -132,7 +129,7 @@ Comments are anything within parentheses, including them.
132
129
  They are ignored for URL purposes, so they have no effect
133
130
  on the URL mask. For example, these two are for `/api/foo`
134
131
  <pre>
135
- api/foo<b>(my comment)</b>.GET.200.json<b>(bar)</b>
132
+ api/foo<b>(my comment)</b>.GET.200.json
136
133
  api/foo.GET.200.json
137
134
  </pre>
138
135
 
@@ -150,6 +147,9 @@ but since that’s part of the query string, it’s ignored anyway.
150
147
 
151
148
 
152
149
  ### Default (index-like) route
150
+ For instance, let's say you have `api/foo/bar`, and
151
+ `api/foo`. For the latter you have two options:
152
+
153
153
  **Option A.** Place it outside the directory:
154
154
  ```
155
155
  api/foo/
@@ -199,6 +199,13 @@ Config.extraHeaders = [
199
199
  ]
200
200
  ```
201
201
 
202
+ ## `Config.extraMimes`
203
+ ```js
204
+ Config.extraMimes = {
205
+ jpg: 'application/jpeg'
206
+ }
207
+ ```
208
+
202
209
  ---
203
210
 
204
211
  ## API
package/Tests.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env -S node --experimental-default-type=module
2
2
 
3
3
  import { tmpdir } from 'node:os'
4
4
  import { dirname } from 'node:path'
@@ -9,13 +9,20 @@ import { equal, deepEqual, match } from 'node:assert/strict'
9
9
  import { writeFileSync, mkdtempSync, mkdirSync } from 'node:fs'
10
10
 
11
11
  import { Route } from './src/Route.js'
12
- import { mimeFor } from './src/utils/mime.js'
12
+ import { mimeFor } from './src/mime.js'
13
13
  import { Mockaton } from './src/Mockaton.js'
14
14
  import { API, DF, DEFAULT_500_COMMENT } from './src/ApiConstants.js'
15
15
 
16
16
 
17
17
  const tmpDir = mkdtempSync(tmpdir()) + '/'
18
18
  const staticTmpDir = mkdtempSync(tmpdir()) + '/'
19
+
20
+ const fixtureCustomMime = [
21
+ '/api/custom-mime',
22
+ 'api/custom-mime.GET.200.my_custom_extension',
23
+ 'Custom Extension and MIME'
24
+ ]
25
+
19
26
  const fixtures = [
20
27
  [
21
28
  '/api',
@@ -87,7 +94,8 @@ const fixtures = [
87
94
  '/api/company-e/1234/?limit=4',
88
95
  'api/company-e/[id]?limit=[limit].GET.200.json',
89
96
  'with pretty-param and query-params'
90
- ]
97
+ ],
98
+ fixtureCustomMime
91
99
  ]
92
100
  for (const [, file, body] of fixtures)
93
101
  write(file, file.endsWith('.json') ? JSON.stringify(body) : body)
@@ -95,6 +103,7 @@ for (const [, file, body] of fixtures)
95
103
  write('api/.GET.500.txt', 'keeps non-autogenerated 500')
96
104
  write('api/alternative(comment-2).GET.200.json', JSON.stringify({ comment: 2 }))
97
105
  write('api/my-route(comment-2).GET.200.json', JSON.stringify({ comment: 2 }))
106
+ write('api/ignored.GET.200.json~', '')
98
107
 
99
108
  // JavaScript to JSON
100
109
  write('/api/object.GET.200.js', 'export default { JSON_FROM_JS: true }')
@@ -111,7 +120,10 @@ const server = Mockaton({
111
120
  userA: 'CookieA',
112
121
  userB: 'CookieB'
113
122
  },
114
- extraHeaders: ['Server', 'MockatonTester']
123
+ extraHeaders: ['Server', 'MockatonTester'],
124
+ extraMimes: {
125
+ my_custom_extension: 'my_custom_mime'
126
+ }
115
127
  })
116
128
  server.on('listening', runTests)
117
129
 
@@ -161,6 +173,7 @@ async function runTests() {
161
173
  await testMockDispatching(url, file, body)
162
174
 
163
175
  await testMockDispatching('/api/object', 'api/object.GET.200.js', { JSON_FROM_JS: true }, mimeFor('.json'))
176
+ await testMockDispatching(...fixtureCustomMime, 'my_custom_mime')
164
177
  await testJsFunctionMocks()
165
178
 
166
179
  await testItUpdatesUserRole()
@@ -190,6 +203,10 @@ async function test404() {
190
203
  const res = await request('/api/non-existing-too', { method: 'DELETE' })
191
204
  equal(res.status, 404)
192
205
  })
206
+ await it('Ignores files ending in ~ by default, e.g. JetBrains temp files', async () => {
207
+ const res = await request('/api/ignored')
208
+ equal(res.status, 404)
209
+ })
193
210
  }
194
211
 
195
212
  async function testMockDispatching(url, file, expectedBody, forcedMime = void 0) {
@@ -354,7 +371,6 @@ async function testInvalidFilenamesAreIgnored() {
354
371
  const consoleErrorSpy = t.mock.method(console, 'error')
355
372
  consoleErrorSpy.mock.mockImplementation(() => {}) // so they don’t render in the test report
356
373
 
357
- // An extension is needed for testing because of `Config.allowedExt`
358
374
  write('api/_INVALID_FILENAME_CONVENTION_.json', '')
359
375
  write('api/bad-filename.GET._INVALID_STATUS_.json', '')
360
376
  write('api/bad-filename._INVALID_METHOD_.200.json', '')
package/index.d.ts CHANGED
@@ -5,12 +5,13 @@ interface Config {
5
5
  staticDir?: string
6
6
  host?: string,
7
7
  port?: number
8
+ ignore?: RegExp
8
9
  delay?: number
9
10
  onReady?: (address: string) => void
10
11
  cookies?: object
11
12
  proxyFallback?: string
12
- allowedExt?: RegExp
13
- extraHeaders?: [string, string][]
13
+ extraHeaders?: [string, string][]
14
+ extraMimes?: object
14
15
  }
15
16
 
16
17
  export function Mockaton(options: Config): Server
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "mockaton",
3
3
  "description": "A deterministic server-side for developing and testing frontend clients",
4
4
  "type": "module",
5
- "version": "6.0.0",
5
+ "version": "6.3.0",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
package/src/Config.js CHANGED
@@ -8,12 +8,13 @@ export const Config = {
8
8
  staticDir: '',
9
9
  host: '127.0.0.1',
10
10
  port: 0, // auto-assigned
11
+ ignore: /(\.DS_Store|~)$/,
11
12
  delay: 1200, // milliseconds
12
13
  cookies: {}, // defaults to the first kv
13
14
  onReady: openInBrowser,
14
15
  proxyFallback: '', // e.g. http://localhost:9999
15
- allowedExt: /\.(json|js|txt)$/, // Just for excluding temporary editor files (e.g. JetBrains appends a ~)
16
- extraHeaders: []
16
+ extraHeaders: [],
17
+ extraMimes: {}
17
18
  }
18
19
 
19
20
  export function setup(options) {
@@ -23,12 +24,13 @@ export function setup(options) {
23
24
  staticDir: optional(isDirectory),
24
25
  host: is(String),
25
26
  port: port => Number.isInteger(port) && port >= 0 && port < 2 ** 16,
27
+ ignore: is(RegExp),
26
28
  delay: ms => Number.isInteger(ms) && ms > 0,
27
29
  cookies: is(Object),
28
30
  onReady: is(Function),
29
31
  proxyFallback: optional(URL.canParse),
30
- allowedExt: is(RegExp),
31
- extraHeaders: Array.isArray
32
+ extraHeaders: Array.isArray,
33
+ extraMimes: is(Object)
32
34
  })
33
35
  }
34
36
 
package/src/MockBroker.js CHANGED
@@ -18,7 +18,6 @@ export class MockBroker {
18
18
  file: '',
19
19
  delay: 0
20
20
  }
21
-
22
21
  this.register(file)
23
22
  }
24
23
 
@@ -4,7 +4,7 @@ import { readFileSync } from 'node:fs'
4
4
  import { proxy } from './ProxyRelay.js'
5
5
  import { cookie } from './cookie.js'
6
6
  import { Config } from './Config.js'
7
- import { mimeFor } from './utils/mime.js'
7
+ import { mimeFor } from './mime.js'
8
8
  import * as mockBrokerCollection from './mockBrokersCollection.js'
9
9
  import { JsonBodyParserError } from './utils/http-request.js'
10
10
  import { sendInternalServerError, sendNotFound, sendBadRequest } from './utils/http-response.js'
@@ -59,7 +59,7 @@ async function jsMockText(file, req, response) {
59
59
  }
60
60
 
61
61
  function readMock(file) {
62
- return readFileSync(join(Config.mocksDir, file), 'utf8')
62
+ return readFileSync(join(Config.mocksDir, file))
63
63
  }
64
64
 
65
65
  async function importDefault(file) {
package/src/mime.js ADDED
@@ -0,0 +1,93 @@
1
+ import { Config } from './Config.js'
2
+
3
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
4
+ // m = {};
5
+ // for (const row of tbody.children)
6
+ // m[row.children[0].querySelector('code').innerText] = row.children[2].querySelector('code').innerText
7
+ const mimes = {
8
+ '3g2': 'video/3gpp2',
9
+ '3gp': 'video/3gpp',
10
+ '7z': 'application/x-7z-compressed',
11
+ aac: 'audio/aac',
12
+ abw: 'application/x-abiword',
13
+ apng: 'image/apng',
14
+ arc: 'application/x-freearc',
15
+ avi: 'video/x-msvideo',
16
+ avif: 'image/avif',
17
+ azw: 'application/vnd.amazon.ebook',
18
+ bin: 'application/octet-stream',
19
+ bmp: 'image/bmp',
20
+ bz2: 'application/x-bzip2',
21
+ bz: 'application/x-bzip',
22
+ cda: 'application/x-cdf',
23
+ cjs: 'text/javascript',
24
+ csh: 'application/x-csh',
25
+ css: 'text/css',
26
+ csv: 'text/csv',
27
+ doc: 'application/msword',
28
+ docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
29
+ eot: 'application/vnd.ms-fontobject',
30
+ epub: 'application/epub+zip',
31
+ gif: 'image/gif',
32
+ gz: 'application/gzip',
33
+ htm: 'text/html',
34
+ html: 'text/html',
35
+ ico: 'image/vnd.microsoft.icon',
36
+ ics: 'text/calendar',
37
+ jar: 'application/java-archive',
38
+ jpeg: 'image/jpeg',
39
+ jpg: 'image/jpeg',
40
+ js: 'text/javascript',
41
+ json: 'application/json',
42
+ jsonld: 'application/ld+json',
43
+ mid: 'audio/midi',
44
+ midi: 'audio/midi',
45
+ mjs: 'text/javascript',
46
+ mp3: 'audio/mpeg',
47
+ mp4: 'video/mp4',
48
+ mpeg: 'video/mpeg',
49
+ mpkg: 'application/vnd.apple.installer+xml',
50
+ odp: 'application/vnd.oasis.opendocument.presentation',
51
+ ods: 'application/vnd.oasis.opendocument.spreadsheet',
52
+ odt: 'application/vnd.oasis.opendocument.text',
53
+ oga: 'audio/ogg',
54
+ ogv: 'video/ogg',
55
+ ogx: 'application/ogg',
56
+ opus: 'audio/ogg',
57
+ otf: 'font/otf',
58
+ pdf: 'application/pdf',
59
+ php: 'application/x-httpd-php',
60
+ png: 'image/png',
61
+ ppt: 'application/vnd.ms-powerpoint',
62
+ pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
63
+ rar: 'application/vnd.rar',
64
+ rtf: 'application/rtf',
65
+ sh: 'application/x-sh',
66
+ svg: 'image/svg+xml',
67
+ tar: 'application/x-tar',
68
+ tif: 'image/tiff',
69
+ ts: 'video/mp2t',
70
+ ttf: 'font/ttf',
71
+ txt: 'text/plain',
72
+ vsd: 'application/vnd.visio',
73
+ wav: 'audio/wav',
74
+ weba: 'audio/webm',
75
+ webm: 'video/webm',
76
+ webp: 'image/webp',
77
+ woff2: 'font/woff2',
78
+ woff: 'font/woff',
79
+ xhtml: 'application/xhtml+xml',
80
+ xls: 'application/vnd.ms-excel',
81
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
82
+ xml: 'application/xml',
83
+ xul: 'application/vnd.mozilla.xul+xml',
84
+ zip: 'application/zip'
85
+ }
86
+
87
+ export function mimeFor(filename) {
88
+ const ext = filename.replace(/.*\./, '').toLowerCase()
89
+ const mime = Config.extraMimes[ext] || mimes[ext] || ''
90
+ if (!mime)
91
+ console.info(`Missing MIME for ${filename}`)
92
+ return mime
93
+ }
@@ -25,7 +25,7 @@ export function init() {
25
25
  cookie.init(Config.cookies)
26
26
 
27
27
  const files = readDir(Config.mocksDir, { recursive: true })
28
- .filter(f => Config.allowedExt.test(f) && isFile(join(Config.mocksDir, f)))
28
+ .filter(f => !Config.ignore.test(f) && isFile(join(Config.mocksDir, f)))
29
29
  .sort()
30
30
 
31
31
  for (const file of files) {
@@ -1,5 +1,5 @@
1
1
  import fs from 'node:fs'
2
- import { mimeFor } from './mime.js'
2
+ import { mimeFor } from '../mime.js'
3
3
  import { isFile, read } from './fs.js'
4
4
 
5
5
 
package/src/utils/mime.js DELETED
@@ -1,47 +0,0 @@
1
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
2
- const mimes = {
3
- aac: 'audio/acc',
4
- apng: 'image/apng',
5
- avif: 'image/avif',
6
- css: 'text/css',
7
- doc: 'application/msword',
8
- docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
9
- eot: 'application/vnd.ms-fontobject',
10
- epub: 'application/epub+zip',
11
- gif: 'image/gif',
12
- gz: 'application/gzip',
13
- htm: 'text/html',
14
- html: 'text/html',
15
- ico: 'image/vnd.microsoft.icon',
16
- ics: 'text/calendar',
17
- jpeg: 'image/jpeg',
18
- jpg: 'image/jpeg',
19
- js: 'application/javascript',
20
- json: 'application/json',
21
- jsonld: 'application/ld+json',
22
- md: 'text/markdown',
23
- mjs: 'text/javascript',
24
- mp3: 'audio/mpeg',
25
- mp4: 'video/mp4',
26
- oft: 'font/otf',
27
- pdf: 'application/pdf',
28
- png: 'image/png',
29
- ttf: 'font/ttf',
30
- txt: 'plain/text',
31
- wav: 'audio/wav',
32
- weba: 'audio/webm',
33
- webm: 'video/webm',
34
- webp: 'image/webp',
35
- woff2: 'font/woff2',
36
- woff: 'font/woff',
37
- xml: 'application/xml',
38
- zip: 'application/zip'
39
- }
40
-
41
- export function mimeFor(filename) {
42
- const ext = filename.replace(/.*\./, '')
43
- const mime = mimes[ext] || ''
44
- if (!mime)
45
- console.error(`Missing MIME for ${filename}`)
46
- return mime
47
- }