mockaton 13.1.0 → 13.2.1
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/package.json +5 -3
- package/src/client/ApiCommander.js +3 -3
- package/src/client/app-store.js +20 -11
- package/src/server/Api.js +35 -28
- package/src/server/Watcher.js +9 -20
- package/www/src/assets/openapi.json +661 -0
package/package.json
CHANGED
|
@@ -2,17 +2,19 @@
|
|
|
2
2
|
"name": "mockaton",
|
|
3
3
|
"description": "HTTP Mock Server",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "13.1
|
|
5
|
+
"version": "13.2.1",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
8
8
|
"import": "./index.js",
|
|
9
9
|
"types": "./index.d.ts"
|
|
10
|
-
}
|
|
10
|
+
},
|
|
11
|
+
"./openapi.json": "./www/src/assets/openapi.json"
|
|
11
12
|
},
|
|
12
13
|
"files": [
|
|
13
14
|
"src",
|
|
14
15
|
"index.js",
|
|
15
|
-
"index.d.ts"
|
|
16
|
+
"index.d.ts",
|
|
17
|
+
"www/src/assets/openapi.json"
|
|
16
18
|
],
|
|
17
19
|
"license": "MIT",
|
|
18
20
|
"homepage": "https://mockaton.com",
|
|
@@ -42,8 +42,7 @@ export class Commander {
|
|
|
42
42
|
/** @returns {JsonPromise<ClientMockBroker>} */
|
|
43
43
|
toggleStatus = (method, urlMask, status) => this.#patch(API.toggleStatus, [method, urlMask, status])
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
|
|
47
46
|
/** @returns {JsonPromise<ClientMockBroker>} */
|
|
48
47
|
setRouteIsProxied = (method, urlMask, proxied) => this.#patch(API.proxied, [method, urlMask, proxied])
|
|
49
48
|
|
|
@@ -61,7 +60,8 @@ export class Commander {
|
|
|
61
60
|
|
|
62
61
|
|
|
63
62
|
/**
|
|
64
|
-
* SSE - Streams an incremental version when a mock is added, deleted, or renamed
|
|
63
|
+
* SSE - Streams an incremental version when a mock is added, deleted, or renamed.
|
|
64
|
+
* Also, when the internal state changes.
|
|
65
65
|
* @returns {Promise<Response>}
|
|
66
66
|
*/
|
|
67
67
|
getSyncVersion = () => fetch(this.addr + API.syncVersion)
|
package/src/client/app-store.js
CHANGED
|
@@ -10,6 +10,7 @@ export const store = {
|
|
|
10
10
|
onError(err) {},
|
|
11
11
|
render() {},
|
|
12
12
|
renderRow(method, urlMask) {},
|
|
13
|
+
skipNextRender: false,
|
|
13
14
|
|
|
14
15
|
brokersByMethod: /** @type ClientBrokersByMethod */ {},
|
|
15
16
|
|
|
@@ -58,49 +59,53 @@ export const store = {
|
|
|
58
59
|
if (store.showProxyField === null) // isFirstCall
|
|
59
60
|
store.showProxyField = Boolean(store.proxyFallback)
|
|
60
61
|
|
|
61
|
-
store.
|
|
62
|
+
if (store.skipNextRender)
|
|
63
|
+
store.skipNextRender = false
|
|
64
|
+
else
|
|
65
|
+
store.render()
|
|
62
66
|
})
|
|
63
67
|
},
|
|
64
68
|
|
|
65
69
|
reset() {
|
|
66
70
|
store._request(api.reset, () => {
|
|
67
71
|
store.setChosenLink('', '')
|
|
68
|
-
store.fetchState()
|
|
69
72
|
})
|
|
70
73
|
},
|
|
71
74
|
|
|
72
75
|
bulkSelectByComment(value) {
|
|
73
|
-
store._request(() => api.bulkSelectByComment(value)
|
|
74
|
-
store.fetchState()
|
|
75
|
-
})
|
|
76
|
+
store._request(() => api.bulkSelectByComment(value))
|
|
76
77
|
},
|
|
77
78
|
|
|
78
79
|
setGlobalDelay(value) {
|
|
80
|
+
store.skipNextRender = true
|
|
79
81
|
store._request(() => api.setGlobalDelay(value), () => {
|
|
80
82
|
store.delay = value
|
|
81
83
|
})
|
|
82
84
|
},
|
|
83
85
|
|
|
84
86
|
setGlobalDelayJitter(value) {
|
|
87
|
+
store.skipNextRender = true
|
|
85
88
|
store._request(() => api.setGlobalDelayJitter(value), () => {
|
|
86
89
|
store.delayJitter = value
|
|
87
90
|
})
|
|
88
91
|
},
|
|
89
92
|
|
|
90
93
|
selectCookie(name) {
|
|
94
|
+
store.skipNextRender = true
|
|
91
95
|
store._request(() => api.selectCookie(name), async response => {
|
|
92
96
|
store.cookies = await response.json()
|
|
93
97
|
})
|
|
94
98
|
},
|
|
95
99
|
|
|
96
100
|
setProxyFallback(value) {
|
|
101
|
+
store.skipNextRender = true
|
|
97
102
|
store._request(() => api.setProxyFallback(value), () => {
|
|
98
103
|
store.proxyFallback = value
|
|
99
|
-
store.render()
|
|
100
104
|
})
|
|
101
105
|
},
|
|
102
106
|
|
|
103
107
|
setCollectProxied(checked) {
|
|
108
|
+
store.skipNextRender = true
|
|
104
109
|
store._request(() => api.setCollectProxied(checked), () => {
|
|
105
110
|
store.collectProxied = checked
|
|
106
111
|
})
|
|
@@ -111,7 +116,7 @@ export const store = {
|
|
|
111
116
|
return store.brokersByMethod[method]?.[urlMask]
|
|
112
117
|
},
|
|
113
118
|
|
|
114
|
-
|
|
119
|
+
_setBroker(broker) {
|
|
115
120
|
const { method, urlMask } = parseFilename(broker.file)
|
|
116
121
|
store.brokersByMethod[method] ??= {}
|
|
117
122
|
store.brokersByMethod[method][urlMask] = broker
|
|
@@ -153,33 +158,37 @@ export const store = {
|
|
|
153
158
|
},
|
|
154
159
|
|
|
155
160
|
selectFile(file) {
|
|
161
|
+
store.skipNextRender = true
|
|
156
162
|
store._request(() => api.select(file), async response => {
|
|
157
163
|
const { method, urlMask } = parseFilename(file)
|
|
158
|
-
store.
|
|
164
|
+
store._setBroker(await response.json())
|
|
159
165
|
store.setChosenLink(method, urlMask)
|
|
160
166
|
store.renderRow(method, urlMask)
|
|
161
167
|
})
|
|
162
168
|
},
|
|
163
169
|
|
|
164
170
|
toggleStatus(method, urlMask, status) {
|
|
171
|
+
store.skipNextRender = true
|
|
165
172
|
store._request(() => api.toggleStatus(method, urlMask, status), async response => {
|
|
166
|
-
store.
|
|
173
|
+
store._setBroker(await response.json())
|
|
167
174
|
store.setChosenLink(method, urlMask)
|
|
168
175
|
store.renderRow(method, urlMask)
|
|
169
176
|
})
|
|
170
177
|
},
|
|
171
178
|
|
|
172
179
|
setProxied(method, urlMask, checked) {
|
|
180
|
+
store.skipNextRender = true
|
|
173
181
|
store._request(() => api.setRouteIsProxied(method, urlMask, checked), async response => {
|
|
174
|
-
store.
|
|
182
|
+
store._setBroker(await response.json())
|
|
175
183
|
store.setChosenLink(method, urlMask)
|
|
176
184
|
store.renderRow(method, urlMask)
|
|
177
185
|
})
|
|
178
186
|
},
|
|
179
187
|
|
|
180
188
|
setDelayed(method, urlMask, checked) {
|
|
189
|
+
store.skipNextRender = true
|
|
181
190
|
store._request(() => api.setRouteIsDelayed(method, urlMask, checked), async response => {
|
|
182
|
-
store.
|
|
191
|
+
store._setBroker(await response.json())
|
|
183
192
|
})
|
|
184
193
|
}
|
|
185
194
|
}
|
package/src/server/Api.js
CHANGED
|
@@ -5,24 +5,18 @@
|
|
|
5
5
|
|
|
6
6
|
import { join } from 'node:path'
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
sseClientHotReload,
|
|
10
|
-
DASHBOARD_ASSETS,
|
|
11
|
-
CLIENT_DIR
|
|
12
|
-
} from './WatcherDevClient.js'
|
|
13
|
-
import { startWatchers, stopWatchers, sseClientSyncVersion, notifyARR } from './Watcher.js'
|
|
14
|
-
|
|
15
8
|
import pkgJSON from '../../package.json' with { type: 'json' }
|
|
16
9
|
|
|
10
|
+
import { sseClientHotReload, DASHBOARD_ASSETS, CLIENT_DIR } from './WatcherDevClient.js'
|
|
11
|
+
import { stopMocksDirWatcher, sseClientSyncVersion, uiSyncVersion, watchMocksDir } from './Watcher.js'
|
|
12
|
+
|
|
17
13
|
import { API } from '../client/ApiConstants.js'
|
|
18
14
|
import { IndexHtml, CSP } from '../client/IndexHtml.js'
|
|
19
15
|
|
|
20
16
|
import { cookie } from './cookie.js'
|
|
21
17
|
import { config, ConfigValidator } from './config.js'
|
|
22
|
-
|
|
23
|
-
import { write, rm, isFile, resolveIn } from './utils/fs.js'
|
|
24
|
-
|
|
25
18
|
import * as mockBrokersCollection from './mockBrokersCollection.js'
|
|
19
|
+
import { write, rm, isFile, resolveIn } from './utils/fs.js'
|
|
26
20
|
|
|
27
21
|
|
|
28
22
|
export const apiGetReqs = new Map([
|
|
@@ -95,6 +89,7 @@ function getState(_, response) {
|
|
|
95
89
|
function reset(_, response) {
|
|
96
90
|
mockBrokersCollection.init()
|
|
97
91
|
response.ok()
|
|
92
|
+
uiSyncVersion.increment()
|
|
98
93
|
}
|
|
99
94
|
|
|
100
95
|
|
|
@@ -106,21 +101,7 @@ async function setCorsAllowed(req, response) {
|
|
|
106
101
|
else {
|
|
107
102
|
config.corsAllowed = corsAllowed
|
|
108
103
|
response.ok()
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
async function setWatchMocks(req, response) {
|
|
114
|
-
const enabled = await req.json()
|
|
115
|
-
|
|
116
|
-
if (typeof enabled !== 'boolean')
|
|
117
|
-
response.unprocessable(`Expected boolean for "watchMocks"`)
|
|
118
|
-
else {
|
|
119
|
-
if (enabled)
|
|
120
|
-
startWatchers()
|
|
121
|
-
else
|
|
122
|
-
stopWatchers()
|
|
123
|
-
response.ok()
|
|
104
|
+
uiSyncVersion.increment()
|
|
124
105
|
}
|
|
125
106
|
}
|
|
126
107
|
|
|
@@ -132,7 +113,9 @@ async function setGlobalDelay(req, response) {
|
|
|
132
113
|
response.unprocessable(`Expected non-negative integer for "delay"`)
|
|
133
114
|
else {
|
|
134
115
|
config.delay = delay
|
|
116
|
+
uiSyncVersion.increment()
|
|
135
117
|
response.ok()
|
|
118
|
+
uiSyncVersion.increment()
|
|
136
119
|
}
|
|
137
120
|
}
|
|
138
121
|
|
|
@@ -144,6 +127,7 @@ async function setGlobalDelayJitter(req, response) {
|
|
|
144
127
|
else {
|
|
145
128
|
config.delayJitter = jitter
|
|
146
129
|
response.ok()
|
|
130
|
+
uiSyncVersion.increment()
|
|
147
131
|
}
|
|
148
132
|
}
|
|
149
133
|
|
|
@@ -154,8 +138,10 @@ async function selectCookie(req, response) {
|
|
|
154
138
|
const error = cookie.setCurrent(cookieKey)
|
|
155
139
|
if (error)
|
|
156
140
|
response.unprocessable(error?.message || error)
|
|
157
|
-
else
|
|
141
|
+
else {
|
|
158
142
|
response.json(cookie.list())
|
|
143
|
+
uiSyncVersion.increment()
|
|
144
|
+
}
|
|
159
145
|
}
|
|
160
146
|
|
|
161
147
|
|
|
@@ -167,6 +153,7 @@ async function setProxyFallback(req, response) {
|
|
|
167
153
|
else {
|
|
168
154
|
config.proxyFallback = fallback
|
|
169
155
|
response.ok()
|
|
156
|
+
uiSyncVersion.increment()
|
|
170
157
|
}
|
|
171
158
|
}
|
|
172
159
|
|
|
@@ -178,6 +165,7 @@ async function setCollectProxied(req, response) {
|
|
|
178
165
|
else {
|
|
179
166
|
config.collectProxied = collectProxied
|
|
180
167
|
response.ok()
|
|
168
|
+
uiSyncVersion.increment()
|
|
181
169
|
}
|
|
182
170
|
}
|
|
183
171
|
|
|
@@ -188,6 +176,7 @@ async function bulkUpdateBrokersByCommentTag(req, response) {
|
|
|
188
176
|
|
|
189
177
|
mockBrokersCollection.setMocksMatchingComment(comment)
|
|
190
178
|
response.ok()
|
|
179
|
+
uiSyncVersion.increment()
|
|
191
180
|
}
|
|
192
181
|
|
|
193
182
|
|
|
@@ -200,6 +189,7 @@ async function selectMock(req, response) {
|
|
|
200
189
|
else {
|
|
201
190
|
broker.selectFile(file)
|
|
202
191
|
response.json(broker)
|
|
192
|
+
uiSyncVersion.increment()
|
|
203
193
|
}
|
|
204
194
|
}
|
|
205
195
|
|
|
@@ -213,6 +203,7 @@ async function toggleRouteStatus(req, response) {
|
|
|
213
203
|
else {
|
|
214
204
|
broker.toggleStatus(status)
|
|
215
205
|
response.json(broker)
|
|
206
|
+
uiSyncVersion.increment()
|
|
216
207
|
}
|
|
217
208
|
}
|
|
218
209
|
|
|
@@ -228,6 +219,7 @@ async function setRouteIsDelayed(req, response) {
|
|
|
228
219
|
else {
|
|
229
220
|
broker.setDelayed(delayed)
|
|
230
221
|
response.json(broker)
|
|
222
|
+
uiSyncVersion.increment()
|
|
231
223
|
}
|
|
232
224
|
}
|
|
233
225
|
|
|
@@ -245,6 +237,7 @@ async function setRouteIsProxied(req, response) {
|
|
|
245
237
|
else {
|
|
246
238
|
broker.setProxied(proxied)
|
|
247
239
|
response.json(broker)
|
|
240
|
+
uiSyncVersion.increment()
|
|
248
241
|
}
|
|
249
242
|
}
|
|
250
243
|
|
|
@@ -263,7 +256,7 @@ async function writeMock(req, response) {
|
|
|
263
256
|
|
|
264
257
|
if (!config.watcherEnabled) {
|
|
265
258
|
mockBrokersCollection.registerMock(file, true)
|
|
266
|
-
|
|
259
|
+
uiSyncVersion.increment()
|
|
267
260
|
}
|
|
268
261
|
response.ok()
|
|
269
262
|
}
|
|
@@ -286,10 +279,24 @@ async function deleteMock(req, response) {
|
|
|
286
279
|
|
|
287
280
|
if (!config.watcherEnabled) {
|
|
288
281
|
mockBrokersCollection.unregisterMock(file)
|
|
289
|
-
|
|
282
|
+
uiSyncVersion.increment()
|
|
290
283
|
}
|
|
291
284
|
response.ok()
|
|
292
285
|
}
|
|
293
286
|
|
|
294
287
|
|
|
295
288
|
|
|
289
|
+
async function setWatchMocks(req, response) {
|
|
290
|
+
const enabled = await req.json()
|
|
291
|
+
|
|
292
|
+
if (typeof enabled !== 'boolean')
|
|
293
|
+
response.unprocessable(`Expected boolean for "watchMocks"`)
|
|
294
|
+
else {
|
|
295
|
+
if (enabled)
|
|
296
|
+
watchMocksDir()
|
|
297
|
+
else
|
|
298
|
+
stopMocksDirWatcher()
|
|
299
|
+
response.ok()
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
package/src/server/Watcher.js
CHANGED
|
@@ -12,24 +12,22 @@ let mocksWatcher = null
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* ARR Event = Add, Remove, or Rename Mock
|
|
16
|
-
*
|
|
17
15
|
* The emitter is debounced so it handles e.g. bulk deletes,
|
|
18
16
|
* and also renames, which are two events (delete + add).
|
|
19
17
|
*/
|
|
20
|
-
const uiSyncVersion = new class extends EventEmitter {
|
|
18
|
+
export const uiSyncVersion = new class extends EventEmitter {
|
|
21
19
|
version = 0
|
|
22
20
|
|
|
23
21
|
increment = /** @type {function} */ this.#debounce(() => {
|
|
24
22
|
this.version++
|
|
25
|
-
super.emit('
|
|
23
|
+
super.emit('INC')
|
|
26
24
|
})
|
|
27
25
|
|
|
28
26
|
subscribe(listener) {
|
|
29
|
-
this.on('
|
|
27
|
+
this.on('INC', listener)
|
|
30
28
|
}
|
|
31
29
|
unsubscribe(listener) {
|
|
32
|
-
this.removeListener('
|
|
30
|
+
this.removeListener('INC', listener)
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
#debounce(fn) { // TESTME
|
|
@@ -42,11 +40,6 @@ const uiSyncVersion = new class extends EventEmitter {
|
|
|
42
40
|
}
|
|
43
41
|
|
|
44
42
|
|
|
45
|
-
export function notifyARR() {
|
|
46
|
-
uiSyncVersion.increment()
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
43
|
export function watchMocksDir() {
|
|
51
44
|
const dir = config.mocksDir
|
|
52
45
|
mocksWatcher = mocksWatcher || watch(dir, { recursive: true, persistent: false }, (_, file) => {
|
|
@@ -69,8 +62,12 @@ export function watchMocksDir() {
|
|
|
69
62
|
})
|
|
70
63
|
}
|
|
71
64
|
|
|
65
|
+
export function stopMocksDirWatcher() {
|
|
66
|
+
mocksWatcher?.close()
|
|
67
|
+
mocksWatcher = null
|
|
68
|
+
}
|
|
69
|
+
|
|
72
70
|
|
|
73
|
-
/** Realtime notify ARR Events */
|
|
74
71
|
export function sseClientSyncVersion(req, response) {
|
|
75
72
|
response.writeHead(200, {
|
|
76
73
|
'Content-Type': 'text/event-stream',
|
|
@@ -99,11 +96,3 @@ export function sseClientSyncVersion(req, response) {
|
|
|
99
96
|
}
|
|
100
97
|
|
|
101
98
|
|
|
102
|
-
export function startWatchers() {
|
|
103
|
-
watchMocksDir()
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function stopWatchers() {
|
|
107
|
-
mocksWatcher?.close()
|
|
108
|
-
mocksWatcher = null
|
|
109
|
-
}
|
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
{
|
|
2
|
+
"openapi": "3.1.2",
|
|
3
|
+
"info": {
|
|
4
|
+
"title": "Mockaton Control API",
|
|
5
|
+
"version": "1.0.0"
|
|
6
|
+
},
|
|
7
|
+
"servers": [
|
|
8
|
+
{
|
|
9
|
+
"url": "http://localhost:2020"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"paths": {
|
|
13
|
+
"/mockaton/reset": {
|
|
14
|
+
"patch": {
|
|
15
|
+
"summary": "Re-initialize Mockaton",
|
|
16
|
+
"description": "The selected mocks, cookies, and delays go back to default, but `proxyFallback`, `collectProxied`, and `corsAllowed` are not affected.",
|
|
17
|
+
"x-js-client-example": "await mockaton.reset()",
|
|
18
|
+
"responses": {
|
|
19
|
+
"200": {
|
|
20
|
+
"description": "OK"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"/mockaton/bulk-select-by-comment": {
|
|
26
|
+
"patch": {
|
|
27
|
+
"summary": "Select all mocks that have a particular comment",
|
|
28
|
+
"x-js-client-example": "await mockaton.bulkSelectByComment('(demo-a)')",
|
|
29
|
+
"requestBody": {
|
|
30
|
+
"required": true,
|
|
31
|
+
"content": {
|
|
32
|
+
"application/json": {
|
|
33
|
+
"schema": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"description": "Parentheses are optional, so you can pass a partial match. For example, passing `'demo-'` (without the final `a`) works too. On routes with many partial matches, their first mock in alphabetical order wins.",
|
|
36
|
+
"example": "(demo-a)"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"responses": {
|
|
42
|
+
"200": {
|
|
43
|
+
"$ref": "#/components/responses/Broker"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"/mockaton/select": {
|
|
49
|
+
"patch": {
|
|
50
|
+
"summary": "Select a mock file for a route",
|
|
51
|
+
"x-js-client-example": "await mockaton.select('api/user/avatar.GET.json')",
|
|
52
|
+
"requestBody": {
|
|
53
|
+
"required": true,
|
|
54
|
+
"content": {
|
|
55
|
+
"application/json": {
|
|
56
|
+
"schema": {
|
|
57
|
+
"$ref": "#/components/schemas/Filename"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"responses": {
|
|
63
|
+
"200": {
|
|
64
|
+
"$ref": "#/components/responses/Broker"
|
|
65
|
+
},
|
|
66
|
+
"422": {
|
|
67
|
+
"description": "Mock file doesn’t exist",
|
|
68
|
+
"content": {
|
|
69
|
+
"application/json": {
|
|
70
|
+
"schema": {
|
|
71
|
+
"type": "string"
|
|
72
|
+
},
|
|
73
|
+
"example": "Mock file does not exist"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"/mockaton/toggle-status": {
|
|
81
|
+
"patch": {
|
|
82
|
+
"summary": "Toggle a specific status for a route",
|
|
83
|
+
"description": "Selects the first found mock with the given status, which could be the autogenerated one. Or, selects the default file.",
|
|
84
|
+
"x-js-client-example": "await mockaton.toggleStatus('GET', '/api/user/friends', 500)",
|
|
85
|
+
"requestBody": {
|
|
86
|
+
"required": true,
|
|
87
|
+
"content": {
|
|
88
|
+
"application/json": {
|
|
89
|
+
"schema": {
|
|
90
|
+
"type": "array",
|
|
91
|
+
"minItems": 3,
|
|
92
|
+
"maxItems": 3,
|
|
93
|
+
"prefixItems": [
|
|
94
|
+
{
|
|
95
|
+
"$ref": "#/components/schemas/Method"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"$ref": "#/components/schemas/UrlMask"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"type": "number",
|
|
102
|
+
"description": "HTTP Status to toggle",
|
|
103
|
+
"example": 500
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
"additionalItems": false
|
|
107
|
+
},
|
|
108
|
+
"example": [
|
|
109
|
+
"GET",
|
|
110
|
+
"/api/user",
|
|
111
|
+
500
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
"responses": {
|
|
117
|
+
"200": {
|
|
118
|
+
"$ref": "#/components/responses/Broker"
|
|
119
|
+
},
|
|
120
|
+
"422": {
|
|
121
|
+
"description": "Route does not exist"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
"/mockaton/proxied": {
|
|
127
|
+
"patch": {
|
|
128
|
+
"summary": "Set whether a route is proxied",
|
|
129
|
+
"description": "Applicable only when there’s a proxy fallback server URL already set. See `PATCH /mockaton/fallback`",
|
|
130
|
+
"x-js-client-example": "await mockaton.setRouteIsProxied('GET', '/api/user/friends', true)",
|
|
131
|
+
"requestBody": {
|
|
132
|
+
"required": true,
|
|
133
|
+
"content": {
|
|
134
|
+
"application/json": {
|
|
135
|
+
"schema": {
|
|
136
|
+
"type": "array",
|
|
137
|
+
"minItems": 3,
|
|
138
|
+
"maxItems": 3,
|
|
139
|
+
"prefixItems": [
|
|
140
|
+
{
|
|
141
|
+
"$ref": "#/components/schemas/Method"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"$ref": "#/components/schemas/UrlMask"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"type": "boolean",
|
|
148
|
+
"description": "proxied",
|
|
149
|
+
"example": true
|
|
150
|
+
}
|
|
151
|
+
],
|
|
152
|
+
"additionalItems": false
|
|
153
|
+
},
|
|
154
|
+
"example": [
|
|
155
|
+
"GET",
|
|
156
|
+
"/api/user",
|
|
157
|
+
true
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
"responses": {
|
|
163
|
+
"200": {
|
|
164
|
+
"$ref": "#/components/responses/Broker"
|
|
165
|
+
},
|
|
166
|
+
"422": {
|
|
167
|
+
"description": "Invalid request. Possible reasons:\n- Route does not exist\n- Expected boolean for `proxied`\n- No `proxyFallback` configured\n"
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
"/mockaton/delay": {
|
|
173
|
+
"patch": {
|
|
174
|
+
"summary": "Set whether a route is delayed",
|
|
175
|
+
"x-js-client-example": "await mockaton.setRouteIsDelayed('GET', '/api/user/friends', true)",
|
|
176
|
+
"requestBody": {
|
|
177
|
+
"required": true,
|
|
178
|
+
"content": {
|
|
179
|
+
"application/json": {
|
|
180
|
+
"schema": {
|
|
181
|
+
"type": "array",
|
|
182
|
+
"minItems": 3,
|
|
183
|
+
"maxItems": 3,
|
|
184
|
+
"prefixItems": [
|
|
185
|
+
{
|
|
186
|
+
"$ref": "#/components/schemas/Method"
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"$ref": "#/components/schemas/UrlMask"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"type": "boolean",
|
|
193
|
+
"description": "delayed",
|
|
194
|
+
"example": true
|
|
195
|
+
}
|
|
196
|
+
],
|
|
197
|
+
"additionalItems": false
|
|
198
|
+
},
|
|
199
|
+
"example": [
|
|
200
|
+
"GET",
|
|
201
|
+
"/api/user",
|
|
202
|
+
true
|
|
203
|
+
]
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
"responses": {
|
|
208
|
+
"200": {
|
|
209
|
+
"$ref": "#/components/responses/Broker"
|
|
210
|
+
},
|
|
211
|
+
"422": {
|
|
212
|
+
"description": "Invalid request. Possible reasons:\n- Route does not exist\n- Expected boolean for `delayed`\n"
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
"/mockaton/global-delay": {
|
|
218
|
+
"patch": {
|
|
219
|
+
"summary": "Set global delay for all responses",
|
|
220
|
+
"x-js-client-example": "await mockaton.setGlobalDelay(1500)",
|
|
221
|
+
"requestBody": {
|
|
222
|
+
"required": true,
|
|
223
|
+
"content": {
|
|
224
|
+
"application/json": {
|
|
225
|
+
"schema": {
|
|
226
|
+
"type": "integer",
|
|
227
|
+
"description": "Delay in milliseconds",
|
|
228
|
+
"example": 1500
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
"responses": {
|
|
234
|
+
"200": {
|
|
235
|
+
"description": "OK"
|
|
236
|
+
},
|
|
237
|
+
"422": {
|
|
238
|
+
"description": "Expected non-negative integer"
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
"/mockaton/global-delay-jitter": {
|
|
244
|
+
"patch": {
|
|
245
|
+
"summary": "Set global delay jitter for all responses",
|
|
246
|
+
"x-js-client-example": "await mockaton.setGlobalDelayJitter(0.5)",
|
|
247
|
+
"requestBody": {
|
|
248
|
+
"required": true,
|
|
249
|
+
"content": {
|
|
250
|
+
"application/json": {
|
|
251
|
+
"schema": {
|
|
252
|
+
"type": "number",
|
|
253
|
+
"description": "0 to 3 float percent",
|
|
254
|
+
"example": 0.5
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
"responses": {
|
|
260
|
+
"200": {
|
|
261
|
+
"description": "OK"
|
|
262
|
+
},
|
|
263
|
+
"422": {
|
|
264
|
+
"description": "Expected 0 to 3 float"
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
"/mockaton/cors": {
|
|
270
|
+
"patch": {
|
|
271
|
+
"summary": "Enable or disable CORS",
|
|
272
|
+
"x-js-client-example": "await mockaton.setCorsAllowed(false)",
|
|
273
|
+
"requestBody": {
|
|
274
|
+
"required": true,
|
|
275
|
+
"content": {
|
|
276
|
+
"application/json": {
|
|
277
|
+
"schema": {
|
|
278
|
+
"type": "boolean",
|
|
279
|
+
"description": "Is enabled?"
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
"responses": {
|
|
285
|
+
"200": {
|
|
286
|
+
"description": "OK"
|
|
287
|
+
},
|
|
288
|
+
"422": {
|
|
289
|
+
"description": "Expected boolean"
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
"/mockaton/fallback": {
|
|
295
|
+
"patch": {
|
|
296
|
+
"summary": "Set proxy fallback address",
|
|
297
|
+
"x-js-client-example": "await mockaton.setProxyFallback('http://example.test')",
|
|
298
|
+
"requestBody": {
|
|
299
|
+
"required": true,
|
|
300
|
+
"content": {
|
|
301
|
+
"application/json": {
|
|
302
|
+
"schema": {
|
|
303
|
+
"type": "string",
|
|
304
|
+
"description": "URL",
|
|
305
|
+
"example": "https://example.test"
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
"responses": {
|
|
311
|
+
"200": {
|
|
312
|
+
"description": "OK"
|
|
313
|
+
},
|
|
314
|
+
"422": {
|
|
315
|
+
"description": "Invalid Proxy Fallback URL"
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
"/mockaton/collect-proxied": {
|
|
321
|
+
"patch": {
|
|
322
|
+
"summary": "Enable or disable collection of proxied responses",
|
|
323
|
+
"description": "Applicable only when there’s a proxy fallback server URL already set. See `PATCH /mockaton/fallback`",
|
|
324
|
+
"x-js-client-example": "await mockaton.setCollectProxied(true)",
|
|
325
|
+
"requestBody": {
|
|
326
|
+
"required": true,
|
|
327
|
+
"content": {
|
|
328
|
+
"application/json": {
|
|
329
|
+
"schema": {
|
|
330
|
+
"type": "boolean",
|
|
331
|
+
"description": "Should Collect?",
|
|
332
|
+
"example": true
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
"responses": {
|
|
338
|
+
"200": {
|
|
339
|
+
"description": "OK"
|
|
340
|
+
},
|
|
341
|
+
"422": {
|
|
342
|
+
"description": "Expected boolean"
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
"/mockaton/cookies": {
|
|
348
|
+
"patch": {
|
|
349
|
+
"summary": "Select a cookie label",
|
|
350
|
+
"x-js-client-example": "await mockaton.selectCookie('Normal User')",
|
|
351
|
+
"requestBody": {
|
|
352
|
+
"required": true,
|
|
353
|
+
"content": {
|
|
354
|
+
"application/json": {
|
|
355
|
+
"schema": {
|
|
356
|
+
"type": "string",
|
|
357
|
+
"description": "Cookie key",
|
|
358
|
+
"example": "Normal User"
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
"responses": {
|
|
364
|
+
"200": {
|
|
365
|
+
"description": "Available cookie labels",
|
|
366
|
+
"content": {
|
|
367
|
+
"application/json": {
|
|
368
|
+
"schema": {
|
|
369
|
+
"$ref": "#/components/schemas/CookieSelectionList"
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
"422": {
|
|
375
|
+
"description": "Cookie key not found"
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
"/mockaton/watch-mocks": {
|
|
381
|
+
"patch": {
|
|
382
|
+
"summary": "Enable or disable mock file watching",
|
|
383
|
+
"description": "Controls file watchers for mocksDir.",
|
|
384
|
+
"x-js-client-example": "await mockaton.setWatchMocks(true)",
|
|
385
|
+
"requestBody": {
|
|
386
|
+
"required": true,
|
|
387
|
+
"content": {
|
|
388
|
+
"application/json": {
|
|
389
|
+
"schema": {
|
|
390
|
+
"type": "boolean",
|
|
391
|
+
"description": "true to start watchers, false to stop them"
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
"responses": {
|
|
397
|
+
"200": {
|
|
398
|
+
"description": "OK"
|
|
399
|
+
},
|
|
400
|
+
"422": {
|
|
401
|
+
"description": "Invalid input - expected boolean"
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
"/mockaton/write-mock": {
|
|
407
|
+
"patch": {
|
|
408
|
+
"summary": "Write a mock file",
|
|
409
|
+
"description": "Writes a mock file to the mocks directory. Guarded by `readOnly` and `mocksDir`.",
|
|
410
|
+
"x-js-client-example": "await mockaton.writeMock('api/user/friends.GET.200.json', '{ \"friends\": [] }')",
|
|
411
|
+
"requestBody": {
|
|
412
|
+
"required": true,
|
|
413
|
+
"content": {
|
|
414
|
+
"application/json": {
|
|
415
|
+
"schema": {
|
|
416
|
+
"type": "array",
|
|
417
|
+
"minItems": 2,
|
|
418
|
+
"maxItems": 2,
|
|
419
|
+
"prefixItems": [
|
|
420
|
+
{
|
|
421
|
+
"$ref": "#/components/schemas/Filename"
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
"type": "string",
|
|
425
|
+
"description": "File content"
|
|
426
|
+
}
|
|
427
|
+
],
|
|
428
|
+
"additionalItems": false
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
"responses": {
|
|
434
|
+
"200": {
|
|
435
|
+
"description": "OK"
|
|
436
|
+
},
|
|
437
|
+
"403": {
|
|
438
|
+
"description": "Forbidden (read-only mode or outside mocksDir)"
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
},
|
|
443
|
+
"/mockaton/delete-mock": {
|
|
444
|
+
"patch": {
|
|
445
|
+
"summary": "Delete a mock file",
|
|
446
|
+
"description": "Deletes a mock file from the mocks directory. Guarded by `readOnly` and `mocksDir`.",
|
|
447
|
+
"x-js-client-example": "await mockaton.deleteMock('api/user/friends.GET.200.json')",
|
|
448
|
+
"requestBody": {
|
|
449
|
+
"required": true,
|
|
450
|
+
"content": {
|
|
451
|
+
"application/json": {
|
|
452
|
+
"schema": {
|
|
453
|
+
"$ref": "#/components/schemas/Filename"
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
"responses": {
|
|
459
|
+
"200": {
|
|
460
|
+
"description": "OK"
|
|
461
|
+
},
|
|
462
|
+
"403": {
|
|
463
|
+
"description": "Forbidden (read-only mode or outside mocksDir)"
|
|
464
|
+
},
|
|
465
|
+
"422": {
|
|
466
|
+
"description": "Mock file does not exist"
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
},
|
|
471
|
+
"/mockaton/state": {
|
|
472
|
+
"get": {
|
|
473
|
+
"summary": "Get complete Mockaton state",
|
|
474
|
+
"x-js-client-example": "await mockaton.getState()",
|
|
475
|
+
"responses": {
|
|
476
|
+
"200": {
|
|
477
|
+
"description": "Mockaton state",
|
|
478
|
+
"content": {
|
|
479
|
+
"application/json": {
|
|
480
|
+
"schema": {
|
|
481
|
+
"$ref": "#/components/schemas/State"
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
"/mockaton/sync-version": {
|
|
490
|
+
"get": {
|
|
491
|
+
"summary": "Get sync version for long‑polling updates",
|
|
492
|
+
"description": "A counter that’s incremented when a new mock is added, removed, or renamed. Also, when the internal state changes, e.g., when changing the mock file for a route.",
|
|
493
|
+
"x-js-client-example": "await mockaton.getSyncVersion()",
|
|
494
|
+
"parameters": [
|
|
495
|
+
{
|
|
496
|
+
"name": "sync_version",
|
|
497
|
+
"in": "header",
|
|
498
|
+
"required": false,
|
|
499
|
+
"example": -1,
|
|
500
|
+
"schema": {
|
|
501
|
+
"type": "number"
|
|
502
|
+
},
|
|
503
|
+
"description": "When not present, or when the version mismatches it responds right away. Otherwise, it long polls. Times out in 8s."
|
|
504
|
+
}
|
|
505
|
+
],
|
|
506
|
+
"responses": {
|
|
507
|
+
"200": {
|
|
508
|
+
"description": "Sync version value",
|
|
509
|
+
"content": {
|
|
510
|
+
"application/json": {
|
|
511
|
+
"schema": {
|
|
512
|
+
"type": "number",
|
|
513
|
+
"description": "Incremental integer"
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
},
|
|
522
|
+
"components": {
|
|
523
|
+
"responses": {
|
|
524
|
+
"Broker": {
|
|
525
|
+
"description": "Updated broker state",
|
|
526
|
+
"content": {
|
|
527
|
+
"application/json": {
|
|
528
|
+
"schema": {
|
|
529
|
+
"$ref": "#/components/schemas/ClientMockBroker"
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
},
|
|
535
|
+
"schemas": {
|
|
536
|
+
"Method": {
|
|
537
|
+
"type": "string",
|
|
538
|
+
"description": "HTTP Method of the mock",
|
|
539
|
+
"example": "GET"
|
|
540
|
+
},
|
|
541
|
+
"UrlMask": {
|
|
542
|
+
"type": "string",
|
|
543
|
+
"example": "/api/user/friends"
|
|
544
|
+
},
|
|
545
|
+
"Filename": {
|
|
546
|
+
"type": "string",
|
|
547
|
+
"description": "Mock filename. The convention is UrlMask.Method.StatusCode.Extension",
|
|
548
|
+
"example": "api/user/avatar.GET.200.svg"
|
|
549
|
+
},
|
|
550
|
+
"ClientMockBroker": {
|
|
551
|
+
"type": "object",
|
|
552
|
+
"properties": {
|
|
553
|
+
"mocks": {
|
|
554
|
+
"type": "array",
|
|
555
|
+
"items": {
|
|
556
|
+
"$ref": "#/components/schemas/Filename"
|
|
557
|
+
}
|
|
558
|
+
},
|
|
559
|
+
"file": {
|
|
560
|
+
"$ref": "#/components/schemas/Filename"
|
|
561
|
+
},
|
|
562
|
+
"status": {
|
|
563
|
+
"type": "number"
|
|
564
|
+
},
|
|
565
|
+
"autoStatus": {
|
|
566
|
+
"type": "number"
|
|
567
|
+
},
|
|
568
|
+
"isStatic": {
|
|
569
|
+
"type": "boolean"
|
|
570
|
+
},
|
|
571
|
+
"delayed": {
|
|
572
|
+
"type": "boolean"
|
|
573
|
+
},
|
|
574
|
+
"proxied": {
|
|
575
|
+
"type": "boolean"
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
"required": [
|
|
579
|
+
"mocks",
|
|
580
|
+
"file",
|
|
581
|
+
"status",
|
|
582
|
+
"autoStatus",
|
|
583
|
+
"isStatic",
|
|
584
|
+
"delayed",
|
|
585
|
+
"proxied"
|
|
586
|
+
]
|
|
587
|
+
},
|
|
588
|
+
"State": {
|
|
589
|
+
"type": "object",
|
|
590
|
+
"properties": {
|
|
591
|
+
"brokersByMethod": {
|
|
592
|
+
"type": "object",
|
|
593
|
+
"additionalProperties": {
|
|
594
|
+
"type": "object",
|
|
595
|
+
"additionalProperties": {
|
|
596
|
+
"$ref": "#/components/schemas/ClientMockBroker"
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
},
|
|
600
|
+
"cookies": {
|
|
601
|
+
"$ref": "#/components/schemas/CookieSelectionList"
|
|
602
|
+
},
|
|
603
|
+
"comments": {
|
|
604
|
+
"type": "array",
|
|
605
|
+
"items": {
|
|
606
|
+
"type": "string"
|
|
607
|
+
}
|
|
608
|
+
},
|
|
609
|
+
"delay": {
|
|
610
|
+
"type": "number"
|
|
611
|
+
},
|
|
612
|
+
"delayJitter": {
|
|
613
|
+
"type": "number"
|
|
614
|
+
},
|
|
615
|
+
"collectProxied": {
|
|
616
|
+
"type": "boolean"
|
|
617
|
+
},
|
|
618
|
+
"proxyFallback": {
|
|
619
|
+
"type": "string"
|
|
620
|
+
},
|
|
621
|
+
"readOnly": {
|
|
622
|
+
"type": "boolean"
|
|
623
|
+
},
|
|
624
|
+
"corsAllowed": {
|
|
625
|
+
"type": "boolean"
|
|
626
|
+
}
|
|
627
|
+
},
|
|
628
|
+
"required": [
|
|
629
|
+
"brokersByMethod",
|
|
630
|
+
"cookies",
|
|
631
|
+
"comments",
|
|
632
|
+
"delay",
|
|
633
|
+
"delayJitter",
|
|
634
|
+
"collectProxied",
|
|
635
|
+
"proxyFallback",
|
|
636
|
+
"readOnly",
|
|
637
|
+
"corsAllowed"
|
|
638
|
+
]
|
|
639
|
+
},
|
|
640
|
+
"CookieSelectionList": {
|
|
641
|
+
"type": "array",
|
|
642
|
+
"items": {
|
|
643
|
+
"type": "array",
|
|
644
|
+
"minItems": 2,
|
|
645
|
+
"maxItems": 2,
|
|
646
|
+
"prefixItems": [
|
|
647
|
+
{
|
|
648
|
+
"type": "string",
|
|
649
|
+
"description": "Label"
|
|
650
|
+
},
|
|
651
|
+
{
|
|
652
|
+
"type": "boolean",
|
|
653
|
+
"description": "Selected"
|
|
654
|
+
}
|
|
655
|
+
],
|
|
656
|
+
"additionalItems": false
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|