mockaton 6.3.9 → 6.3.11

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
@@ -1,4 +1,4 @@
1
- # Mockaton
1
+ # Mockaton
2
2
  _Mockaton_ is a mock server for developing and testing frontends.
3
3
 
4
4
  It scans a given directory for files following a specific
@@ -30,9 +30,10 @@ Create a `my-mockaton.js` file
30
30
  import { resolve } from 'node:path'
31
31
  import { Mockaton } from 'mockaton'
32
32
 
33
+
33
34
  Mockaton({
34
- mocksDir: resolve('my-mocks-dir'),
35
- port: 2345
35
+ mocksDir: resolve('my-mocks-dir'),
36
+ port: 2345
36
37
  })
37
38
  ```
38
39
 
@@ -43,21 +44,21 @@ node my-mockaton.js
43
44
  ## Config Options
44
45
  ```ts
45
46
  interface Config {
46
- mocksDir: string
47
- ignore?: RegExp // defaults to /(\.DS_Store|~)$/
48
-
49
- staticDir?: string
50
-
51
- host?: string, // defaults to 'localhost'
52
- port?: number // defaults to 0, which means auto-assigned
53
- proxyFallback?: string // e.g. http://localhost:9999 Target for relaying routes without mocks
54
-
55
- delay?: number // defaults to 1200 (ms)
56
- cookies?: { [label: string]: string }
57
- extraMimes?: { [fileExt: string]: string }
58
- extraHeaders?: []
59
-
60
- onReady?: (dashboardUrl: string) => void // defaults to trying to open macOS default browser. pass a noop to prevent opening the dashboard
47
+ mocksDir: string
48
+ ignore?: RegExp // defaults to /(\.DS_Store|~)$/
49
+
50
+ staticDir?: string
51
+
52
+ host?: string, // defaults to 'localhost'
53
+ port?: number // defaults to 0, which means auto-assigned
54
+ proxyFallback?: string // e.g. http://localhost:9999 Target for relaying routes without mocks
55
+
56
+ delay?: number // defaults to 1200 (ms)
57
+ cookies?: { [label: string]: string }
58
+ extraMimes?: { [fileExt: string]: string }
59
+ extraHeaders?: []
60
+
61
+ onReady?: (dashboardUrl: string) => void // defaults to trying to open macOS default browser. pass a noop to prevent opening the dashboard
61
62
  }
62
63
  ```
63
64
 
@@ -65,9 +66,9 @@ interface Config {
65
66
 
66
67
  ## Mock Variants
67
68
  Each route can have many mocks, which could either be:
68
- - Different response __status code__. For example, for testing error responses.
69
+ - Different response __status code__. For example, for testing error responses.
69
70
  - __Comment__ on the filename, which is anything within parentheses.
70
- - e.g. `api/user(my-comment).POST.201.json`
71
+ - e.g. `api/user(my-comment).POST.201.json`
71
72
 
72
73
  Those alternatives can be manually selected in the dashboard
73
74
  UI, or programmatically, for instance, for setting up tests.
@@ -80,7 +81,7 @@ An Object, Array, or String is sent as JSON.
80
81
  `api/foo.GET.200.js`
81
82
  ```js
82
83
  export default [
83
- { id: 0 }
84
+ { id: 0 }
84
85
  ]
85
86
  ```
86
87
 
@@ -94,10 +95,10 @@ database, or pull data from a backend. The `request` is of type
94
95
  `response` a [ServerResponse](https://nodejs.org/api/http.html#class-httpserverresponse).
95
96
  ```js
96
97
  export default function optionalName(request, response) {
97
- globalThis.myDatabase ??= { count: 0 }
98
- globalThis.myDatabase.count++
99
-
100
- return JSON.stringify({ a: 1 })
98
+ globalThis.myDatabase ??= { count: 0 }
99
+ globalThis.myDatabase.count++
100
+
101
+ return JSON.stringify({ a: 1 })
101
102
  }
102
103
  ```
103
104
 
@@ -107,7 +108,6 @@ export default function optionalName(request, response) {
107
108
  server for serving routes you don’t have mocks for.
108
109
 
109
110
 
110
-
111
111
  ## Delay 🕓
112
112
  The clock icon next to the mock selector is a checkbox for delaying a
113
113
  particular response. They are handy for testing spinners.
@@ -115,7 +115,6 @@ particular response. They are handy for testing spinners.
115
115
  The delay is globally configurable via `Config.delay = 1200` (milliseconds).
116
116
 
117
117
 
118
-
119
118
  ## File Name Convention
120
119
 
121
120
 
@@ -167,7 +166,6 @@ api/foo/.GET.200.json
167
166
  ```
168
167
 
169
168
 
170
-
171
169
  ## `Config.cookies`
