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 +1 -1
- package/ApiConstants.js +1 -3
- package/Config.js +8 -13
- package/Dashboard.js +2 -10
- package/README.md +72 -66
- package/Route.js +1 -1
- package/Tests.js +0 -2
- package/_usage_example.js +6 -3
- package/package.json +1 -1
- package/utils/validate.js +5 -24
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.
|
|
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: {},
|
|
13
|
-
skipOpen: false,
|
|
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({
|
|
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
|
|
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
|
|
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
|
|
19
|
-
-
|
|
20
|
-
-
|
|
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
|
|
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_
|
|
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
|

|
|
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
|

|
|
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
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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', {
|
|
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
|
|
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
|
-
###
|
|
186
|
+
### `/mockaton/edit` Select a mock for a route
|
|
211
187
|
```
|
|
212
|
-
PATCH
|
|
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
|
-
###
|
|
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
|
|
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(
|
|
19
|
+
this.#urlRegex = new RegExp('^' + disregardVariables(removeQueryStringAndFragment(urlMask)) + '/*$')
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
urlMaskMatches(url) {
|
package/Tests.js
CHANGED
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', {
|
|
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
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
|
-
|
|
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)
|