mockaton 0.9.5 → 0.9.7
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/Dashboard.js +2 -10
- package/MockBroker.js +9 -12
- package/MockDispatcher.js +29 -30
- package/README-dashboard-dropdown.png +0 -0
- package/README-dashboard.png +0 -0
- package/README-mocks-with-comments.png +0 -0
- package/README.md +52 -55
- package/Route.js +7 -9
- package/Tests.js +35 -33
- package/_usage_example.js +2 -2
- package/mockBrokersCollection.js +2 -3
- package/package.json +1 -1
- package/sample-mocks/api/user/.GET.500.txt +7 -0
- package/sample-mocks/api/user/.GET.501.txt +0 -7
- /package/sample-mocks/api/user/{edit-name.PATCH.501.txt → edit-name.PATCH.500.txt} +0 -0
- /package/sample-mocks/api/user/{friends.GET.501.txt → friends.GET.500.txt} +0 -0
- /package/sample-mocks/api/user/{logout.POST.501.txt → logout.POST.500.txt} +0 -0
- /package/sample-mocks/api/user/{videos.GET.501.txt → videos.GET.500.txt} +0 -0
- /package/sample-mocks/api/video/{[id].GET.501.txt → [id].GET.500.txt} +0 -0
- /package/sample-mocks/api/video/{list.GET.501.txt → list.GET.500.txt} +0 -0
- /package/sample-mocks/api/video/stat/[stat-id]/{[video-id].GET.501.txt → [video-id].GET.500.txt} +0 -0
- /package/sample-mocks/api/video/stat/[stat-id]/{all-videos?limit=[limit].GET.501.txt → all-videos?limit=[limit].GET.500.txt} +0 -0
- /package/sample-mocks/api/video/{upload.POST.501.txt → upload.POST.500.txt} +0 -0
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/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/MockBroker.js
CHANGED
|
@@ -20,7 +20,7 @@ export class MockBroker {
|
|
|
20
20
|
this.mocks = [] // *.json,txt
|
|
21
21
|
this.currentMock = {
|
|
22
22
|
file: '',
|
|
23
|
-
status
|
|
23
|
+
get status() { return Route.parseFilename(this.file).status },
|
|
24
24
|
delay: 0
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -36,10 +36,8 @@ export class MockBroker {
|
|
|
36
36
|
else if (file.endsWith('.mjs'))
|
|
37
37
|
this.transforms.push(file)
|
|
38
38
|
else {
|
|
39
|
-
if (!this.mocks.length)
|
|
39
|
+
if (!this.mocks.length)
|
|
40
40
|
this.currentMock.file = file // The first mock file option for a particular route becomes the default
|
|
41
|
-
this.currentMock.status = Route.parseFilename(file).status
|
|
42
|
-
}
|
|
43
41
|
this.mocks.push(file)
|
|
44
42
|
}
|
|
45
43
|
}
|
|
@@ -52,7 +50,6 @@ export class MockBroker {
|
|
|
52
50
|
|
|
53
51
|
updateFile(filename) {
|
|
54
52
|
this.currentMock.file = filename
|
|
55
|
-
this.currentMock.status = Route.parseFilename(filename).status
|
|
56
53
|
}
|
|
57
54
|
|
|
58
55
|
updateDelay(delayed) {
|
|
@@ -83,24 +80,24 @@ export class MockBroker {
|
|
|
83
80
|
return comments
|
|
84
81
|
}
|
|
85
82
|
|
|
86
|
-
|
|
87
|
-
if (!this.#
|
|
88
|
-
this.#
|
|
83
|
+
ensureItHas500() {
|
|
84
|
+
if (!this.#has500())
|
|
85
|
+
this.#write500()
|
|
89
86
|
}
|
|
90
87
|
|
|
91
|
-
#
|
|
88
|
+
#has500() {
|
|
92
89
|
return this.mocks.some(mock =>
|
|
93
|
-
Route.parseFilename(mock).status ===
|
|
90
|
+
Route.parseFilename(mock).status === 500)
|
|
94
91
|
}
|
|
95
92
|
|
|
96
|
-
#
|
|
93
|
+
#write500() {
|
|
97
94
|
// TODO handle route with transforms but without mocks
|
|
98
95
|
const { urlMask, method } = Route.parseFilename(this.mocks[0])
|
|
99
96
|
let mask = urlMask
|
|
100
97
|
const t = join(Config.mocksDir, urlMask)
|
|
101
98
|
if (existsSync(t) && lstatSync(t).isDirectory())
|
|
102
99
|
mask = urlMask + '/'
|
|
103
|
-
const file = `${mask}.${method}.
|
|
100
|
+
const file = `${mask}.${method}.500.txt`
|
|
104
101
|
writeFileSync(join(Config.mocksDir, file), '')
|
|
105
102
|
this.register(file)
|
|
106
103
|
}
|
package/MockDispatcher.js
CHANGED
|
@@ -10,46 +10,45 @@ import { parseJSON, JsonBodyParserError } from './utils/http-request.js'
|
|
|
10
10
|
import { sendInternalServerError, sendNotFound, sendFile, sendBadRequest } from './utils/http-response.js'
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
function serveDocumentation(req, response) {
|
|
14
|
-
sendFile(response, join(Config.mocksDir, decodeURIComponent(req.url)))
|
|
15
|
-
}
|
|
16
|
-
|
|
17
13
|
export async function dispatchMock(req, response) {
|
|
14
|
+
/* Serve Documentation */
|
|
18
15
|
if (req.method === 'GET' && req.url.endsWith('.md')) {
|
|
19
|
-
|
|
16
|
+
sendFile(response, join(Config.mocksDir, decodeURIComponent(req.url)))
|
|
20
17
|
return
|
|
21
18
|
}
|
|
22
19
|
|
|
23
20
|
const mockBroker = MockBrokerCollection.findMatchingBroker(req.method, req.url)
|
|
24
|
-
if (!mockBroker)
|
|
21
|
+
if (!mockBroker) {
|
|
25
22
|
sendNotFound(response)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const { file, status, delay, currentTransform } = mockBroker
|
|
28
|
+
console.log('\n', req.url, '->\n ', file)
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
response.statusCode = status
|
|
31
|
+
response.setHeader('content-type', mimeFor(file))
|
|
32
|
+
if (cookie.getCurrent())
|
|
33
|
+
response.setHeader('set-cookie', cookie.getCurrent())
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
setTimeout(() => response.end(mockAsText), delay)
|
|
43
|
-
}
|
|
44
|
-
catch (error) {
|
|
45
|
-
console.error(error)
|
|
46
|
-
if (error instanceof JsonBodyParserError)
|
|
47
|
-
sendBadRequest(response)
|
|
48
|
-
else if (error.code === 'ENOENT')
|
|
49
|
-
sendNotFound(response) // file has been deleted
|
|
50
|
-
else
|
|
51
|
-
sendInternalServerError(response)
|
|
35
|
+
let mockAsText = readMock(file)
|
|
36
|
+
if (mockBroker.currentTransform) {
|
|
37
|
+
const body = await requestBodyForTransform(req, mockAsText)
|
|
38
|
+
const transformFunc = await importTransformFunc(currentTransform)
|
|
39
|
+
mockAsText = transformFunc(mockAsText, body, Config)
|
|
52
40
|
}
|
|
41
|
+
setTimeout(() => response.end(mockAsText), delay)
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
console.error(error)
|
|
45
|
+
if (error instanceof JsonBodyParserError)
|
|
46
|
+
sendBadRequest(response)
|
|
47
|
+
else if (error.code === 'ENOENT')
|
|
48
|
+
sendNotFound(response) // file has been deleted
|
|
49
|
+
else
|
|
50
|
+
sendInternalServerError(response)
|
|
51
|
+
}
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
const nonSafeMethods = ['PATCH', 'POST', 'PUT', 'DELETE', 'CONNECT']
|
|
Binary file
|
package/README-dashboard.png
CHANGED
|
Binary file
|
|
Binary file
|
package/README.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Mockaton
|
|
2
2
|
_Mockaton_ is a mock server for developing and testing frontends.
|
|
3
3
|
|
|
4
|
-
It scans `Config.mocksDir` for files following a specific
|
|
5
|
-
|
|
4
|
+
It scans `Config.mocksDir` for files following a specific
|
|
5
|
+
file name convention, which is similar to the URL paths. For
|
|
6
|
+
example, the following file will be served for `/api/user/1234`
|
|
6
7
|
```
|
|
7
8
|
api/
|
|
8
9
|
api/user/
|
|
@@ -22,10 +23,12 @@ Each route can have many mocks, which could either be:
|
|
|
22
23
|
Those alternatives can be manually selected in the dashboard
|
|
23
24
|
UI, or programmatically, for instance, for setting up tests.
|
|
24
25
|
|
|
26
|
+
About the default mock file, the first file in **alphabetical order** wins.
|
|
27
|
+
|
|
25
28
|
|
|
26
29
|
## Getting Started
|
|
27
30
|
The best way to learn _Mockaton_ is by checking out this repo and
|
|
28
|
-
exploring its [sample-mocks/](./sample-mocks) directory. Then run
|
|
31
|
+
exploring its [sample-mocks/](./sample-mocks) directory. Then, run
|
|
29
32
|
[`./_usage_example.js`](./_usage_example.js) and you’ll see this dashboard:
|
|
30
33
|
|
|
31
34
|

|
|
@@ -36,8 +39,8 @@ The **sample-mocks/** directory has three mock alternatives for serving
|
|
|
36
39
|
`/api/user/friends`:
|
|
37
40
|
- _200 - OK_
|
|
38
41
|
- _204 - No Content_ with an empty list of friends
|
|
39
|
-
-
|
|
40
|
-
- BTW,
|
|
42
|
+
- _500 - Internal Server Error_
|
|
43
|
+
- BTW, 500 mocks get autogenerated for routes that have no 500’s.
|
|
41
44
|
|
|
42
45
|

|
|
43
46
|
|
|
@@ -46,7 +49,7 @@ Comments are anything within parentheses, including them.
|
|
|
46
49
|

|
|
47
50
|
|
|
48
51
|
## Delay 🕓
|
|
49
|
-
The clock icon next to the mock selector
|
|
52
|
+
The clock icon next to the mock selector is a checkbox for delaying a
|
|
50
53
|
particular response. They are handy for testing spinners.
|
|
51
54
|
|
|
52
55
|
The milliseconds for the delay is globally configurable via `Config.delay = 1200`
|
|
@@ -102,7 +105,7 @@ Config.cookies = {
|
|
|
102
105
|
```
|
|
103
106
|
|
|
104
107
|
That `jwtCookie` has a hardcoded header and signature. In other
|
|
105
|
-
words, it’s useful iff you care about its payload in frontend.
|
|
108
|
+
words, it’s useful iff you care about its payload in the frontend.
|
|
106
109
|
|
|
107
110
|
---
|
|
108
111
|
|
|
@@ -134,13 +137,13 @@ api/foo.GET.200.json
|
|
|
134
137
|
```
|
|
135
138
|
api/video?limit=[limit].GET.200.json
|
|
136
139
|
```
|
|
137
|
-
The query string behaves like comments in the sense it’s
|
|
138
|
-
|
|
140
|
+
The query string behaves like comments in the sense it’s only used for documenting
|
|
141
|
+
the URL API contract. In other words, the query string is ignored when routing to it.
|
|
139
142
|
|
|
140
|
-
|
|
141
|
-
|
|
143
|
+
BTW, in Windows, filenames containing "?" are [not
|
|
144
|
+
permitted](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file),
|
|
145
|
+
but since that’s part of the query string, it’s ignored anyway.
|
|
142
146
|
|
|
143
|
-
https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
|
|
144
147
|
|
|
145
148
|
|
|
146
149
|
### Default (index-like) file
|
|
@@ -153,28 +156,6 @@ api/foo/?bar=[bar].GET.200.json
|
|
|
153
156
|
api/foo/(my comment).GET.200.json
|
|
154
157
|
```
|
|
155
158
|
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
## Mock Precedence
|
|
159
|
-
The first file in **alphabetical order** wins when a particular route has many files.
|
|
160
|
-
|
|
161
|
-
### Why do we have many mocks per Route+Method?
|
|
162
|
-
Each route has mocks for many status codes, and also different
|
|
163
|
-
mocks (by having comments) for testing particular scenarios.
|
|
164
|
-
For example, different 422 validation error messages.
|
|
165
|
-
|
|
166
|
-
---
|
|
167
|
-
|
|
168
|
-
## Reset the Dashboard UI after insert or delete
|
|
169
|
-
When deleting the currently selected option, without refreshing the dashboard, the
|
|
170
|
-
served mock will be an alternative mock if it exists. That is, the dashboard won't show
|
|
171
|
-
a 404 after deleting the current mock if there’s another mock for that particular route.
|
|
172
|
-
|
|
173
|
-
Similarly, inserting a file that goes first in alphabetical order will
|
|
174
|
-
send a different mock from the one stated in the dashboard dropdown.
|
|
175
|
-
|
|
176
|
-
---
|
|
177
|
-
|
|
178
159
|
## Documenting Contracts (.md)
|
|
179
160
|
This is handy for documenting request payload parameters. The dashboard will
|
|
180
161
|
print the markdown document (as plain text) above the actual payload content.
|
|
@@ -183,15 +164,14 @@ Create a markdown file following the same filename convention.
|
|
|
183
164
|
The status code can be any number. For example,
|
|
184
165
|
```text
|
|
185
166
|
api/foo/[user-id].POST.201.md
|
|
167
|
+
api/foo/[user-id].POST.201.json
|
|
186
168
|
```
|
|
187
169
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
## Non-Deterministic Mocks (.mjs handlers)
|
|
170
|
+
## Transforms (.mjs)
|
|
191
171
|
Using the same filename convention, files ending
|
|
192
172
|
with `.mjs` will process the mock before serving it.
|
|
193
173
|
|
|
194
|
-
For example, this handler will
|
|
174
|
+
For example, this handler will capitalize the mock body and increment a counter.
|
|
195
175
|
```js
|
|
196
176
|
export default function capitalizeAllText(mockAsText, requestBody, Config) {
|
|
197
177
|
Config.database.myCount ??= 0
|
|
@@ -200,43 +180,60 @@ export default function capitalizeAllText(mockAsText, requestBody, Config) {
|
|
|
200
180
|
}
|
|
201
181
|
```
|
|
202
182
|
|
|
203
|
-
In demo mode, transforms tagged with the string `demo` within a filename
|
|
204
|
-
comment get activated. Mock sets tags e.g. `demo-a` have no effect. In
|
|
205
|
-
other words, only one transform per route is supported in demo mode.
|
|
206
|
-
|
|
207
183
|
---
|
|
208
184
|
|
|
209
185
|
## API
|
|
210
186
|
|
|
211
|
-
###
|
|
187
|
+
### `/mockaton/edit` Select a mock for a route
|
|
212
188
|
```
|
|
213
|
-
PATCH
|
|
189
|
+
PATCH /mockaton/edit
|
|
214
190
|
{
|
|
215
191
|
"file": "api/foo.200.GET.json"
|
|
216
192
|
"delayed": true // optional
|
|
217
193
|
}
|
|
218
194
|
```
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
### `/mockaton/bulk-select` Select all mocks that have a particular comment
|
|
219
198
|
|
|
220
|
-
### Bulk Selecting Mocks by Matching comments
|
|
221
199
|
```
|
|
222
|
-
PATCH
|
|
200
|
+
PATCH /mockaton/bulk-select
|
|
223
201
|
{
|
|
224
202
|
"comment": "demo-a"
|
|
225
203
|
}
|
|
226
204
|
```
|
|
205
|
+
---
|
|
227
206
|
|
|
228
|
-
|
|
229
|
-
|
|
207
|
+
### `/mockaton/reset` Reset
|
|
208
|
+
Re-Initialize the collection and its states (selected mocks and cookies, delays, etc.).
|
|
209
|
+
```
|
|
210
|
+
PATCH /mockaton/reset
|
|
211
|
+
```
|
|
212
|
+
---
|
|
230
213
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
214
|
+
### `/mockaton/cookies` Select a cookie
|
|
215
|
+
In `Config.cookies`, each key is the label used
|
|
216
|
+
for changing it. Only one cookie can be set.
|
|
217
|
+
```
|
|
218
|
+
PATCH /mockaton/cookies
|
|
219
|
+
{
|
|
220
|
+
"current_cookie_key": "My Normal User"
|
|
221
|
+
}
|
|
222
|
+
```
|
|
234
223
|
|
|
235
|
-
|
|
236
|
-
a
|
|
224
|
+
### `/mockaton/cookies` List Cookies
|
|
225
|
+
Sends a list of the available cookies along with a flag indicated if it’s the selected.
|
|
226
|
+
```
|
|
227
|
+
GET /mockaton/cookies
|
|
228
|
+
```
|
|
237
229
|
|
|
230
|
+
---
|
|
238
231
|
|
|
239
|
-
###
|
|
232
|
+
### `/mockaton/transform` Select a Transform
|
|
240
233
|
```
|
|
241
|
-
PATCH
|
|
234
|
+
PATCH /mockaton/transform
|
|
235
|
+
{
|
|
236
|
+
"file": "api/video/list(concat newly uploaded).GET.200.mjs"
|
|
237
|
+
}
|
|
242
238
|
```
|
|
239
|
+
---
|
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) {
|
|
@@ -45,20 +45,18 @@ export class Route {
|
|
|
45
45
|
static parseFilename(file) {
|
|
46
46
|
const tokens = file.replace(Route.reComments, '').split('.')
|
|
47
47
|
|
|
48
|
-
let error = ''
|
|
49
48
|
if (tokens.length < 4)
|
|
50
|
-
error
|
|
51
|
-
|
|
52
|
-
const method = tokens.at(-3)
|
|
53
|
-
if (!httpMethods.includes(method))
|
|
54
|
-
error = `Unrecognized HTTP Method: "${method}"`
|
|
49
|
+
return { error: 'Invalid Filename Convention' }
|
|
55
50
|
|
|
56
51
|
const status = Number(tokens.at(-2))
|
|
57
52
|
if (!responseStatusIsValid(status))
|
|
58
|
-
error
|
|
53
|
+
return { error: `Invalid HTTP Response Status: "${status}"` }
|
|
54
|
+
|
|
55
|
+
const method = tokens.at(-3)
|
|
56
|
+
if (!httpMethods.includes(method))
|
|
57
|
+
return { error: `Unrecognized HTTP Method: "${method}"` }
|
|
59
58
|
|
|
60
59
|
return {
|
|
61
|
-
error,
|
|
62
60
|
urlMask: '/' + removeTrailingSlash(tokens.at(-4)),
|
|
63
61
|
method,
|
|
64
62
|
status
|
package/Tests.js
CHANGED
|
@@ -90,15 +90,10 @@ const fixtures = [
|
|
|
90
90
|
for (const [, file, body] of fixtures)
|
|
91
91
|
write(file, file.endsWith('.json') ? JSON.stringify(body) : body)
|
|
92
92
|
|
|
93
|
-
write('api/.GET.
|
|
93
|
+
write('api/.GET.500.txt', 'keeps non-autogenerated 500')
|
|
94
94
|
write('api/alternative(comment-2).GET.200.json', JSON.stringify({ comment: 2 }))
|
|
95
95
|
write('api/my-route(comment-2).GET.200.json', JSON.stringify({ comment: 2 }))
|
|
96
96
|
|
|
97
|
-
// These files ensure the server doesn’t crash. We don’t test their console.error
|
|
98
|
-
write('api/bad-filename.200.json', 'missing method')
|
|
99
|
-
write('api/bad-filename.GET.200', 'missing extension')
|
|
100
|
-
write('api/bad-filename.GET.json', 'missing response status')
|
|
101
|
-
|
|
102
97
|
writeStatic('index.html', '<h1>Static</h1>')
|
|
103
98
|
writeStatic('assets/app.js', 'const app = 1')
|
|
104
99
|
writeStatic('another-entry/index.html', '<h1>Another</h1>')
|
|
@@ -125,14 +120,14 @@ async function runTests() {
|
|
|
125
120
|
'/api/alternative',
|
|
126
121
|
'api/alternative(comment-1).GET.200.json')
|
|
127
122
|
|
|
128
|
-
await
|
|
123
|
+
await testAutogenerates500(
|
|
129
124
|
'/api/company-e/123?limit=9',
|
|
130
|
-
'api/company-e/[id]?limit=[limit].GET.
|
|
125
|
+
'api/company-e/[id]?limit=[limit].GET.500.txt')
|
|
131
126
|
|
|
132
|
-
await
|
|
127
|
+
await testPreservesExiting500(
|
|
133
128
|
'/api',
|
|
134
|
-
'api/.GET.
|
|
135
|
-
'keeps non-autogenerated
|
|
129
|
+
'api/.GET.500.txt',
|
|
130
|
+
'keeps non-autogenerated 500')
|
|
136
131
|
|
|
137
132
|
await reset()
|
|
138
133
|
await testItUpdatesTheCurrentSelectedMock(
|
|
@@ -148,12 +143,10 @@ async function runTests() {
|
|
|
148
143
|
'(this is the actual comment)',
|
|
149
144
|
'(another comment)'
|
|
150
145
|
])
|
|
151
|
-
await testItBulkSelectsByComment('(comment-2)',
|
|
152
|
-
[
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
]
|
|
156
|
-
)
|
|
146
|
+
await testItBulkSelectsByComment('(comment-2)', [
|
|
147
|
+
['/api/alternative', 'api/alternative(comment-2).GET.200.json', { comment: 2 }],
|
|
148
|
+
['/api/my-route', 'api/my-route(comment-2).GET.200.json', { comment: 2 }]
|
|
149
|
+
])
|
|
157
150
|
|
|
158
151
|
await reset()
|
|
159
152
|
for (const [url, file, body] of fixtures)
|
|
@@ -161,9 +154,8 @@ async function runTests() {
|
|
|
161
154
|
|
|
162
155
|
await testItUpdatesUserRole()
|
|
163
156
|
await testTransforms()
|
|
164
|
-
|
|
165
157
|
await testStaticFileServing()
|
|
166
|
-
|
|
158
|
+
await testInvalidFilenamesAreIgnored()
|
|
167
159
|
server.close()
|
|
168
160
|
}
|
|
169
161
|
|
|
@@ -223,29 +215,29 @@ async function testItUpdatesDelayAndFile(url, file) {
|
|
|
223
215
|
}
|
|
224
216
|
|
|
225
217
|
|
|
226
|
-
async function
|
|
218
|
+
async function testAutogenerates500(url, file) {
|
|
227
219
|
await request(DP.edit, {
|
|
228
220
|
method: 'PATCH',
|
|
229
221
|
body: JSON.stringify({ [DF.file]: file })
|
|
230
222
|
})
|
|
231
223
|
const res = await request(url)
|
|
232
224
|
const body = await res.text()
|
|
233
|
-
await describe('autogenerated
|
|
225
|
+
await describe('autogenerated 500', () => {
|
|
234
226
|
it('body is empty', () => equal(body, ''))
|
|
235
|
-
it('status is:
|
|
227
|
+
it('status is: 500', () => equal(res.status, 500))
|
|
236
228
|
})
|
|
237
229
|
}
|
|
238
230
|
|
|
239
|
-
async function
|
|
231
|
+
async function testPreservesExiting500(url, file, expectedBody) {
|
|
240
232
|
await request(DP.edit, {
|
|
241
233
|
method: 'PATCH',
|
|
242
234
|
body: JSON.stringify({ [DF.file]: file })
|
|
243
235
|
})
|
|
244
236
|
const res = await request(url)
|
|
245
237
|
const body = await res.text()
|
|
246
|
-
await describe('preserves existing
|
|
238
|
+
await describe('preserves existing 500', () => {
|
|
247
239
|
it('body is empty', () => equal(body, expectedBody))
|
|
248
|
-
it('status is:
|
|
240
|
+
it('status is: 500', () => equal(res.status, 500))
|
|
249
241
|
})
|
|
250
242
|
}
|
|
251
243
|
|
|
@@ -259,9 +251,7 @@ async function testExtractsAllComments(expected) {
|
|
|
259
251
|
async function testItBulkSelectsByComment(comment, tests) {
|
|
260
252
|
await request(DP.bulkSelect, {
|
|
261
253
|
method: 'PATCH',
|
|
262
|
-
body: JSON.stringify({
|
|
263
|
-
[DF.comment]: comment
|
|
264
|
-
})
|
|
254
|
+
body: JSON.stringify({ [DF.comment]: comment })
|
|
265
255
|
})
|
|
266
256
|
for (const [url, file, body] of tests)
|
|
267
257
|
await testMockDispatching(url, file, body)
|
|
@@ -305,11 +295,7 @@ export default function (mock, reqBody, config) {
|
|
|
305
295
|
await reset() // for registering the files
|
|
306
296
|
await request(DP.transform, {
|
|
307
297
|
method: 'PATCH',
|
|
308
|
-
body: JSON.stringify({
|
|
309
|
-
[DF.method]: 'POST',
|
|
310
|
-
[DF.urlMask]: '/api/transform',
|
|
311
|
-
[DF.file]: 'api/transform.POST.200.mjs'
|
|
312
|
-
})
|
|
298
|
+
body: JSON.stringify({ [DF.file]: 'api/transform.POST.200.mjs' })
|
|
313
299
|
})
|
|
314
300
|
await testMockDispatching('/api/transform',
|
|
315
301
|
'api/transform.POST.200.json',
|
|
@@ -344,6 +330,22 @@ async function testStaticFileServing() {
|
|
|
344
330
|
})
|
|
345
331
|
}
|
|
346
332
|
|
|
333
|
+
async function testInvalidFilenamesAreIgnored() {
|
|
334
|
+
await it('Invalid filenames get skipped, so they don’t crash the server', async (t) => {
|
|
335
|
+
const consoleErrorSpy = t.mock.method(console, 'error')
|
|
336
|
+
consoleErrorSpy.mock.mockImplementation(() => {}) // so they don’t render in the test report
|
|
337
|
+
|
|
338
|
+
// An extension is needed for testing because of `Config.allowedExt`
|
|
339
|
+
write('api/_INVALID_FILENAME_CONVENTION_.json', '')
|
|
340
|
+
write('api/bad-filename.GET._INVALID_STATUS_.json', '')
|
|
341
|
+
write('api/bad-filename._INVALID_METHOD_.200.json', '')
|
|
342
|
+
await reset()
|
|
343
|
+
equal(consoleErrorSpy.mock.calls[0].arguments[0], 'Invalid Filename Convention')
|
|
344
|
+
equal(consoleErrorSpy.mock.calls[1].arguments[0], 'Invalid HTTP Response Status: "NaN"')
|
|
345
|
+
equal(consoleErrorSpy.mock.calls[2].arguments[0], 'Unrecognized HTTP Method: "_INVALID_METHOD_"')
|
|
346
|
+
})
|
|
347
|
+
}
|
|
348
|
+
|
|
347
349
|
|
|
348
350
|
// Utils
|
|
349
351
|
|
package/_usage_example.js
CHANGED
|
@@ -8,8 +8,8 @@ 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',
|
|
11
|
+
'My Admin User': 'my-cookie=1;Path=/;SameSite=strict',
|
|
12
|
+
'My Normal User': 'my-cookie=0;Path=/;SameSite=strict',
|
|
13
13
|
'My JWT': jwtCookie('my-cookie', {
|
|
14
14
|
email: 'john.doe@example.com',
|
|
15
15
|
picture: 'https://cdn.auth0.com/avatars/jd.png'
|
package/mockBrokersCollection.js
CHANGED
|
@@ -35,14 +35,13 @@ export function init() {
|
|
|
35
35
|
collection[method][urlMask].register(file)
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
forEachBroker(broker => broker.
|
|
38
|
+
forEachBroker(broker => broker.ensureItHas500())
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export const getAll = () => collection
|
|
42
|
-
export const getBroker = (method, urlMask) => collection[method][urlMask]
|
|
43
42
|
export const getBrokerByFilename = file => {
|
|
44
43
|
const { method, urlMask } = Route.parseFilename(file)
|
|
45
|
-
return
|
|
44
|
+
return collection[method][urlMask]
|
|
46
45
|
}
|
|
47
46
|
|
|
48
47
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
This is a plain text response for (/api/user).
|
|
2
|
+
|
|
3
|
+
In this case, it’s for mocking up a 500 - Internal Server Error.
|
|
4
|
+
|
|
5
|
+
This file could have been empty, or some JSON if it had a `.json` extension.
|
|
6
|
+
|
|
7
|
+
By the way, on initialization an 500 is auto-generated for routes that don’t have a 500.
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
This is a plain text response for (/api/user).
|
|
2
|
-
|
|
3
|
-
In this case, it’s for mocking up a 501 - Internal Server Error.
|
|
4
|
-
|
|
5
|
-
This file could have been empty, or some JSON if it had a `.json` extension.
|
|
6
|
-
|
|
7
|
-
By the way, on initialization an 501 is auto-generated for routes that don’t have a 501.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/sample-mocks/api/video/stat/[stat-id]/{[video-id].GET.501.txt → [video-id].GET.500.txt}
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|