mockaton 1.1.0 → 2.1.0
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-dashboard.png +0 -0
- package/README.md +15 -29
- package/Tests.js +18 -24
- package/index.d.ts +1 -1
- package/package.json +1 -1
- package/sample-mocks/api/user/friends.GET.500.txt +7 -0
- package/sample-mocks/api/user/loves.GET.200.js +7 -0
- package/src/Api.js +0 -15
- package/src/ApiConstants.js +1 -3
- package/src/Config.js +4 -4
- package/src/Dashboard.css +0 -6
- package/src/Dashboard.js +4 -53
- package/src/MockBroker.js +5 -20
- package/src/MockDispatcher.js +13 -22
- package/src/mockBrokersCollection.js +3 -1
- package/sample-mocks/api/user/.GET.500.txt +0 -7
- package/sample-mocks/api/user/edit-name.PATCH.500.txt +0 -0
- package/sample-mocks/api/user/likes.GET.500.txt +0 -0
- package/sample-mocks/api/user/logout.POST.500.txt +0 -0
- package/sample-mocks/api/user/videos.GET.500.txt +0 -0
- package/sample-mocks/api/video/[id].GET.500.txt +0 -0
- package/sample-mocks/api/video/list(concat newly uploaded).GET.200.mjs +0 -8
- package/sample-mocks/api/video/list.GET.200.json +0 -11
- package/sample-mocks/api/video/list.GET.500.txt +0 -0
- package/sample-mocks/api/video/stat/[stat-id]/[video-id].GET.500.txt +0 -0
- package/sample-mocks/api/video/stat/[stat-id]/all-videos?limit=[limit].GET.500.txt +0 -0
- package/sample-mocks/api/video/upload(insert newly uploaded).POST.201.mjs +0 -10
- package/sample-mocks/api/video/upload.POST.201.json +0 -3
- package/sample-mocks/api/video/upload.POST.500.txt +0 -0
package/README-dashboard.png
CHANGED
|
Binary file
|
package/README.md
CHANGED
|
@@ -16,8 +16,7 @@ be used for downloading a TAR of your XHR requests following that convention.
|
|
|
16
16
|
### Mock Variants
|
|
17
17
|
Each route can have many mocks, which could either be:
|
|
18
18
|
- Different response __status code__.
|
|
19
|
-
- e.g. for testing error responses.
|
|
20
|
-
Error_ mock is autogenerated for routes that have no 500.
|
|
19
|
+
- e.g. for testing error responses.
|
|
21
20
|
- __Comment__ on the filename, which is anything within parentheses.
|
|
22
21
|
- e.g. `api/user(my-comment).POST.201.json`
|
|
23
22
|
|
|
@@ -36,6 +35,15 @@ export default [
|
|
|
36
35
|
]
|
|
37
36
|
```
|
|
38
37
|
|
|
38
|
+
Or, export default a function. There, you
|
|
39
|
+
can override the response status and the default JSON content
|
|
40
|
+
type. But don’t call `response.end()`, just return a string.
|
|
41
|
+
```js
|
|
42
|
+
export default function (req, response) {
|
|
43
|
+
return JSON.stringify({ a: 1 })
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
39
47
|
|
|
40
48
|
### Proxying Routes
|
|
41
49
|
`Config.proxyFallback` lets you specify a target
|
|
@@ -47,7 +55,7 @@ The best way to learn _Mockaton_ is by checking out this repo and
|
|
|
47
55
|
exploring its [sample-mocks/](./sample-mocks) directory. Then, run
|
|
48
56
|
[`./_usage_example.js`](./_usage_example.js) and you’ll see this dashboard:
|
|
49
57
|
|
|
50
|
-
|
|
58
|
+
<img src="./README-dashboard.png" style="max-width:890px"/>
|
|
51
59
|
|
|
52
60
|
|
|
53
61
|
## Delay 🕓
|
|
@@ -86,10 +94,10 @@ interface Config {
|
|
|
86
94
|
port?: number // defaults to 0, which means auto-assigned
|
|
87
95
|
delay?: number // defaults to 1200 (ms)
|
|
88
96
|
cookies?: object
|
|
89
|
-
database?: object // for "Transforms"
|
|
90
97
|
skipOpen?: boolean // Prevents opening the dashboard in a browser
|
|
91
98
|
proxyFallback?: string // e.g. http://localhost:9999 Target for relaying routes without mocks
|
|
92
|
-
allowedExt?: RegExp // /\.(json|txt|md|js
|
|
99
|
+
allowedExt?: RegExp // /\.(json|txt|md|js)$/ Just for excluding temporary editor files (e.g. JetBrains appends a ~)
|
|
100
|
+
generate500?: boolean // autogenerates an Internal Server Error empty mock for routes that have no 500
|
|
93
101
|
}
|
|
94
102
|
```
|
|
95
103
|
|
|
@@ -119,10 +127,9 @@ words, it’s useful if you only care about its payload.
|
|
|
119
127
|
### Extension
|
|
120
128
|
`.Method.HttpResponseStatusCode.FileExt`
|
|
121
129
|
|
|
122
|
-
The **file extension** can anything, but `.md`
|
|
123
|
-
for documentation, and mock processors (more on that later).
|
|
130
|
+
The **file extension** can be anything, but `.md` is reserved for documentation.
|
|
124
131
|
|
|
125
|
-
The `Config.allowedExt` regex defaults to: `/\.(json|txt|md|
|
|
132
|
+
The `Config.allowedExt` regex defaults to: `/\.(json|txt|md|js)$/`
|
|
126
133
|
|
|
127
134
|
|
|
128
135
|
### Dynamic Parameters
|
|
@@ -175,19 +182,6 @@ api/foo/[user-id].POST.201.md
|
|
|
175
182
|
api/foo/[user-id].POST.201.json
|
|
176
183
|
```
|
|
177
184
|
|
|
178
|
-
## Transforms (.mjs)
|
|
179
|
-
Using the same filename convention, files ending
|
|
180
|
-
with `.mjs` will process the mock before serving it.
|
|
181
|
-
|
|
182
|
-
For example, this handler will capitalize the mock body and increment a counter.
|
|
183
|
-
```js
|
|
184
|
-
export default function capitalizeAllText(mockAsText, requestBody, config) {
|
|
185
|
-
config.database.myCount ??= 0
|
|
186
|
-
config.database.myCount++
|
|
187
|
-
return mockAsText.toUpperCase()
|
|
188
|
-
}
|
|
189
|
-
```
|
|
190
|
-
|
|
191
185
|
|
|
192
186
|
## API
|
|
193
187
|
|
|
@@ -234,14 +228,6 @@ Sends a list of the available cookies along with a flag indicated if it’s the
|
|
|
234
228
|
fetch(addr + '/mockaton/cookies')
|
|
235
229
|
```
|
|
236
230
|
|
|
237
|
-
### Select a Transform
|
|
238
|
-
```js
|
|
239
|
-
fetch(addr + '/mockaton/transform', {
|
|
240
|
-
method: 'PATCH',
|
|
241
|
-
body: JSON.stringify('api/video/list(concat newly uploaded).GET.200.mjs')
|
|
242
|
-
})
|
|
243
|
-
```
|
|
244
|
-
|
|
245
231
|
### Update Fallback Proxy
|
|
246
232
|
```js
|
|
247
233
|
fetch(addr + '/mockaton/fallback', {
|
package/Tests.js
CHANGED
|
@@ -110,7 +110,8 @@ const server = Mockaton({
|
|
|
110
110
|
cookies: {
|
|
111
111
|
userA: 'CookieA',
|
|
112
112
|
userB: 'CookieB'
|
|
113
|
-
}
|
|
113
|
+
},
|
|
114
|
+
generate500: true
|
|
114
115
|
})
|
|
115
116
|
server.on('listening', runTests)
|
|
116
117
|
|
|
@@ -121,8 +122,6 @@ async function runTests() {
|
|
|
121
122
|
for (const [url, file, body] of fixtures)
|
|
122
123
|
await testMockDispatching(url, file, body)
|
|
123
124
|
|
|
124
|
-
await testMockDispatching('/api/object', 'api/object.GET.200.js', { JSON_FROM_JS: true }, undefined, mimeFor('.json'))
|
|
125
|
-
|
|
126
125
|
await testItUpdatesDelayAndFile(
|
|
127
126
|
'/api/alternative',
|
|
128
127
|
'api/alternative(comment-2).GET.200.json',
|
|
@@ -159,9 +158,11 @@ async function runTests() {
|
|
|
159
158
|
await reset()
|
|
160
159
|
for (const [url, file, body] of fixtures)
|
|
161
160
|
await testMockDispatching(url, file, body)
|
|
161
|
+
|
|
162
|
+
await testMockDispatching('/api/object', 'api/object.GET.200.js', { JSON_FROM_JS: true }, mimeFor('.json'))
|
|
163
|
+
await testJsFunctionMocks()
|
|
162
164
|
|
|
163
165
|
await testItUpdatesUserRole()
|
|
164
|
-
await testTransforms()
|
|
165
166
|
await testStaticFileServing()
|
|
166
167
|
await testInvalidFilenamesAreIgnored()
|
|
167
168
|
await testEnableFallbackSoRoutesWithoutMocksGetRelayed()
|
|
@@ -190,11 +191,11 @@ async function test404() {
|
|
|
190
191
|
})
|
|
191
192
|
}
|
|
192
193
|
|
|
193
|
-
async function testMockDispatching(url, file, expectedBody,
|
|
194
|
+
async function testMockDispatching(url, file, expectedBody, forcedMime = void 0) {
|
|
194
195
|
const { urlMask, method, status } = Route.parseFilename(file)
|
|
195
196
|
const mime = forcedMime || mimeFor(file)
|
|
196
197
|
const now = new Date()
|
|
197
|
-
const res = await request(url, { method
|
|
198
|
+
const res = await request(url, { method })
|
|
198
199
|
const body = mime === 'application/json'
|
|
199
200
|
? await res.json()
|
|
200
201
|
: await res.text()
|
|
@@ -305,25 +306,18 @@ async function testItUpdatesUserRole() {
|
|
|
305
306
|
})
|
|
306
307
|
}
|
|
307
308
|
|
|
308
|
-
async function
|
|
309
|
-
await describe('
|
|
310
|
-
write('api/
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
body.push(reqBody[0]);
|
|
315
|
-
body.push(config.mocksDir);
|
|
316
|
-
return JSON.stringify(body);
|
|
309
|
+
async function testJsFunctionMocks() {
|
|
310
|
+
await describe('JS Function Mocks', async () => {
|
|
311
|
+
write('api/js-func.POST.200.js', `
|
|
312
|
+
export default function (req, response) {
|
|
313
|
+
response.setHeader('content-type', 'custom-mime')
|
|
314
|
+
return 'SOME_STRING'
|
|
317
315
|
}`)
|
|
318
|
-
await reset() // for registering the
|
|
319
|
-
await
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
await testMockDispatching('/api/transform',
|
|
324
|
-
'api/transform.POST.200.json',
|
|
325
|
-
['initial', 'another', tmpDir],
|
|
326
|
-
JSON.stringify(['another']))
|
|
316
|
+
await reset() // for registering the file
|
|
317
|
+
await testMockDispatching('/api/js-func',
|
|
318
|
+
'api/js-func.POST.200.js',
|
|
319
|
+
'SOME_STRING',
|
|
320
|
+
'custom-mime')
|
|
327
321
|
})
|
|
328
322
|
}
|
|
329
323
|
|
package/index.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
This is a plain text response for (/api/friends).
|
|
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.
|
package/src/Api.js
CHANGED
|
@@ -28,7 +28,6 @@ export const apiPatchRequests = new Map([
|
|
|
28
28
|
[API.edit, updateBroker],
|
|
29
29
|
[API.reset, reinitialize],
|
|
30
30
|
[API.cookies, selectCookie],
|
|
31
|
-
[API.transform, updateBrokerTransform],
|
|
32
31
|
[API.fallback, updateProxyFallback]
|
|
33
32
|
])
|
|
34
33
|
|
|
@@ -61,7 +60,6 @@ async function selectCookie(req, response) {
|
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
function reinitialize(_, response) {
|
|
64
|
-
Config.database = {}
|
|
65
63
|
mockBrokersCollection.init()
|
|
66
64
|
sendOK(response)
|
|
67
65
|
}
|
|
@@ -92,19 +90,6 @@ async function bulkUpdateBrokersByCommentTag(req, response) {
|
|
|
92
90
|
}
|
|
93
91
|
}
|
|
94
92
|
|
|
95
|
-
async function updateBrokerTransform(req, response) {
|
|
96
|
-
try {
|
|
97
|
-
const file = await parseJSON(req)
|
|
98
|
-
const broker = mockBrokersCollection.getBrokerByFilename(file)
|
|
99
|
-
broker.updateTransform(file)
|
|
100
|
-
sendOK(response)
|
|
101
|
-
}
|
|
102
|
-
catch (error) {
|
|
103
|
-
console.error(error)
|
|
104
|
-
sendBadRequest(response)
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
93
|
async function updateProxyFallback(req, response) {
|
|
109
94
|
try {
|
|
110
95
|
Config.proxyFallback = await parseJSON(req)
|
package/src/ApiConstants.js
CHANGED
|
@@ -6,13 +6,11 @@ export const API = {
|
|
|
6
6
|
edit: MOUNT + '/edit',
|
|
7
7
|
mocks: MOUNT + '/mocks',
|
|
8
8
|
reset: MOUNT + '/reset',
|
|
9
|
-
transform: MOUNT + '/transform',
|
|
10
9
|
cookies: MOUNT + '/cookies',
|
|
11
10
|
fallback: MOUNT + '/fallback'
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
export const DF = { // Dashboard Fields (XHR)
|
|
15
14
|
delayed: 'delayed',
|
|
16
|
-
file: 'file'
|
|
17
|
-
isForDashboard: 'mock_request_payload'
|
|
15
|
+
file: 'file'
|
|
18
16
|
}
|
package/src/Config.js
CHANGED
|
@@ -9,10 +9,10 @@ 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
12
|
skipOpen: false,
|
|
14
13
|
proxyFallback: '', // e.g. http://localhost:9999
|
|
15
|
-
allowedExt: /\.(json|txt|md|js
|
|
14
|
+
allowedExt: /\.(json|txt|md|js)$/, // Just for excluding temporary editor files (e.g. JetBrains appends a ~)
|
|
15
|
+
generate500: false
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export function setup(options) {
|
|
@@ -24,10 +24,10 @@ export function setup(options) {
|
|
|
24
24
|
port: port => Number.isInteger(port) && port >= 0 && port < 2 ** 16,
|
|
25
25
|
delay: ms => Number.isInteger(ms) && ms > 0,
|
|
26
26
|
cookies: is(Object),
|
|
27
|
-
database: is(Object),
|
|
28
27
|
skipOpen: is(Boolean),
|
|
29
28
|
proxyFallback: is(String),
|
|
30
|
-
allowedExt: is(RegExp)
|
|
29
|
+
allowedExt: is(RegExp),
|
|
30
|
+
generate500: is(Boolean)
|
|
31
31
|
})
|
|
32
32
|
}
|
|
33
33
|
|
package/src/Dashboard.css
CHANGED
|
@@ -135,17 +135,11 @@ main {
|
|
|
135
135
|
.BulkSelectSection {
|
|
136
136
|
margin: 20px 0;
|
|
137
137
|
}
|
|
138
|
-
.TransformsSection {
|
|
139
|
-
padding-top: 30px;
|
|
140
|
-
border-top: 1px solid #ccc;
|
|
141
|
-
margin: 30px 0;
|
|
142
|
-
}
|
|
143
138
|
|
|
144
139
|
.BulkSelectSection select {
|
|
145
140
|
margin-top: 5px;
|
|
146
141
|
}
|
|
147
142
|
|
|
148
|
-
.TransformSelector,
|
|
149
143
|
.MockSelector {
|
|
150
144
|
width: 300px;
|
|
151
145
|
padding: 8px 1px;
|
package/src/Dashboard.js
CHANGED
|
@@ -10,11 +10,9 @@ const Strings = {
|
|
|
10
10
|
empty_response_body: '/* Empty Response Body */',
|
|
11
11
|
fetching: '⌚ Fetching…',
|
|
12
12
|
mock: 'Mock',
|
|
13
|
-
none: 'None',
|
|
14
13
|
reset: 'Reset',
|
|
15
14
|
select_one: 'Select One',
|
|
16
|
-
title: 'Mockaton'
|
|
17
|
-
transforms: 'Transforms'
|
|
15
|
+
title: 'Mockaton'
|
|
18
16
|
}
|
|
19
17
|
|
|
20
18
|
const CSS = {
|
|
@@ -26,8 +24,6 @@ const CSS = {
|
|
|
26
24
|
PayloadViewer: 'PayloadViewer',
|
|
27
25
|
PreviewLink: 'PreviewLink',
|
|
28
26
|
TitleWrap: 'TitleWrap',
|
|
29
|
-
TransformSelector: 'TransformSelector',
|
|
30
|
-
TransformsSection: 'TransformsSection',
|
|
31
27
|
|
|
32
28
|
bold: 'bold',
|
|
33
29
|
chosen: 'chosen',
|
|
@@ -74,10 +70,7 @@ function DevPanel(brokersByMethod, cookies, comments) {
|
|
|
74
70
|
r('div', { className: CSS.PayloadViewer },
|
|
75
71
|
r('pre', { ref: refDocumentation, className: CSS.Documentation }),
|
|
76
72
|
r('h2', { ref: refPayloadFile }, Strings.mock),
|
|
77
|
-
r('pre', { ref: refPayloadViewer }, Strings.click_link_to_preview)))
|
|
78
|
-
r('div', { className: CSS.TransformsSection },
|
|
79
|
-
r('h2', null, Strings.transforms),
|
|
80
|
-
r(Transforms, { brokersByMethod }))))
|
|
73
|
+
r('pre', { ref: refPayloadViewer }, Strings.click_link_to_preview)))))
|
|
81
74
|
}
|
|
82
75
|
|
|
83
76
|
|
|
@@ -142,7 +135,7 @@ function SectionByMethod({ method, brokers }) {
|
|
|
142
135
|
r('th', null, method),
|
|
143
136
|
Object.entries(brokers)
|
|
144
137
|
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
145
|
-
.filter(([, broker]) => broker.mocks.length) // handles Markdown doc
|
|
138
|
+
.filter(([, broker]) => broker.mocks.length) // handles Markdown doc
|
|
146
139
|
.map(([urlMask, broker]) =>
|
|
147
140
|
r('tr', null,
|
|
148
141
|
r('td', null, r(PreviewLink, { method, urlMask, documentation: broker.documentation })),
|
|
@@ -168,8 +161,7 @@ function PreviewLink({ method, urlMask, documentation }) {
|
|
|
168
161
|
|
|
169
162
|
const spinner = setTimeout(() => refPayloadViewer.current.innerText = Strings.fetching, 180)
|
|
170
163
|
const res = await fetch(this.href, {
|
|
171
|
-
method: this.getAttribute('data-method')
|
|
172
|
-
headers: { [DF.isForDashboard]: '1' }
|
|
164
|
+
method: this.getAttribute('data-method')
|
|
173
165
|
})
|
|
174
166
|
document.querySelector(`.${CSS.PreviewLink}.${CSS.chosen}`)?.classList.remove(CSS.chosen)
|
|
175
167
|
this.classList.add(CSS.chosen)
|
|
@@ -246,47 +238,6 @@ function TimerIcon() {
|
|
|
246
238
|
}
|
|
247
239
|
|
|
248
240
|
|
|
249
|
-
function Transforms({ brokersByMethod }) {
|
|
250
|
-
const brokersWithTransforms = []
|
|
251
|
-
for (const brokers of Object.values(brokersByMethod))
|
|
252
|
-
for (const [urlMask, broker] of Object.entries(brokers))
|
|
253
|
-
if (broker.transforms.length)
|
|
254
|
-
brokersWithTransforms.push([urlMask, broker])
|
|
255
|
-
return (
|
|
256
|
-
r('table', null, brokersWithTransforms.map(([urlMask, broker]) =>
|
|
257
|
-
r('tr', null,
|
|
258
|
-
r('td', null, r(PreviewLink, { method: broker.method, urlMask })),
|
|
259
|
-
r('td', null, r(TransformSelector, {
|
|
260
|
-
items: ['', ...broker.transforms],
|
|
261
|
-
selected: broker.currentTransform
|
|
262
|
-
})))
|
|
263
|
-
)))
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function TransformSelector({ items, selected }) {
|
|
267
|
-
const className = defaultIsSelected => cssClass(
|
|
268
|
-
CSS.TransformSelector,
|
|
269
|
-
!defaultIsSelected && CSS.bold)
|
|
270
|
-
return (
|
|
271
|
-
r('select', {
|
|
272
|
-
className: className(selected === items[0]),
|
|
273
|
-
autocomplete: 'off',
|
|
274
|
-
onChange() {
|
|
275
|
-
fetch(API.transform, {
|
|
276
|
-
method: 'PATCH',
|
|
277
|
-
body: JSON.stringify(this.value)
|
|
278
|
-
}).then(() => {
|
|
279
|
-
this.closest('tr').querySelector('a').click()
|
|
280
|
-
this.className = className(this.value === this.options[0].value)
|
|
281
|
-
})
|
|
282
|
-
}
|
|
283
|
-
}, items.map(item =>
|
|
284
|
-
r('option', {
|
|
285
|
-
value: item,
|
|
286
|
-
selected: item === selected
|
|
287
|
-
}, item || Strings.none))))
|
|
288
|
-
}
|
|
289
|
-
|
|
290
241
|
|
|
291
242
|
/* === Utils === */
|
|
292
243
|
function cssClass(...args) {
|
package/src/MockBroker.js
CHANGED
|
@@ -5,9 +5,9 @@ import { Route } from './Route.js'
|
|
|
5
5
|
import { Config } from './Config.js'
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
// MockBroker is a state for a particular route. It knows the available
|
|
9
|
-
// that can be served for the route, the currently selected
|
|
10
|
-
// knows if the route has
|
|
8
|
+
// MockBroker is a state for a particular route. It knows the available
|
|
9
|
+
// mock files that can be served for the route, the currently selected
|
|
10
|
+
// file, and its delay. Also, knows if the route has documentation (md).
|
|
11
11
|
export class MockBroker {
|
|
12
12
|
#route
|
|
13
13
|
|
|
@@ -17,23 +17,18 @@ export class MockBroker {
|
|
|
17
17
|
|
|
18
18
|
this.documentation = '' // .md
|
|
19
19
|
|
|
20
|
-
this.mocks = [] // *.json,txt
|
|
20
|
+
this.mocks = [] // *.json,txt,js
|
|
21
21
|
this.currentMock = {
|
|
22
22
|
file: '',
|
|
23
23
|
delay: 0
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
this.transforms = [] // *.mjs
|
|
27
|
-
this.currentTransform = ''
|
|
28
|
-
|
|
29
26
|
this.register(file)
|
|
30
27
|
}
|
|
31
28
|
|
|
32
29
|
register(file) {
|
|
33
30
|
if (file.endsWith('.md'))
|
|
34
31
|
this.documentation = file
|
|
35
|
-
else if (file.endsWith('.mjs'))
|
|
36
|
-
this.transforms.push(file)
|
|
37
32
|
else {
|
|
38
33
|
if (!this.mocks.length)
|
|
39
34
|
this.currentMock.file = file // The first mock file option for a particular route becomes the default
|
|
@@ -55,26 +50,17 @@ export class MockBroker {
|
|
|
55
50
|
this.currentMock.delay = Number(delayed) * Config.delay
|
|
56
51
|
}
|
|
57
52
|
|
|
58
|
-
updateTransform(filename) {
|
|
59
|
-
this.currentTransform = filename
|
|
60
|
-
}
|
|
61
|
-
|
|
62
53
|
setByMatchingComment(comment) {
|
|
63
54
|
for (const file of this.mocks)
|
|
64
55
|
if (Route.hasInParentheses(file, comment)) {
|
|
65
56
|
this.updateFile(file)
|
|
66
57
|
break
|
|
67
58
|
}
|
|
68
|
-
for (const file of this.transforms)
|
|
69
|
-
if (Route.hasInParentheses(file, comment)) {
|
|
70
|
-
this.updateTransform(file)
|
|
71
|
-
break
|
|
72
|
-
}
|
|
73
59
|
}
|
|
74
60
|
|
|
75
61
|
extractComments() {
|
|
76
62
|
let comments = []
|
|
77
|
-
for (const file of
|
|
63
|
+
for (const file of this.mocks)
|
|
78
64
|
comments = comments.concat(Route.extractComments(file))
|
|
79
65
|
return comments
|
|
80
66
|
}
|
|
@@ -90,7 +76,6 @@ export class MockBroker {
|
|
|
90
76
|
}
|
|
91
77
|
|
|
92
78
|
#write500() {
|
|
93
|
-
// TODO handle route with transforms but without mocks
|
|
94
79
|
const { urlMask, method } = Route.parseFilename(this.mocks[0])
|
|
95
80
|
let mask = urlMask
|
|
96
81
|
const t = join(Config.mocksDir, urlMask)
|
package/src/MockDispatcher.js
CHANGED
|
@@ -28,25 +28,26 @@ export async function dispatchMock(req, response) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
try {
|
|
31
|
-
const { file, status, delay
|
|
31
|
+
const { file, status, delay } = broker
|
|
32
32
|
console.log('\n', req.url, '→\n ', file)
|
|
33
33
|
|
|
34
|
-
const shouldJavaScriptToJSON = file.endsWith('.js')
|
|
35
34
|
response.statusCode = status
|
|
36
|
-
response.setHeader('content-type', mimeFor(shouldJavaScriptToJSON ? '.json' : file))
|
|
37
35
|
if (cookie.getCurrent())
|
|
38
36
|
response.setHeader('set-cookie', cookie.getCurrent())
|
|
39
37
|
|
|
40
|
-
let
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
38
|
+
let mockText
|
|
39
|
+
if (file.endsWith('.js')) {
|
|
40
|
+
response.setHeader('content-type', mimeFor('.json'))
|
|
41
|
+
const jsExport = await importDefault(file)
|
|
42
|
+
mockText = typeof jsExport === 'function'
|
|
43
|
+
? jsExport(req, response)
|
|
44
|
+
: JSON.stringify(jsExport)
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
response.setHeader('content-type', mimeFor(file))
|
|
48
|
+
mockText = readMock(file)
|
|
48
49
|
}
|
|
49
|
-
setTimeout(() => response.end(
|
|
50
|
+
setTimeout(() => response.end(mockText), delay)
|
|
50
51
|
}
|
|
51
52
|
catch (error) {
|
|
52
53
|
console.error(error)
|
|
@@ -59,16 +60,6 @@ export async function dispatchMock(req, response) {
|
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
const nonSafeMethods = ['PATCH', 'POST', 'PUT', 'DELETE', 'CONNECT']
|
|
63
|
-
|
|
64
|
-
async function requestBodyForTransform(req, mockAsText) {
|
|
65
|
-
if (nonSafeMethods.includes(req.method))
|
|
66
|
-
return req.headers[DF.isForDashboard] // TODO unit TESTME
|
|
67
|
-
? JSON.parse(mockAsText)
|
|
68
|
-
: await parseJSON(req)
|
|
69
|
-
return ''
|
|
70
|
-
}
|
|
71
|
-
|
|
72
63
|
function readMock(file) {
|
|
73
64
|
return readFileSync(join(Config.mocksDir, file), 'utf8')
|
|
74
65
|
}
|
|
@@ -39,7 +39,9 @@ export function init() {
|
|
|
39
39
|
else
|
|
40
40
|
collection[method][urlMask].register(file)
|
|
41
41
|
}
|
|
42
|
-
|
|
42
|
+
|
|
43
|
+
if (Config.generate500)
|
|
44
|
+
forEachBroker(broker => broker.ensureItHas500())
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
function forEachBroker(fn) {
|
|
@@ -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 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.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
// This is an example "transform". It takes the mock for the same route as
|
|
2
|
-
// input, so you can modify it. In this case, it uses the `database` field.
|
|
3
|
-
|
|
4
|
-
export default function concatNewlyUploadedVideos(mockAsText, _, config) {
|
|
5
|
-
const mockList = JSON.parse(mockAsText)
|
|
6
|
-
mockList.videos = mockList.videos.concat(config.database.videos || [])
|
|
7
|
-
return JSON.stringify(mockList, null, 2)
|
|
8
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
// An example "transform" for saving a POST request payload into the `config.database`
|
|
2
|
-
|
|
3
|
-
export default function concatNewlyUploadedVideos(mockAsText, requestBody, config) {
|
|
4
|
-
config.database.videos ??= []
|
|
5
|
-
config.database.videos.push({
|
|
6
|
-
createdAt: Date.now(),
|
|
7
|
-
...requestBody
|
|
8
|
-
})
|
|
9
|
-
return JSON.stringify({})
|
|
10
|
-
}
|
|
File without changes
|