172
170
  The selected cookie is sent in every response in the `Set-Cookie` header.
173
171
 
@@ -181,13 +179,14 @@ words, it’s useful if you only care about its payload.
181
179
  ```js
182
180
  import { jwtCookie } from 'mockaton'
183
181
 
182
+
184
183
  Config.cookies = {
185
- 'My Admin User': 'my-cookie=1;Path=/;SameSite=strict',
186
- 'My Normal User': 'my-cookie=0;Path=/;SameSite=strict',
187
- 'My JWT': jwtCookie('my-cookie', {
188
- email: 'john.doe@example.com',
189
- picture: 'https://cdn.auth0.com/avatars/jd.png'
190
- })
184
+ 'My Admin User': 'my-cookie=1;Path=/;SameSite=strict',
185
+ 'My Normal User': 'my-cookie=0;Path=/;SameSite=strict',
186
+ 'My JWT': jwtCookie('my-cookie', {
187
+ email: 'john.doe@example.com',
188
+ picture: 'https://cdn.auth0.com/avatars/jd.png'
189
+ })
191
190
  }
192
191
  ```
193
192
 
@@ -198,16 +197,16 @@ that it's an array and the header name goes in even indices.
198
197
 
199
198
  ```js
200
199
  Config.extraHeaders = [
201
- 'Server', 'Mockaton',
202
- 'Set-Cookie', 'foo=FOO;Path=/;SameSite=strict',
203
- 'Set-Cookie', 'bar=BAR;Path=/;SameSite=strict'
200
+ 'Server', 'Mockaton',
201
+ 'Set-Cookie', 'foo=FOO;Path=/;SameSite=strict',
202
+ 'Set-Cookie', 'bar=BAR;Path=/;SameSite=strict'
204
203
  ]
205
204
  ```
206
205
 
207
206
  ## `Config.extraMimes`
208
207
  ```js
209
208
  Config.extraMimes = {
210
- jpg: 'application/jpeg'
209
+ jpg: 'application/jpeg'
211
210
  }
212
211
  ```
213
212
 
