mockaton 0.9.4 → 0.9.6

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/Api.js CHANGED
@@ -97,7 +97,7 @@ async function bulkUpdateBrokersByCommentTag(req, response) {
97
97
  async function updateBrokerTransform(req, response) {
98
98
  try {
99
99
  const body = await parseJSON(req)
100
- const broker = mockBrokersCollection.getBroker(body[DF.method], body[DF.urlMask])
100
+ const broker = mockBrokersCollection.getBrokerByFilename(body[DF.file])
101
101
  broker.updateTransform(body[DF.file])
102
102
  sendOK(response)
103
103
  }
package/ApiConstants.js CHANGED
@@ -15,7 +15,5 @@ export const DF = { // Dashboard Fields (XHR)
15
15
  delayed: 'delayed',
16
16
  file: 'file',
17
17
  currentCookieKey: 'current_cookie_key',
18
- isForDashboard: 'mock_request_payload',
19
- method: 'method',
20
- urlMask: 'url_mask'
18
+ isForDashboard: 'mock_request_payload'
21
19
  }
package/Config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { existsSync, lstatSync } from 'node:fs'
2
- import { validate } from './utils/validate.js'
2
+ import { validate, is, optional } from './utils/validate.js'
3
3
 
4
4
 
5
5
  export const Config = {
@@ -9,8 +9,8 @@ export const Config = {
9
9
  port: 0, // auto-assigned
10
10
  delay: 1200, // milliseconds
11
11
  cookies: {}, // defaults to the first kv
12
- database: {}, // for mjs transforms
13
- skipOpen: false, // Prevents opening the dashboard in a browser
12
+ database: {},
13
+ skipOpen: false,
14
14
  allowedExt: /\.(json|txt|md|mjs)$/ // Just for excluding temporary editor files (e.g. JetBrains appends a ~)
15
15
  }
16
16
 
@@ -19,21 +19,16 @@ export function setup(options) {
19
19
  validate(Config, {
20
20
  mocksDir: isDirectory,
21
21
  staticDir: optional(isDirectory),
22
- host: String,
22
+ host: is(String),
23
23
  port: port => Number.isInteger(port) && port >= 0 && port < 2 ** 16,
24
24
  delay: ms => Number.isInteger(ms) && ms > 0,
25
- cookies: Object,
26
- database: Object,
27
- skipOpen: Boolean,
28
- allowedExt: RegExp
25
+ cookies: is(Object),
26
+ database: is(Object),
27
+ skipOpen: is(Boolean),
28
+ allowedExt: is(RegExp)
29
29
  })
30
30
  }
31
31
 
32
-
33
- function optional(tester) {
34
- return val => !val || tester(val)
35
- }
36
-
37
32
  function isDirectory(dir) {
38
33
  return existsSync(dir) && lstatSync(dir).isDirectory()
39
34
  }
package/Dashboard.js CHANGED
@@ -257,15 +257,13 @@ function Transforms({ brokersByMethod }) {
257
257
  r('tr', null,
258
258
  r('td', null, r(PreviewLink, { method: broker.method, urlMask })),
259
259
  r('td', null, r(TransformSelector, {
260
- urlMask,
261
- method: broker.method,
262
260
  items: ['', ...broker.transforms],
263
261
  selected: broker.currentTransform
264
262
  })))
265
263
  )))
266
264
  }
267
265
 
