mockaton 8.9.1 → 8.11.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.md +18 -0
- package/package.json +1 -1
- package/src/Api.js +8 -1
- package/src/ApiConstants.js +1 -0
- package/src/Commander.js +7 -0
- package/src/Dashboard.css +29 -15
- package/src/Dashboard.js +34 -7
- package/src/Filename.js +13 -4
- package/src/MockBroker.js +3 -4
- package/src/MockDispatcher.js +2 -2
- package/src/Mockaton.test.js +8 -2
- package/src/ProxyRelay.js +2 -2
package/README.md
CHANGED
|
@@ -224,6 +224,24 @@ api/user.GET.200.json
|
|
|
224
224
|
You can also use `.empty` or `.unknown` if you don’t
|
|
225
225
|
want a `Content-Type` header in the response.
|
|
226
226
|
|
|
227
|
+
<details>
|
|
228
|
+
<summary>Supported Methods</summary>
|
|
229
|
+
<p>From Node.js <code>http.METHODS</code></p>
|
|
230
|
+
<p>
|
|
231
|
+
ACL, BIND, CHECKOUT,
|
|
232
|
+
CONNECT, COPY, DELETE,
|
|
233
|
+
GET, HEAD, LINK,
|
|
234
|
+
LOCK, M-SEARCH, MERGE,
|
|
235
|
+
MKACTIVITY, MKCALENDAR, MKCOL,
|
|
236
|
+
MOVE, NOTIFY, OPTIONS,
|
|
237
|
+
PATCH, POST, PROPFIND,
|
|
238
|
+
PROPPATCH, PURGE, PUT,
|
|
239
|
+
QUERY, REBIND, REPORT,
|
|
240
|
+
SEARCH, SOURCE, SUBSCRIBE,
|
|
241
|
+
TRACE, UNBIND, UNLINK,
|
|
242
|
+
UNLOCK, UNSUBSCRIBE
|
|
243
|
+
</p>
|
|
244
|
+
</details>
|
|
227
245
|
|
|
228
246
|
### Dynamic Parameters
|
|
229
247
|
Anything within square brackets is always matched. For example, for this route
|
package/package.json
CHANGED
package/src/Api.js
CHANGED
|
@@ -34,6 +34,7 @@ export const apiGetRequests = new Map([
|
|
|
34
34
|
[API.fallback, getProxyFallback],
|
|
35
35
|
[API.arEvents, longPollAR_Events],
|
|
36
36
|
[API.comments, listComments],
|
|
37
|
+
[API.globalDelay, getGlobalDelay],
|
|
37
38
|
[API.collectProxied, getCollectProxied]
|
|
38
39
|
])
|
|
39
40
|
|
|
@@ -46,6 +47,7 @@ export const apiPatchRequests = new Map([
|
|
|
46
47
|
[API.cookies, selectCookie],
|
|
47
48
|
[API.fallback, updateProxyFallback],
|
|
48
49
|
[API.bulkSelect, bulkUpdateBrokersByCommentTag],
|
|
50
|
+
[API.globalDelay, setGlobalDelay],
|
|
49
51
|
[API.collectProxied, setCollectProxied]
|
|
50
52
|
])
|
|
51
53
|
|
|
@@ -65,6 +67,7 @@ function serveDashboardAsset(req, response) {
|
|
|
65
67
|
|
|
66
68
|
function listCookies(_, response) { sendJSON(response, cookie.list()) }
|
|
67
69
|
function listComments(_, response) { sendJSON(response, mockBrokersCollection.extractAllComments()) }
|
|
70
|
+
function getGlobalDelay(_, response) { sendJSON(response, config.delay) }
|
|
68
71
|
function listMockBrokers(_, response) { sendJSON(response, mockBrokersCollection.getAll()) }
|
|
69
72
|
function getProxyFallback(_, response) { sendJSON(response, config.proxyFallback) }
|
|
70
73
|
function getIsCorsAllowed(_, response) { sendJSON(response, config.corsAllowed) }
|
|
@@ -137,7 +140,7 @@ async function setRouteIsDelayed(req, response) {
|
|
|
137
140
|
else if (typeof delayed !== 'boolean')
|
|
138
141
|
sendUnprocessableContent(response, `Expected a boolean for "delayed"`) // TESTME
|
|
139
142
|
else {
|
|
140
|
-
broker.
|
|
143
|
+
broker.updateDelayed(body[DF.delayed])
|
|
141
144
|
sendOK(response)
|
|
142
145
|
}
|
|
143
146
|
}
|
|
@@ -188,3 +191,7 @@ async function setCorsAllowed(req, response) {
|
|
|
188
191
|
sendOK(response)
|
|
189
192
|
}
|
|
190
193
|
|
|
194
|
+
async function setGlobalDelay(req, response) { // TESTME
|
|
195
|
+
config.delay = parseInt(await parseJSON(req), 10)
|
|
196
|
+
sendOK(response)
|
|
197
|
+
}
|
package/src/ApiConstants.js
CHANGED
package/src/Commander.js
CHANGED
|
@@ -77,6 +77,13 @@ export class Commander {
|
|
|
77
77
|
return this.#patch(API.cors, value)
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
getGlobalDelay() {
|
|
81
|
+
return this.#get(API.globalDelay)
|
|
82
|
+
}
|
|
83
|
+
setGlobalDelay(delay) {
|
|
84
|
+
return this.#patch(API.globalDelay, delay)
|
|
85
|
+
}
|
|
86
|
+
|
|
80
87
|
listStaticFiles() {
|
|
81
88
|
return this.#get(API.static)
|
|
82
89
|
}
|
package/src/Dashboard.css
CHANGED
|
@@ -112,7 +112,7 @@ select {
|
|
|
112
112
|
border-bottom: 1px solid rgba(127, 127, 127, 0.1);
|
|
113
113
|
background: var(--colorHeaderBackground);
|
|
114
114
|
box-shadow: var(--boxShadow1);
|
|
115
|
-
gap:
|
|
115
|
+
gap: 10px;
|
|
116
116
|
|
|
117
117
|
img {
|
|
118
118
|
width: 130px;
|
|
@@ -129,16 +129,26 @@ select {
|
|
|
129
129
|
color: var(--colorLabel);
|
|
130
130
|
font-size: 11px;
|
|
131
131
|
gap: 4px;
|
|
132
|
+
|
|
133
|
+
svg {
|
|
134
|
+
width: 14px;
|
|
135
|
+
height: 14px;
|
|
136
|
+
fill: var(--colorLabel);
|
|
137
|
+
opacity: 0.6;
|
|
138
|
+
}
|
|
132
139
|
}
|
|
133
140
|
|
|
134
141
|
input[type=url],
|
|
142
|
+
input[type=number],
|
|
135
143
|
select {
|
|
136
144
|
width: 100%;
|
|
137
145
|
height: 28px;
|
|
138
146
|
padding: 4px 8px;
|
|
139
147
|
border-right: 3px solid transparent;
|
|
140
148
|
margin-top: 4px;
|
|
149
|
+
color: var(--colorText);
|
|
141
150
|
font-size: 11px;
|
|
151
|
+
box-shadow: var(--boxShadow1);
|
|
142
152
|
background-color: var(--colorComboBoxHeaderBackground);
|
|
143
153
|
border-radius: var(--radius);
|
|
144
154
|
}
|
|
@@ -147,21 +157,26 @@ select {
|
|
|
147
157
|
background: var(--colorHover);
|
|
148
158
|
}
|
|
149
159
|
|
|
150
|
-
&.
|
|
151
|
-
|
|
152
|
-
|
|
160
|
+
&.GlobalDelayField {
|
|
161
|
+
width: 82px;
|
|
162
|
+
|
|
163
|
+
input[type=number] {
|
|
164
|
+
padding-right: 0;
|
|
165
|
+
}
|
|
153
166
|
|
|
154
167
|
svg {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
opacity: 0.6;
|
|
168
|
+
border: 1px solid var(--colorLabel);
|
|
169
|
+
border-radius: 50%;
|
|
170
|
+
transform: scale(.85);
|
|
159
171
|
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
&.FallbackBackend {
|
|
175
|
+
position: relative;
|
|
176
|
+
width: 210px;
|
|
160
177
|
|
|
161
178
|
input[type=url] {
|
|
162
179
|
padding: 0 6px;
|
|
163
|
-
box-shadow: var(--boxShadow1);
|
|
164
|
-
color: var(--colorText);
|
|
165
180
|
}
|
|
166
181
|
|
|
167
182
|
.SaveProxiedCheckbox {
|
|
@@ -279,8 +294,8 @@ select {
|
|
|
279
294
|
.MockSelector {
|
|
280
295
|
width: 260px;
|
|
281
296
|
height: 30px;
|
|
282
|
-
border: 0;
|
|
283
297
|
padding-right: 5px;
|
|
298
|
+
border: 0;
|
|
284
299
|
text-align: right;
|
|
285
300
|
direction: rtl;
|
|
286
301
|
text-overflow: ellipsis;
|
|
@@ -333,7 +348,7 @@ select {
|
|
|
333
348
|
}
|
|
334
349
|
|
|
335
350
|
&:disabled ~ svg {
|
|
336
|
-
opacity: .
|
|
351
|
+
fill-opacity: 0.4;
|
|
337
352
|
cursor: not-allowed;
|
|
338
353
|
box-shadow: none;
|
|
339
354
|
}
|
|
@@ -350,10 +365,9 @@ select {
|
|
|
350
365
|
}
|
|
351
366
|
|
|
352
367
|
.ProxyToggler {
|
|
353
|
-
margin-left: 4px;
|
|
354
368
|
> svg {
|
|
355
|
-
width:
|
|
356
|
-
padding:
|
|
369
|
+
width: 20px;
|
|
370
|
+
padding: 3px;
|
|
357
371
|
border-color: transparent;
|
|
358
372
|
border-radius: var(--radiusSmall);
|
|
359
373
|
}
|
package/src/Dashboard.js
CHANGED
|
@@ -17,6 +17,7 @@ const Strings = {
|
|
|
17
17
|
cookie: 'Cookie',
|
|
18
18
|
cookie_disabled_title: 'No cookies specified in config.cookies',
|
|
19
19
|
delay: 'Delay',
|
|
20
|
+
delay_ms: 'Delay (ms)',
|
|
20
21
|
empty_response_body: '/* Empty Response Body */',
|
|
21
22
|
fallback_server: 'Fallback Backend',
|
|
22
23
|
fallback_server_placeholder: 'Type Server Address',
|
|
@@ -46,6 +47,7 @@ const CSS = {
|
|
|
46
47
|
ProgressBar: 'ProgressBar',
|
|
47
48
|
ProxyToggler: 'ProxyToggler',
|
|
48
49
|
ResetButton: 'ResetButton',
|
|
50
|
+
GlobalDelayField: 'GlobalDelayField',
|
|
49
51
|
SaveProxiedCheckbox: 'SaveProxiedCheckbox',
|
|
50
52
|
StaticFilesList: 'StaticFilesList',
|
|
51
53
|
|
|
@@ -58,6 +60,10 @@ const CSS = {
|
|
|
58
60
|
const r = createElement
|
|
59
61
|
const mockaton = new Commander(window.location.origin)
|
|
60
62
|
|
|
63
|
+
const PROGRESS_BAR_DELAY = 180
|
|
64
|
+
let globalDelay = 1200
|
|
65
|
+
|
|
66
|
+
|
|
61
67
|
init()
|
|
62
68
|
pollAR_Events() // Add or Remove Mocks from File System
|
|
63
69
|
document.addEventListener('visibilitychange', () => {
|
|
@@ -70,6 +76,7 @@ function init() {
|
|
|
70
76
|
mockaton.listMocks(),
|
|
71
77
|
mockaton.listCookies(),
|
|
72
78
|
mockaton.listComments(),
|
|
79
|
+
mockaton.getGlobalDelay(),
|
|
73
80
|
mockaton.getCollectProxied(),
|
|
74
81
|
mockaton.getProxyFallback(),
|
|
75
82
|
mockaton.listStaticFiles()
|
|
@@ -78,22 +85,24 @@ function init() {
|
|
|
78
85
|
.catch(onError)
|
|
79
86
|
}
|
|
80
87
|
|
|
81
|
-
function App([brokersByMethod, cookies, comments, collectProxied, fallbackAddress, staticFiles]) {
|
|
88
|
+
function App([brokersByMethod, cookies, comments, delay, collectProxied, fallbackAddress, staticFiles]) {
|
|
89
|
+
globalDelay = delay
|
|
82
90
|
return (
|
|
83
91
|
r('div', null,
|
|
84
|
-
r(Header, { cookies, comments, fallbackAddress, collectProxied }),
|
|
92
|
+
r(Header, { cookies, comments, delay, fallbackAddress, collectProxied }),
|
|
85
93
|
r(MockList, { brokersByMethod, canProxy: Boolean(fallbackAddress) }),
|
|
86
94
|
r(StaticFilesList, { staticFiles })))
|
|
87
95
|
}
|
|
88
96
|
|
|
89
97
|
// Header ===============
|
|
90
98
|
|
|
91
|
-
function Header({ cookies, comments, fallbackAddress, collectProxied }) {
|
|
99
|
+
function Header({ cookies, comments, delay, fallbackAddress, collectProxied }) {
|
|
92
100
|
return (
|
|
93
101
|
r('menu', { className: CSS.Header },
|
|
94
102
|
r(Logo),
|
|
95
103
|
r(CookieSelector, { cookies }),
|
|
96
104
|
r(BulkSelector, { comments }),
|
|
105
|
+
r(GlobalDelayField, { delay }),
|
|
97
106
|
r(ProxyFallbackField, { fallbackAddress, collectProxied }),
|
|
98
107
|
r(ResetButton)))
|
|
99
108
|
}
|
|
@@ -153,6 +162,24 @@ function BulkSelector({ comments }) {
|
|
|
153
162
|
r('option', { value }, value)))))
|
|
154
163
|
}
|
|
155
164
|
|
|
165
|
+
function GlobalDelayField({ delay }) {
|
|
166
|
+
function onChange() {
|
|
167
|
+
globalDelay = this.valueAsNumber
|
|
168
|
+
mockaton.setGlobalDelay(globalDelay).catch(onError)
|
|
169
|
+
}
|
|
170
|
+
return (
|
|
171
|
+
r('label', { className: cssClass(CSS.Field, CSS.GlobalDelayField) },
|
|
172
|
+
r('span', null, r(TimerIcon), Strings.delay_ms),
|
|
173
|
+
r('input', {
|
|
174
|
+
type: 'number',
|
|
175
|
+
min: 0,
|
|
176
|
+
step: 100,
|
|
177
|
+
autocomplete: 'none',
|
|
178
|
+
value: delay,
|
|
179
|
+
onChange
|
|
180
|
+
})))
|
|
181
|
+
}
|
|
182
|
+
|
|
156
183
|
function ProxyFallbackField({ fallbackAddress, collectProxied }) {
|
|
157
184
|
function onChange() {
|
|
158
185
|
const saveCheckbox = this.closest(`.${CSS.FallbackBackend}`).querySelector('[type=checkbox]')
|
|
@@ -238,8 +265,8 @@ function SectionByMethod({ method, brokers, canProxy }) {
|
|
|
238
265
|
r('tr', { 'data-method': method, 'data-urlMask': urlMask },
|
|
239
266
|
r('td', null, r(PreviewLink, { method, urlMask })),
|
|
240
267
|
r('td', null, r(MockSelector, { broker })),
|
|
241
|
-
r('td', null, r(DelayRouteToggler, { broker })),
|
|
242
268
|
r('td', null, r(InternalServerErrorToggler, { broker })),
|
|
269
|
+
r('td', null, r(DelayRouteToggler, { broker })),
|
|
243
270
|
r('td', null, r(ProxyToggler, { broker, disabled: !canProxy }))))))
|
|
244
271
|
}
|
|
245
272
|
|
|
@@ -313,7 +340,7 @@ function DelayRouteToggler({ broker }) {
|
|
|
313
340
|
},
|
|
314
341
|
r('input', {
|
|
315
342
|
type: 'checkbox',
|
|
316
|
-
checked:
|
|
343
|
+
checked: broker.currentMock.delayed,
|
|
317
344
|
onChange
|
|
318
345
|
}),
|
|
319
346
|
TimerIcon()))
|
|
@@ -386,7 +413,7 @@ function PayloadViewer() {
|
|
|
386
413
|
function PayloadViewerProgressBar() {
|
|
387
414
|
return (
|
|
388
415
|
r('div', { className: CSS.ProgressBar },
|
|
389
|
-
r('div', { style: { animationDuration: '
|
|
416
|
+
r('div', { style: { animationDuration: globalDelay - PROGRESS_BAR_DELAY + 'ms' } })))
|
|
390
417
|
}
|
|
391
418
|
|
|
392
419
|
function PayloadViewerTitle({ file, status, statusText }) {
|
|
@@ -406,7 +433,7 @@ function PayloadViewerTitleWhenProxied({ mime, status, statusText }) {
|
|
|
406
433
|
}
|
|
407
434
|
|
|
408
435
|
async function previewMock(method, urlMask, href) {
|
|
409
|
-
const timer = setTimeout(renderProgressBar,
|
|
436
|
+
const timer = setTimeout(renderProgressBar, PROGRESS_BAR_DELAY)
|
|
410
437
|
const response = await fetch(href, { method })
|
|
411
438
|
clearTimeout(timer)
|
|
412
439
|
await updatePayloadViewer(method, urlMask, response)
|
package/src/Filename.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
const httpMethods = [
|
|
2
|
-
'
|
|
3
|
-
'
|
|
4
|
-
'
|
|
1
|
+
const httpMethods = [ // node:http.METHODS
|
|
2
|
+
'ACL', 'BIND', 'CHECKOUT',
|
|
3
|
+
'CONNECT', 'COPY', 'DELETE',
|
|
4
|
+
'GET', 'HEAD', 'LINK',
|
|
5
|
+
'LOCK', 'M-SEARCH', 'MERGE',
|
|
6
|
+
'MKACTIVITY', 'MKCALENDAR', 'MKCOL',
|
|
7
|
+
'MOVE', 'NOTIFY', 'OPTIONS',
|
|
8
|
+
'PATCH', 'POST', 'PROPFIND',
|
|
9
|
+
'PROPPATCH', 'PURGE', 'PUT',
|
|
10
|
+
'QUERY', 'REBIND', 'REPORT',
|
|
11
|
+
'SEARCH', 'SOURCE', 'SUBSCRIBE',
|
|
12
|
+
'TRACE', 'UNBIND', 'UNLINK',
|
|
13
|
+
'UNLOCK', 'UNSUBSCRIBE'
|
|
5
14
|
]
|
|
6
15
|
|
|
7
16
|
const reComments = /\(.*?\)/g // Anything within parentheses
|
package/src/MockBroker.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { config } from './config.js'
|
|
2
1
|
import { includesComment, extractComments, parseFilename } from './Filename.js'
|
|
3
2
|
import { DEFAULT_500_COMMENT, DEFAULT_MOCK_COMMENT } from './ApiConstants.js'
|
|
4
3
|
|
|
@@ -13,7 +12,7 @@ export class MockBroker {
|
|
|
13
12
|
this.mocks = []
|
|
14
13
|
this.currentMock = {
|
|
15
14
|
file: '',
|
|
16
|
-
|
|
15
|
+
delayed: false
|
|
17
16
|
}
|
|
18
17
|
this.register(file)
|
|
19
18
|
}
|
|
@@ -59,7 +58,7 @@ export class MockBroker {
|
|
|
59
58
|
}
|
|
60
59
|
|
|
61
60
|
get file() { return this.currentMock.file }
|
|
62
|
-
get
|
|
61
|
+
get delayed() { return this.currentMock.delayed }
|
|
63
62
|
get proxied() { return !this.currentMock.file }
|
|
64
63
|
get status() { return parseFilename(this.file).status }
|
|
65
64
|
get temp500IsSelected() { return this.#isTemp500(this.file) }
|
|
@@ -83,7 +82,7 @@ export class MockBroker {
|
|
|
83
82
|
|
|
84
83
|
hasMock(file) { return this.mocks.includes(file) }
|
|
85
84
|
updateFile(filename) { this.currentMock.file = filename }
|
|
86
|
-
|
|
85
|
+
updateDelayed(delayed) { this.currentMock.delayed = delayed }
|
|
87
86
|
|
|
88
87
|
updateProxied(proxied) {
|
|
89
88
|
if (proxied)
|
package/src/MockDispatcher.js
CHANGED
|
@@ -14,7 +14,7 @@ export async function dispatchMock(req, response) {
|
|
|
14
14
|
const broker = mockBrokerCollection.getBrokerForUrl(req.method, req.url)
|
|
15
15
|
if (!broker || broker.proxied) {
|
|
16
16
|
if (config.proxyFallback)
|
|
17
|
-
await proxy(req, response)
|
|
17
|
+
await proxy(req, response, Number(Boolean(broker?.delayed)) * config.delay)
|
|
18
18
|
else
|
|
19
19
|
sendNotFound(response)
|
|
20
20
|
return
|
|
@@ -34,7 +34,7 @@ export async function dispatchMock(req, response) {
|
|
|
34
34
|
: await applyPlugins(join(config.mocksDir, broker.file), req, response)
|
|
35
35
|
|
|
36
36
|
response.setHeader('Content-Type', mime)
|
|
37
|
-
setTimeout(() => response.end(body), broker.delay)
|
|
37
|
+
setTimeout(() => response.end(body), Number(broker.delayed) * config.delay)
|
|
38
38
|
}
|
|
39
39
|
catch (error) {
|
|
40
40
|
if (error instanceof BodyReaderError)
|
package/src/Mockaton.test.js
CHANGED
|
@@ -106,6 +106,12 @@ const fixtures = [
|
|
|
106
106
|
'Decodes URI'
|
|
107
107
|
],
|
|
108
108
|
|
|
109
|
+
[
|
|
110
|
+
'/api/uncommon-method',
|
|
111
|
+
'/api/uncommon-method.ACL.200.json',
|
|
112
|
+
'node.js doesn’t support arbitrary HTTP methods, but it does support a few non-standard ones'
|
|
113
|
+
],
|
|
114
|
+
|
|
109
115
|
|
|
110
116
|
// Dynamic Params
|
|
111
117
|
[
|
|
@@ -352,7 +358,7 @@ async function testRegistering() {
|
|
|
352
358
|
])
|
|
353
359
|
deepEqual(currentMock, {
|
|
354
360
|
file: fixtureForRegisteringPutA[1],
|
|
355
|
-
|
|
361
|
+
delayed: false
|
|
356
362
|
})
|
|
357
363
|
})
|
|
358
364
|
await it('unregisters selected', async () => {
|
|
@@ -367,7 +373,7 @@ async function testRegistering() {
|
|
|
367
373
|
])
|
|
368
374
|
deepEqual(currentMock, {
|
|
369
375
|
file: fixtureForRegisteringPutB[1],
|
|
370
|
-
|
|
376
|
+
delayed: false
|
|
371
377
|
})
|
|
372
378
|
})
|
|
373
379
|
await it('unregistering the last mock removes broker', async () => {
|
package/src/ProxyRelay.js
CHANGED
|
@@ -8,7 +8,7 @@ import { write, isFile } from './utils/fs.js'
|
|
|
8
8
|
import { makeMockFilename } from './Filename.js'
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
export async function proxy(req, response) {
|
|
11
|
+
export async function proxy(req, response, delay) {
|
|
12
12
|
const proxyResponse = await fetch(config.proxyFallback + req.url, {
|
|
13
13
|
method: req.method,
|
|
14
14
|
headers: req.headers,
|
|
@@ -21,7 +21,7 @@ export async function proxy(req, response) {
|
|
|
21
21
|
headers['set-cookie'] = proxyResponse.headers.getSetCookie() // parses multiple into an array
|
|
22
22
|
response.writeHead(proxyResponse.status, headers)
|
|
23
23
|
const body = await proxyResponse.text()
|
|
24
|
-
response.end(body)
|
|
24
|
+
setTimeout(() => response.end(body), delay) // TESTME
|
|
25
25
|
|
|
26
26
|
if (config.collectProxied) {
|
|
27
27
|
const ext = extFor(proxyResponse.headers.get('content-type'))
|