mockaton 6.1.0 → 6.3.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/README.md CHANGED
@@ -48,9 +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
55
+ extraMimes?: object
54
56
  extraHeaders?: []
55
57
  }
56
58
  ```
@@ -115,8 +117,6 @@ The delay is globally configurable via `Config.delay = 1200` (milliseconds).
115
117
  ### Extension
116
118
  `.Method.ResponseStatusCode.FileExt`
117
119
 
118
- The **file extension** can be anything, but `.md` is reserved for documentation.
119
-
120
120
 
121
121
  ### Dynamic Parameters
122
122
  Anything within square brackets. For example:
@@ -129,7 +129,7 @@ Comments are anything within parentheses, including them.
129
129
  They are ignored for URL purposes, so they have no effect
130
130
  on the URL mask. For example, these two are for `/api/foo`
131
131
  <pre>
132
- api/foo<b>(my comment)</b>.GET.200.json<b>(bar)</b>
132
+ api/foo<b>(my comment)</b>.GET.200.json
133
133
  api/foo.GET.200.json
134
134
  </pre>
135
135
 
@@ -147,6 +147,9 @@ but since that’s part of the query string, it’s ignored anyway.
147
147
 
148
148
 
149
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
+
150
153
  **Option A.** Place it outside the directory:
151
154
  ```
152
155
  api/foo/
@@ -196,6 +199,13 @@ Config.extraHeaders = [
196
199
  ]
197
200
  ```
198
201
 
202
+ ## `Config.extraMimes`
203
+ ```js
204
+ Config.extraMimes = {
205
+ jpg: 'application/jpeg'
206
+ }
207
+ ```
208
+
199
209
  ---
200
210
 
201
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) {
package/index.d.ts CHANGED
@@ -5,11 +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
- extraHeaders?: [string, string][]
13
+ extraHeaders?: [string, string][]
14
+ extraMimes?: object
13
15
  }
14
16
 
15
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.1.0",
5
+ "version": "6.3.2",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
package/src/Config.js CHANGED
@@ -8,11 +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
- extraHeaders: []
16
+ extraHeaders: [],
17
+ extraMimes: {}
16
18
  }
17
19
 
18
20
  export function setup(options) {
@@ -22,11 +24,13 @@ export function setup(options) {
22
24
  staticDir: optional(isDirectory),
23
25
  host: is(String),
24
26
  port: port => Number.isInteger(port) && port >= 0 && port < 2 ** 16,
27
+ ignore: is(RegExp),
25
28
  delay: ms => Number.isInteger(ms) && ms > 0,
26
29
  cookies: is(Object),
27
30
  onReady: is(Function),
28
31
  proxyFallback: optional(URL.canParse),
29
- extraHeaders: Array.isArray
32
+ extraHeaders: Array.isArray,
33
+ extraMimes: is(Object)
30
34
  })
31
35
  }
32
36
 
package/src/MockBroker.js CHANGED
@@ -1,7 +1,5 @@
1
- import { join } from 'node:path'
2
1
  import { Route } from './Route.js'
3
2
  import { Config } from './Config.js'
4
- import { isDirectory } from './utils/fs.js'
5
3
  import { DEFAULT_500_COMMENT } from './ApiConstants.js'
6
4
 
7
5
 
@@ -12,13 +10,11 @@ export class MockBroker {
12
10
 
13
11
  constructor(file) {
14
12
  this.#route = new Route(file)
15
- this.method = this.#route.method
16
13
  this.mocks = []
17
14
  this.currentMock = {
18
15
  file: '',
19
16
  delay: 0
20
17
  }
21
-
22
18
  this.register(file)
23
19
  }
24
20
 
@@ -62,19 +58,11 @@ export class MockBroker {
62
58
  if (!this.#has500())
63
59
  this.#registerTemp500()
64
60
  }
65
-
66
61
  #has500() {
67
- return this.mocks.some(mock =>
68
- Route.parseFilename(mock).status === 500)
62
+ return this.mocks.some(mock => Route.parseFilename(mock).status === 500)
69
63
  }
70
-
71
64
  #registerTemp500() {
72
65
  const { urlMask, method } = Route.parseFilename(this.mocks[0])
73
- let mask = urlMask
74
- if (isDirectory(join(Config.mocksDir, urlMask)))
75
- mask = urlMask + '/'
76
- mask = mask.replace(/^\//, '') // remove initial slash
77
- const file = `${mask}${DEFAULT_500_COMMENT}.${method}.500.txt`
78
- this.register(file)
66
+ this.register(`${urlMask}${DEFAULT_500_COMMENT}.${method}.500.txt`)
79
67
  }
80
68
  }
@@ -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'
@@ -22,7 +22,7 @@ export async function dispatchMock(req, response) {
22
22
 
23
23
  try {
24
24
  const { file, status, delay } = broker
25
- console.log('\n', req.url, '→\n ', file)
25
+ console.log(req.url, ' ', file)
26
26
 
27
27
  let mockText
28
28
  if (file.endsWith('.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/Route.js CHANGED
@@ -14,8 +14,7 @@ export class Route {
14
14
  #urlRegex
15
15
 
16
16
  constructor(file) {
17
- const { urlMask, method } = Route.parseFilename(file)
18
- this.method = method
17
+ const { urlMask } = Route.parseFilename(file)
19
18
  this.#urlRegex = new RegExp('^' + disregardVariables(removeQueryStringAndFragment(urlMask)) + '/*$')
20
19
  }
21
20
 
@@ -1,8 +1,9 @@
1
+ import { Config } from './Config.js'
2
+
1
3
  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
2
4
  // m = {};
3
5
  // for (const row of tbody.children)
4
6
  // m[row.children[0].querySelector('code').innerText] = row.children[2].querySelector('code').innerText
5
-
6
7
  const mimes = {
7
8
  '3g2': 'video/3gpp2',
8
9
  '3gp': 'video/3gpp',
@@ -40,6 +41,7 @@ const mimes = {
40
41
  json: 'application/json',
41
42
  jsonld: 'application/ld+json',
42
43
  mid: 'audio/midi',
44
+ midi: 'audio/midi',
43
45
  mjs: 'text/javascript',
44
46
  mp3: 'audio/mpeg',
45
47
  mp4: 'video/mp4',
@@ -84,7 +86,7 @@ const mimes = {
84
86
 
85
87
  export function mimeFor(filename) {
86
88
  const ext = filename.replace(/.*\./, '').toLowerCase()
87
- const mime = mimes[ext] || ''
89
+ const mime = Config.extraMimes[ext] || mimes[ext] || ''
88
90
  if (!mime)
89
91
  console.info(`Missing MIME for ${filename}`)
90
92
  return mime
@@ -5,7 +5,6 @@ import { Route } from './Route.js'
5
5
  import { Config } from './Config.js'
6
6
  import { cookie } from './cookie.js'
7
7
  import { isFile } from './utils/fs.js'
8
- import { mimeFor } from './utils/mime.js'
9
8
  import { MockBroker } from './MockBroker.js'
10
9
 
11
10
 
@@ -26,7 +25,7 @@ export function init() {
26
25
  cookie.init(Config.cookies)
27
26
 
28
27
  const files = readDir(Config.mocksDir, { recursive: true })
29
- .filter(f => isFile(join(Config.mocksDir, f)) && mimeFor(f))
28
+ .filter(f => !Config.ignore.test(f) && isFile(join(Config.mocksDir, f)))
30
29
  .sort()
31
30
 
32
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