@@ -218,49 +217,51 @@ Config.extraMimes = {
218
217
  ### Select a mock for a route
219
218
  ```js
220
219
  fetch(addr + '/mockaton/edit', {
221
- method: 'PATCH',
222
- body: JSON.stringify({
223
- file: 'api/foo.200.GET.json',
224
- delayed: true // optional
225
- })
220
+ method: 'PATCH',
221
+ body: JSON.stringify({
222
+ file: 'api/foo.200.GET.json',
223
+ delayed: true // optional
224
+ })
226
225
  })
227
226
  ```
228
227
 
229
228
  ### Select all mocks that have a particular comment
230
229
  ```js
231
230
  fetch(addr + '/mockaton/bulk-select-by-comment', {
232
- method: 'PATCH',
233
- body: JSON.stringify('(demo-a)')
231
+ method: 'PATCH',
232
+ body: JSON.stringify('(demo-a)')
234
233
  })
235
234
  ```
236
235
 
237
- ### Reset
238
- Re-Initialize the collection and its states (selected mocks and cookies, delays, etc.).
236
+ ### List Cookies
237
+ Sends a list of the available cookies along with an "is selected" boolean flag.
239
238
  ```js
240
- fetch(addr + '/mockaton/reset', {
241
- method: 'PATCH'
242
- })
239
+ fetch(addr + '/mockaton/cookies')
243
240
  ```
244
241
 
245
242
  ### Select a cookie
246
243
  In `Config.cookies`, each key is the label used for changing it.
247
244
  ```js
248
245
  fetch(addr + '/mockaton/cookies', {
249
- method: 'PATCH',
250
- body: JSON.stringify('My Normal User')
246
+ method: 'PATCH',
247
+ body: JSON.stringify('My Normal User')
251
248
  })
252
249
  ```
253
250
 
254
- ### List Cookies
255
- Sends a list of the available cookies along with a flag indicated if it’s the selected.
251
+ ### Update Fallback Proxy
256
252
  ```js
257
- fetch(addr + '/mockaton/cookies')
253
+ fetch(addr + '/mockaton/fallback', {
254
+ method: 'PATCH',
255
+ body: JSON.stringify('http://example.com')
256
+ })
258
257
  ```
259
258
 
260
- ### Update Fallback Proxy
259
+ ### Reset
260
+ Re-initialize the collection. So if you added or removed mocks they
261
+ will be considered. The selected mocks, cookies, and delays are
262
+ back to default. But the `Config.proxyFalllback` is not affected.
261
263
  ```js
262
- fetch(addr + '/mockaton/fallback', {
263
- method: 'PATCH',
264
- body: JSON.stringify('http://example.com')
264
+ fetch(addr + '/mockaton/reset', {
265
+ method: 'PATCH'
265
266
  })
266
267
  ```
package/Tests.js CHANGED
@@ -17,6 +17,7 @@ import { API, DF, DEFAULT_500_COMMENT } from './src/ApiConstants.js'
17
17
 
18
18
  const tmpDir = mkdtempSync(tmpdir()) + '/'
19
19
  const staticTmpDir = mkdtempSync(tmpdir()) + '/'
20
+ console.log(tmpDir)
20
21
 
21
22
  const fixtureCustomMime = [
22
23
  '/api/custom-mime',
@@ -28,7 +29,7 @@ const fixtures = [
28
29
  [
29
30
  '/api',
30
31
  'api/.GET.200.json',
31
- 'index-like route is just the extension convention'
32
+ 'index-like route for /api, which could just be the extension convention'
32
33
  ],
33
34
 
34
35
  // Exact route paths
@@ -149,6 +150,8 @@ async function runTests() {
149
150
  'api/alternative(comment-2).GET.200.json',
150
151
  JSON.stringify({ comment: 2 }))
151
152
 
153
+ await test422WhenUpdatingNonExistingMockAlternative()
154
+
152
155
  await testAutogenerates500(
153
156
  '/api/alternative',
154
157
  `api/alternative${DEFAULT_500_COMMENT}.GET.500.txt`)
@@ -265,6 +268,15 @@ async function testItUpdatesDelayAndFile(url, file, expectedBody) {
265
268
  })
266
269
  }
267
270
 
271
+ async function test422WhenUpdatingNonExistingMockAlternative() {
272
+ await it('There are mocks for /api/the-route but not this one', async () => {
273
+ const res = await request(API.edit, {
274
+ method: 'PATCH',
275
+ body: JSON.stringify({ [DF.file]: 'api/the-route(non-existing-variant).GET.200.json' })
276
+ })
277
+ equal(res.status, 422)
278
+ })
279
+ }
268
280
 
269
281
  async function testAutogenerates500(url, file) {
270
282
  await request(API.edit, {
@@ -380,12 +392,12 @@ async function testInvalidFilenamesAreIgnored() {
380
392
  consoleErrorSpy.mock.mockImplementation(() => {}) // so they don’t render in the test report
381
393
 
382
394
  write('api/_INVALID_FILENAME_CONVENTION_.json', '')
383
- write('api/bad-filename.GET._INVALID_STATUS_.json', '')
384
- write('api/bad-filename._INVALID_METHOD_.200.json', '')
395
+ write('api/bad-filename-method._INVALID_METHOD_.200.json', '')
396
+ write('api/bad-filename-status.GET._INVALID_STATUS_.json', '')
385
397
  await reset()
386
398
  equal(consoleErrorSpy.mock.calls[0].arguments[0], 'Invalid Filename Convention')
387
- equal(consoleErrorSpy.mock.calls[1].arguments[0], 'Invalid HTTP Response Status: "NaN"')
388
- equal(consoleErrorSpy.mock.calls[2].arguments[0], 'Unrecognized HTTP Method: "_INVALID_METHOD_"')
399
+ equal(consoleErrorSpy.mock.calls[1].arguments[0], 'Unrecognized HTTP Method: "_INVALID_METHOD_"')
400
+ equal(consoleErrorSpy.mock.calls[2].arguments[0], 'Invalid HTTP Response Status: "NaN"')
389
401
  })
390
402
  }
391
403
 
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.3.9",
5
+ "version": "6.3.11",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
package/src/Api.js CHANGED
@@ -57,14 +57,15 @@ async function selectCookie(req, response) {
57
57
  async function updateBroker(req, response) {
58
58
  try {
59
59
  const body = await parseJSON(req)
60
- const broker = mockBrokersCollection.getBrokerByFilename(body[DF.file])
61
- if (!broker) {
60
+ const file = body[DF.file]
61
+ const broker = mockBrokersCollection.getBrokerByFilename(file)
62
+ if (!broker || !broker.mockExists(file)) {
62
63
  sendUnprocessableContent(response)
63
64
  return
64
65
  }
65
66
  if (DF.delayed in body)
66
67
  broker.updateDelay(body[DF.delayed])
67
- broker.updateFile(body[DF.file])
68
+ broker.updateFile(file)
68
69
  sendOK(response)
69
70
  }
70
71
  catch (error) {
package/src/Filename.js CHANGED
@@ -14,7 +14,14 @@ export const includesComment = (filename, search) =>
14
14
  extractComments(filename).some(comment => comment.includes(search))
15
15
 
16
16
 
17
- export function validateFilename(file) {
17
+ export function filenameIsValid(file) {
18
+ const error = validateFilename(file)
19
+ if (error)
20
+ console.error(error, file)
21
+ return !error
22
+ }
23
+
24
+ function validateFilename(file) {
18
25
  const tokens = file.replace(reComments, '').split('.')
19
26
  if (tokens.length < 4)
20
27
  return 'Invalid Filename Convention'
package/src/MockBroker.js CHANGED
@@ -41,7 +41,10 @@ export class MockBroker {
41
41
  get file() { return this.currentMock.file }
42
42
  get delay() { return this.currentMock.delay }
43
43
  get status() { return parseFilename(this.file).status }
44
- get isTemp500() { return includesComment(this.file, DEFAULT_500_COMMENT) }
44
+
45
+ mockExists(file) {
46
+ return this.mocks.includes(file)
47
+ }
45
48
 
46
49
  updateFile(filename) {
47
50
  this.currentMock.file = filename
@@ -78,6 +81,9 @@ export class MockBroker {
78
81
  const file = urlMask.replace(/^\//, '') // Removes leading slash TESTME
79
82
  this.register(`${file}${DEFAULT_500_COMMENT}.${method}.500.txt`)
80
83
  }
84
+ get isTemp500() {
85
+ return includesComment(this.file, DEFAULT_500_COMMENT)
86
+ }
81
87
  }
82
88
 
83
89
  // Stars out (for regex) all the paths that are in square brackets
@@ -1,11 +1,8 @@
1
- import { join } from 'node:path'
2
- import { readdirSync as readDir } from 'node:fs'
3
-
4
1
  import { Config } from './Config.js'
5
2
  import { cookie } from './cookie.js'
6
- import { isFile } from './utils/fs.js'
7
3
  import { MockBroker } from './MockBroker.js'
8
- import { parseFilename, validateFilename } from './Filename.js'
4
+ import { listFilesRecursively } from './utils/fs.js'
5
+ import { parseFilename, filenameIsValid } from './Filename.js'
9
6
 
10
7
 
11
8
  /**
@@ -24,16 +21,11 @@ export function init() {
24
21
  collection = {}
25
22
  cookie.init(Config.cookies)
26
23
 
27
- const files = readDir(Config.mocksDir, { recursive: true })
28
- .filter(f => !Config.ignore.test(f) && isFile(join(Config.mocksDir, f)))
24
+ const files = listFilesRecursively(Config.mocksDir)
29
25
  .sort()
26
+ .filter(f => !Config.ignore.test(f) && filenameIsValid(f))
30
27
 
31
28
  for (const file of files) {
32
- const error = validateFilename(file)
33
- if (error) {
34
- console.error(error, file)
35
- continue
36
- }
37
29
  const { method, urlMask } = parseFilename(file)
38
30
  collection[method] ??= {}
39
31
  if (!collection[method][urlMask])
@@ -45,10 +37,6 @@ export function init() {
45
37
  forEachBroker(broker => broker.ensureItHas500())
46
38
  }
47
39
 
48
- function forEachBroker(fn) {
49
- for (const brokers of Object.values(collection))
50
- Object.values(brokers).forEach(fn)
51
- }
52
40
 
53
41
  export const getAll = () => collection
54
42
 
@@ -75,8 +63,8 @@ export function getBrokerForUrl(method, url) {
75
63
  export function extractAllComments() {
76
64
  const comments = new Set()
77
65
  forEachBroker(broker => {
78
- for (const comment of broker.extractComments())
79
- comments.add(comment)
66
+ for (const c of broker.extractComments())
67
+ comments.add(c)
80
68
  })
81
69
  return Array.from(comments)
82
70
  }
@@ -86,3 +74,8 @@ export function setMocksMatchingComment(comment) {
86
74
  }
87
75
 
88
76
 
77
+ function forEachBroker(fn) {
78
+ for (const brokers of Object.values(collection))
79
+ Object.values(brokers).forEach(fn)
80
+ }
81
+
package/src/utils/fs.js CHANGED
@@ -1,7 +1,12 @@
1
- import { lstatSync, readFileSync } from 'node:fs'
1
+ import { join } from 'node:path'
2
+ import { lstatSync, readFileSync, readdirSync } from 'node:fs'
2
3
 
3
4
 
4
5
  export const isFile = path => lstatSync(path, { throwIfNoEntry: false })?.isFile()
5
6
  export const isDirectory = path => lstatSync(path, { throwIfNoEntry: false })?.isDirectory()
6
7
 
7
8
  export const read = path => readFileSync(path)
9
+
10
+ /** @returns {Array<string>} paths relative to `dir` */
11
+ export const listFilesRecursively = dir => readdirSync(dir, { recursive: true })
12
+ .filter(f => isFile(join(dir, f)))