268
- function TransformSelector({ method, urlMask, items, selected }) {
266
+ function TransformSelector({ items, selected }) {
269
267
  const className = defaultIsSelected => cssClass(
270
268
  CSS.TransformSelector,
271
269
  !defaultIsSelected && CSS.bold)
@@ -273,16 +271,10 @@ function TransformSelector({ method, urlMask, items, selected }) {
273
271
  r('select', {
274
272
  className: className(selected === items[0]),
275
273
  autocomplete: 'off',
276
- 'data-urlMask': urlMask,
277
- 'data-method': method,
278
274
  onChange() {
279
275
  fetch(DP.transform, {
280
276
  method: 'PATCH',
281
- body: JSON.stringify({
282
- [DF.file]: this.value,
283
- [DF.urlMask]: this.getAttribute('data-urlMask'),
284
- [DF.method]: this.getAttribute('data-method')
285
- })
277
+ body: JSON.stringify({ [DF.file]: this.value })
286
278
  }).then(() => {
287
279
  this.closest('tr').querySelector('a').click()
288
280
  this.className = className(this.value === this.options[0].value)
package/README.md CHANGED
@@ -9,19 +9,21 @@ api/user/
9
9
  api/user/[user-id].GET.200.json
10
10
  ```
11
11
 
12
- By the way, this [browser
12
+ By the way, [this browser
13
13
  extension](https://github.com/ericfortis/devtools-ext-tar-http-requests) can
14
- be used for downloading a tar of your XHR requests following that convention.
14
+ be used for downloading a TAR of your XHR requests following that convention.
15
15
 
16
16
 
17
17
  ### Mock Variants
18
- Each route can have different mocks and those variants could either be:
19
- - a different response status code, or
20
- - a comment on the filename, which is anything within parentheses.
18
+ Each route can have many mocks, which could either be:
19
+ - Different response status code. For example, for testing error responses.
20
+ - Comment on the filename, which is anything within parentheses.
21
21
 
22
- Those variants can be manually selected in the dashboard
22
+ Those alternatives can be manually selected in the dashboard
23
23
  UI, or programmatically, for instance, for setting up tests.
24
24
 
25
+ About the mock precedence, the first file in **alphabetical order** wins.
26
+
25
27
 
26
28
  ## Getting Started
27
29
  The best way to learn _Mockaton_ is by checking out this repo and
@@ -34,10 +36,10 @@ exploring its [sample-mocks/](./sample-mocks) directory. Then run
34
36
  ### Mock Variants of Status Code
35
37
  The **sample-mocks/** directory has three mock alternatives for serving
36
38
  `/api/user/friends`:
37
- - _200 - OK_,
38
- - _204 - No Content_ of an empty list of friends, and
39
+ - _200 - OK_
40
+ - _204 - No Content_ with an empty list of friends
39
41
  - _501 - Internal Server Error_
40
- - 501 mocks get autogenerated for routes that have no 501’s.
42
+ - BTW, 501 mocks get autogenerated for routes that have no 501’s.
41
43
 
42
44
  ![](./README-dashboard-dropdown.png)
43
45
 
@@ -45,11 +47,9 @@ The **sample-mocks/** directory has three mock alternatives for serving
45
47
  Comments are anything within parentheses, including them.
46
48
  ![](./README-mocks-with-comments.png)
47
49
 
48
- ---
49
-
50
- ## Delay
50
+ ## Delay 🕓
51
51
  The clock icon next to the mock selector dropdown is a checkbox for delaying a
52
- particular response. They are handy for testing spinners when developing UIs.
52
+ particular response. They are handy for testing spinners.
53
53
 
54
54
  The milliseconds for the delay is globally configurable via `Config.delay = 1200`
55
55
 
@@ -77,15 +77,15 @@ node my-mockaton.js
77
77
  ## Config Options
78
78
  ```ts
79
79
  interface Config {
80
- mocksDir: string
81
- staticDir?: string
82
- host?: string,
83
- port?: number
84
- delay?: number
85
- cookies?(): object
86
- database?: object
87
- skipOpen?: boolean
88
- allowedExt?: RegExp
80
+ mocksDir: string
81
+ staticDir?: string
82
+ host?: string, // 'localhost'
83
+ port?: number // 0 auto-assigned
84
+ delay?: number // 1200 ms
85
+ cookies?(): object
86
+ database?: object // for "Transforms"
87
+ skipOpen?: boolean // Prevents opening the dashboard in a browser
88
+ allowedExt?: RegExp // /\.(json|txt|md|mjs)$/ Just for excluding temporary editor files (e.g. JetBrains appends a ~)
89
89
  }
90
90
  ```
91
91
 
@@ -96,12 +96,15 @@ import { jwtCookie } from 'mockaton'
96
96
  Config.cookies = {
97
97
  'My Admin User': 'my-cookie=1;Path=/;SameSite=strict',
98
98
  'My Normal User': 'my-cookie=0;Path=/;SameSite=strict',
99
- 'My JWT': jwtCookie('my-cookie', { foo: 'bar' })
99
+ 'My JWT': jwtCookie('my-cookie', {
100
+ email: 'john.doe@example.com',
101
+ picture: 'https://cdn.auth0.com/avatars/jd.png'
102
+ })
100
103
  }
101
104
  ```
102
105
 
103
106
  That `jwtCookie` has a hardcoded header and signature. In other
104
- words, it’s useful iff you care about its payload in frontend.
107
+ words, it’s useful iff you care about its payload in the frontend.
105
108
 
106
109
  ---
107
110
 
@@ -152,28 +155,6 @@ api/foo/?bar=[bar].GET.200.json
152
155
  api/foo/(my comment).GET.200.json
153
156
  ```
154
157
 
155
-
156
- ---
157
- ## Mock Precedence
158
- The first file in **alphabetical order** wins when a particular route has many files.
159
-
160
- ### Why do we have many mocks per Route+Method?
161
- Each route has mocks for many status codes, and also different
162
- mocks (by having comments) for testing particular scenarios.
163
- For example, different 422 validation error messages.
164
-
165
- ---
166
-
167
- ## Reset the Dashboard UI after insert or delete
168
- When deleting the currently selected option, without refreshing the dashboard, the
169
- served mock will be an alternative mock if it exists. That is, the dashboard won't show
170
- a 404 after deleting the current mock if there’s another mock for that particular route.
171
-
172
- Similarly, inserting a file that goes first in alphabetical order will
173
- send a different mock from the one stated in the dashboard dropdown.
174
-
175
- ---
176
-
177
158
  ## Documenting Contracts (.md)
178
159
  This is handy for documenting request payload parameters. The dashboard will
179
160
  print the markdown document (as plain text) above the actual payload content.
@@ -182,15 +163,14 @@ Create a markdown file following the same filename convention.
182
163
  The status code can be any number. For example,
183
164
  ```text
184
165
  api/foo/[user-id].POST.201.md
166
+ api/foo/[user-id].POST.201.json
185
167
  ```
186
168
 
187
- ---
188
-
189
- ## Non-Deterministic Mocks (.mjs handlers)
169
+ ## Transforms (.mjs)
190
170
  Using the same filename convention, files ending
191
171
  with `.mjs` will process the mock before serving it.
192
172
 
193
- For example, this handler will uppercase the mock body.
173
+ For example, this handler will capitalize the mock body and increment a counter.
194
174
  ```js
195
175
  export default function capitalizeAllText(mockAsText, requestBody, Config) {
196
176
  Config.database.myCount ??= 0
@@ -199,31 +179,21 @@ export default function capitalizeAllText(mockAsText, requestBody, Config) {
199
179
  }
200
180
  ```
201
181
 
202
- In demo mode, transforms tagged with the string `demo` within a filename
203
- comment get activated. Mock sets tags e.g. `demo-a` have no effect. In
204
- other words, only one transform per route is supported in demo mode.
205
-
206
182
  ---
207
183
 
208
184
  ## API
209
185
 
210
- ### Changing a mock for one route
186
+ ### `/mockaton/edit` Select a mock for a route
211
187
  ```
212
- PATCH http://localhost:2345/mockaton/edit
188
+ PATCH /mockaton/edit
213
189
  {
214
190
  "file": "api/foo.200.GET.json"
215
191
  "delayed": true // optional
216
192
  }
217
193
  ```
194
+ ---
218
195
 
219
- ### Bulk Selecting Mocks by Matching comments
220
- ```
221
- PATCH http://localhost:2345/mockaton/bulk-select
222
- {
223
- "comment": "demo-a"
224
- }
225
- ```
226
-
196
+ ### `/mockaton/bulk-select` Select all mocks that have a particular comment
227
197
  Many mocks can be changed at once. We do that by searching the
228
198
  comments on the filename. For example, `api/foo(demo-a).GET.200.json`
229
199
 
@@ -234,8 +204,44 @@ particular API there is only `demo-a` and `demo-b`, changing to
234
204
  Similarly, if there’s no demo mock at all for
235
205
  a route, the first dev mock (a-z) will be served.
236
206
 
207
+ ```
208
+ PATCH /mockaton/bulk-select
209
+ {
210
+ "comment": "demo-a"
211
+ }
212
+ ```
213
+ ---
237
214
 
238
- ### Reset
215
+ ### `/mockaton/reset` Reset
216
+ Re-Initialize the collection and its states (selected mocks and cookies, delays, etc.).
239
217
  ```
240
- PATCH http://localhost:2345/mockaton/reset
218
+ PATCH /mockaton/reset
219
+ ```
220
+ ---
221
+
222
+ ### `/mockaton/cookies` Select a cookie
223
+ In `Config.cookies`, each key is a label used to change them.
224
+ ```
225
+ PATCH /mockaton/cookies
226
+ {
227
+ "current_cookie_key": "My Normal User"
228
+ }
241
229
  ```
230
+
231
+ ### `/mockaton/cookies` List Cookies
232
+ Sends a list of the cookie labels (keys) and
233
+ along with a flag indicated if it’s the selected.
234
+ ```
235
+ GET /mockaton/cookies
236
+ ```
237
+
238
+ ---
239
+
240
+ ### `/mockaton/transform` Select a Transform
241
+ ```
242
+ PATCH /mockaton/transform
243
+ {
244
+ "file": "api/video/list(concat newly uploaded).GET.200.mjs"
245
+ }
246
+ ```
247
+ ---
package/Route.js CHANGED
@@ -16,7 +16,7 @@ export class Route {
16
16
  constructor(file) {
17
17
  const { urlMask, method } = Route.parseFilename(file)
18
18
  this.method = method
19
- this.#urlRegex = new RegExp(`^${disregardVariables(removeQueryStringAndFragment(urlMask))}/*$`)
19
+ this.#urlRegex = new RegExp('^' + disregardVariables(removeQueryStringAndFragment(urlMask)) + '/*$')
20
20
  }
21
21
 
22
22
  urlMaskMatches(url) {
package/Tests.js CHANGED
@@ -306,8 +306,6 @@ export default function (mock, reqBody, config) {
306
306
  await request(DP.transform, {
307
307
  method: 'PATCH',
308
308
  body: JSON.stringify({
309
- [DF.method]: 'POST',
310
- [DF.urlMask]: '/api/transform',
311
309
  [DF.file]: 'api/transform.POST.200.mjs'
312
310
  })
313
311
  })
package/_usage_example.js CHANGED
@@ -8,8 +8,11 @@ Mockaton({
8
8
  mocksDir: resolve('sample-mocks'),
9
9
  staticDir: resolve('sample-static'),
10
10
  cookies: {
11
- 'Admin User': 'my-cookie=1;Path=/;SameSite=strict',
12
- 'Normal User': 'my-cookie=0;Path=/;SameSite=strict',
13
- 'My JWT': jwtCookie('my-cookie', { foo: 'bar' })
11
+ 'My Admin User': 'my-cookie=1;Path=/;SameSite=strict',
12
+ 'My Normal User': 'my-cookie=0;Path=/;SameSite=strict',
13
+ 'My JWT': jwtCookie('my-cookie', {
14
+ email: 'john.doe@example.com',
15
+ picture: 'https://cdn.auth0.com/avatars/jd.png'
16
+ })
14
17
  }
15
18
  })
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": "0.9.4",
5
+ "version": "0.9.6",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
package/utils/validate.js CHANGED
@@ -1,27 +1,8 @@
1
- const isTypeOf = example => value =>
2
- Object.prototype.toString.call(value) ===
3
- Object.prototype.toString.call(example)
4
-
5
- const typeCheckers = new Map([
6
- [Date, isTypeOf(new Date())],
7
- [Array, isTypeOf([])],
8
- [String, isTypeOf('')],
9
- [Object, isTypeOf({})],
10
- [Number, isTypeOf(1)],
11
- [RegExp, isTypeOf(/a/)],
12
- [Boolean, isTypeOf(true)],
13
- [Function, isTypeOf(a => a)]
14
- ])
15
-
16
-
17
1
  export function validate(obj, shape) {
18
- for (const [field, value] of Object.entries(obj)) {
19
- const validator = shape[field]
20
- if (typeCheckers.has(validator)) {
21
- if (!typeCheckers.get(validator)(value))
22
- throw new TypeError(`${field} ${value}`)
23
- }
24
- else if (!validator(value))
2
+ for (const [field, value] of Object.entries(obj))
3
+ if (!shape[field](value))
25
4
  throw new TypeError(`${field} ${value}`)
26
- }
27
5
  }
6
+
7
+ export const is = ctor => val => val.constructor === ctor
8
+ export const optional = tester => val => !val || tester(val)