mockaton 11.1.4 → 11.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/Makefile +1 -1
- package/README.md +56 -43
- package/index.js +1 -1
- package/package.json +1 -1
- package/src/Dashboard.css +21 -11
- package/src/Dashboard.js +21 -17
- package/src/DashboardStore.js +3 -0
- package/src/Filename.js +3 -3
- package/src/Logo.svg +12 -11
- package/src/MockBroker.js +8 -7
- package/src/MockDispatcher.js +6 -6
- package/src/Mockaton.js +8 -2
- package/src/config.js +0 -2
- package/src/mockBrokersCollection.js +3 -7
- package/src/staticCollection.js +1 -1
- package/src/utils/http-response.js +6 -0
package/Makefile
CHANGED
package/README.md
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
[](https://github.com/ericfortis/mockaton/actions/workflows/github-code-scanning/codeql)
|
|
7
7
|
[](https://codecov.io/github/ericfortis/mockaton)
|
|
8
8
|
|
|
9
|
-
An HTTP mock server for simulating APIs with minimal setup
|
|
10
|
-
|
|
9
|
+
An HTTP mock server for simulating APIs with minimal setup — ideal
|
|
10
|
+
for testing difficult to reproduce backend states.
|
|
11
11
|
|
|
12
12
|
## Overview
|
|
13
13
|
With Mockaton, you don’t need to write code for wiring up your
|
|
@@ -97,8 +97,8 @@ api/videos.GET.<b>500</b>.txt # Internal Server Error
|
|
|
97
97
|
### Option 1: Browser extension
|
|
98
98
|
The companion Chrome [devtools
|
|
99
99
|
extension](https://github.com/ericfortis/download-http-requests-browser-ext)
|
|
100
|
-
lets you download all the HTTP responses
|
|
101
|
-
|
|
100
|
+
lets you download all the HTTP responses and
|
|
101
|
+
save them following Mockaton’s filename convention.
|
|
102
102
|
|
|
103
103
|
### Option 2: Fallback to your backend
|
|
104
104
|
<details>
|
|
@@ -123,24 +123,12 @@ They will be saved in your `config.mocksDir` following the filename convention.
|
|
|
123
123
|
|
|
124
124
|
## Motivation
|
|
125
125
|
|
|
126
|
-
### Testing scenarios that would otherwise be skipped
|
|
127
|
-
- Simulate errors on third-party APIs, or on your project’s backend (if you are a frontend dev, or unfamiliar with that code)
|
|
128
|
-
- Trigger dynamic states on an API. You can do this by using comments on mock filenames, for example, for polled alerts or notifications.
|
|
129
|
-
- Trigger empty (no content) responses
|
|
130
|
-
- Sometimes, the ideal flow you need is just too difficult to reproduce from the actual backend
|
|
131
|
-
|
|
132
|
-
### Works around unstable dev backends while developing UIs
|
|
133
|
-
- Spinning up dev infrastructure
|
|
134
|
-
- Syncing the database
|
|
135
|
-
- Mitigates progress from being blocked by waiting for APIs
|
|
136
|
-
|
|
137
|
-
### Time travel
|
|
138
|
-
If you commit the mocks to your repo, you don’t have to downgrade backends when:
|
|
139
|
-
- Checking out long-lived branches
|
|
140
|
-
- Bisecting bugs
|
|
141
|
-
|
|
142
126
|
### Deterministic and comprehensive states
|
|
143
|
-
|
|
127
|
+
Sometimes the flow you need to test is
|
|
128
|
+
too difficult to reproduce from the actual backend.
|
|
129
|
+
|
|
130
|
+
- Demo edge cases to PMs, Design, and clients
|
|
131
|
+
- Set up screenshot tests, e.g., with [pixaton](https://github.com/ericfortis/pixaton)
|
|
144
132
|
- Spot inadvertent regressions during development. For example, the demo app in
|
|
145
133
|
this repo has a list of colors containing all of their possible states, such as
|
|
146
134
|
permutations for out-of-stock, new-arrival, and discontinued. This way you’ll
|
|
@@ -148,6 +136,10 @@ If you commit the mocks to your repo, you don’t have to downgrade backends whe
|
|
|
148
136
|
|
|
149
137
|
<img src="./demo-app-vite/pixaton-tests/pic-for-readme.vp740x880.light.gold.png" alt="Mockaton Demo App Screenshot" width="740" />
|
|
150
138
|
|
|
139
|
+
<br/>
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
## Benefits
|
|
151
143
|
|
|
152
144
|
### Standalone demo server (Docker)
|
|
153
145
|
You can demo your app by compiling the frontend and putting
|
|
@@ -157,25 +149,52 @@ repo includes a demo which builds and runs a docker container.
|
|
|
157
149
|
```sh
|
|
158
150
|
git clone https://github.com/ericfortis/mockaton.git --depth 1
|
|
159
151
|
cd mockaton/demo-app-vite
|
|
160
|
-
make
|
|
152
|
+
make run-standalone-demo
|
|
161
153
|
```
|
|
162
154
|
- App: http://localhost:4040
|
|
163
155
|
- Dashboard: http://localhost:4040/mockaton
|
|
164
156
|
|
|
157
|
+
### Testing scenarios that would otherwise be skipped
|
|
158
|
+
- Trigger dynamic states on an API. You can do this by using comments on mock filenames, for example, for polled alerts or notifications.
|
|
159
|
+
- Testing retries, on-the-fly you can change an endpoint from a 500 to a 200.
|
|
160
|
+
- Simulate errors on third-party APIs, or on your project’s backend (if you are a frontend dev, or unfamiliar with that code)
|
|
161
|
+
- Generating dynamic responses on the fly. Mockaton lets you use Node’s HTTP handlers (see function mocks) when using function mocks.
|
|
162
|
+
So you can, e.g.:
|
|
163
|
+
- have an in-memory database
|
|
164
|
+
- read from disk
|
|
165
|
+
- read query string
|
|
166
|
+
- pretty much anything you can do with a normal backend request handler
|
|
165
167
|
|
|
166
168
|
### Privacy and security
|
|
167
169
|
- Does not write to disk. Except when you select ✅ **Save Mocks** for scraping mocks from a backend
|
|
168
170
|
- Does not initiate network connections (no logs, no telemetry)
|
|
169
171
|
- Does not hijack your HTTP client
|
|
170
|
-
- Auditable
|
|
172
|
+
- Auditable
|
|
173
|
+
- Organized and small. 4 KLoC (half is UI and tests)
|
|
171
174
|
- Zero dependencies. No runtime and no build packages.
|
|
172
175
|
|
|
173
176
|
<br/>
|
|
174
177
|
|
|
178
|
+
## Benefits of Mocking APIs in General
|
|
179
|
+
The section above highlights benefits specific to Mockaton. There are more, but
|
|
180
|
+
in general here are some benefits which Mockaton has but other tools have as well:
|
|
181
|
+
|
|
182
|
+
### Works around unstable dev backends while developing UIs
|
|
183
|
+
- Syncing the database and spinning up dev infrastructure can be complex
|
|
184
|
+
- Mitigates progress from being blocked by waiting for APIs
|
|
185
|
+
|
|
186
|
+
### Time travel
|
|
187
|
+
If you commit the mocks to your repo, you don’t have to downgrade
|
|
188
|
+
backends when checking out long-lived branches or bisecting bugs.
|
|
189
|
+
|
|
190
|
+
<br/>
|
|
175
191
|
|
|
176
|
-
## Usage Without Docker
|
|
177
192
|
|
|
178
|
-
|
|
193
|
+
## Usage (without Docker)
|
|
194
|
+
|
|
195
|
+
_For Docker, see the Quick-Start section above._
|
|
196
|
+
|
|
197
|
+
Requires Node.js **v22.18+**, which supports mocks in TypeScript.
|
|
179
198
|
|
|
180
199
|
1. Create a mock in the default mocks directory (`./mockaton-mocks`)
|
|
181
200
|
```sh
|
|
@@ -230,16 +249,11 @@ The CLI options override their counterparts in `mockaton.config.js`
|
|
|
230
249
|
## mockaton.config.js (Optional)
|
|
231
250
|
Mockaton looks for a file `mockaton.config.js` in its current working directory.
|
|
232
251
|
|
|
233
|
-
|
|
234
|
-
|
|
252
|
+
### Defaults Overview
|
|
253
|
+
The next section has the documentation, but here's an overview of the defaults:
|
|
235
254
|
|
|
236
255
|
```js
|
|
237
|
-
import {
|
|
238
|
-
defineConfig,
|
|
239
|
-
jsToJsonPlugin,
|
|
240
|
-
openInBrowser,
|
|
241
|
-
SUPPORTED_METHODS
|
|
242
|
-
} from 'mockaton'
|
|
256
|
+
import { defineConfig, jsToJsonPlugin, openInBrowser, SUPPORTED_METHODS } from 'mockaton'
|
|
243
257
|
|
|
244
258
|
export default defineConfig({
|
|
245
259
|
mocksDir: 'mockaton-mocks',
|
|
@@ -248,11 +262,11 @@ export default defineConfig({
|
|
|
248
262
|
watcherEnabled: true,
|
|
249
263
|
|
|
250
264
|
host: '127.0.0.1',
|
|
251
|
-
port: 0,
|
|
265
|
+
port: 0, // auto-assigned
|
|
252
266
|
|
|
253
267
|
logLevel: 'normal',
|
|
254
268
|
|
|
255
|
-
delay: 1200,
|
|
269
|
+
delay: 1200, // Global value in ms. But only applied to routes with the Delayed checkbox "ON"
|
|
256
270
|
delayJitter: 0,
|
|
257
271
|
|
|
258
272
|
proxyFallback: '',
|
|
@@ -280,8 +294,7 @@ export default defineConfig({
|
|
|
280
294
|
})
|
|
281
295
|
```
|
|
282
296
|
|
|
283
|
-
|
|
284
|
-
|
|
297
|
+
<br/>
|
|
285
298
|
<details>
|
|
286
299
|
<summary><b>Config Documentation…</b></summary>
|
|
287
300
|
|
|
@@ -361,7 +374,7 @@ the filename will have `[id]` in their place. For example:
|
|
|
361
374
|
my-mocks-dir<b>/api/user/</b>[id]<b>/likes</b>.GET.200.json
|
|
362
375
|
</pre>
|
|
363
376
|
|
|
364
|
-
Your existing mocks won’t be overwritten.
|
|
377
|
+
Your existing mocks won’t be overwritten. Responses of routes with
|
|
365
378
|
the ☁️ **Cloud Checkbox** selected will be saved with unique filename-comments.
|
|
366
379
|
|
|
367
380
|
|
|
@@ -411,7 +424,7 @@ If you need to send more than one cookie, you can inject them globally
|
|
|
411
424
|
in `config.extraHeaders`, or individually in a function `.js` or `.ts` mock.
|
|
412
425
|
|
|
413
426
|
By the way, the `jwtCookie` helper has a hardcoded header and signature.
|
|
414
|
-
|
|
427
|
+
So it’s useful only if you care about its payload.
|
|
415
428
|
|
|
416
429
|
<br/>
|
|
417
430
|
|
|
@@ -572,7 +585,7 @@ For example, `api/foo.GET.200.js`
|
|
|
572
585
|
export default { foo: 'bar' }
|
|
573
586
|
```
|
|
574
587
|
|
|
575
|
-
### Option B: Function (async or sync)
|
|
588
|
+
### Option B: Function Mocks (async or sync)
|
|
576
589
|
|
|
577
590
|
**Return** a `string | Buffer | Uint8Array`, but **don’t call** `response.end()`
|
|
578
591
|
|
|
@@ -632,7 +645,7 @@ export default function listColors() {
|
|
|
632
645
|
**What if I need to serve a static .js or .ts?**
|
|
633
646
|
|
|
634
647
|
**Option A:** Put it in your `config.staticDir` without the `.GET.200.js` extension.
|
|
635
|
-
|
|
648
|
+
Mocks in `staticDir` take precedence over `mocksDir/*`.
|
|
636
649
|
|
|
637
650
|
**Option B:** Read it and return it. For example:
|
|
638
651
|
```js
|
|
@@ -715,7 +728,7 @@ api/user<b>(default)</b>.GET.200.json
|
|
|
715
728
|
<br/>
|
|
716
729
|
|
|
717
730
|
### Query string params
|
|
718
|
-
The query string is ignored for routing purposes.
|
|
731
|
+
The query string is ignored for routing purposes. It’s only used for
|
|
719
732
|
documenting the URL contract.
|
|
720
733
|
<pre>
|
|
721
734
|
api/video<b>?limit=[limit]</b>.GET.200.json
|
|
@@ -752,7 +765,6 @@ All of its methods return their `fetch` response promise.
|
|
|
752
765
|
```js
|
|
753
766
|
import { Commander } from 'mockaton'
|
|
754
767
|
|
|
755
|
-
|
|
756
768
|
const myMockatonAddr = 'http://localhost:4040'
|
|
757
769
|
const mockaton = new Commander(myMockatonAddr)
|
|
758
770
|
```
|
|
@@ -875,6 +887,7 @@ programs hijack your browser’s HTTP client (and Node’s).
|
|
|
875
887
|
- [Wire Mock](https://github.com/wiremock/wiremock)
|
|
876
888
|
- [Mock](https://github.com/dhuan/mock)
|
|
877
889
|
- [Swagger](https://swagger.io/)
|
|
890
|
+
- [Mockoon](https://mockoon.com)
|
|
878
891
|
|
|
879
892
|
|
|
880
893
|
<br/>
|
package/index.js
CHANGED
|
@@ -3,6 +3,6 @@ export { Commander } from './src/ApiCommander.js'
|
|
|
3
3
|
|
|
4
4
|
export { jwtCookie } from './src/utils/jwt.js'
|
|
5
5
|
export { jsToJsonPlugin } from './src/MockDispatcher.js'
|
|
6
|
-
export { parseJSON } from './src/utils/http-request.js'
|
|
6
|
+
export { parseJSON, BodyReaderError } from './src/utils/http-request.js'
|
|
7
7
|
|
|
8
8
|
export const defineConfig = opts => opts
|
package/package.json
CHANGED
package/src/Dashboard.css
CHANGED
|
@@ -132,7 +132,7 @@ header {
|
|
|
132
132
|
border-bottom: 1px solid var(--colorSecondaryActionBorder);
|
|
133
133
|
background: var(--colorHeaderBackground);
|
|
134
134
|
|
|
135
|
-
>
|
|
135
|
+
> object {
|
|
136
136
|
align-self: end;
|
|
137
137
|
margin-right: 22px;
|
|
138
138
|
margin-bottom: 5px;
|
|
@@ -235,6 +235,9 @@ header {
|
|
|
235
235
|
margin: 0;
|
|
236
236
|
margin-right: 4px;
|
|
237
237
|
}
|
|
238
|
+
input:enabled + span {
|
|
239
|
+
cursor: pointer;
|
|
240
|
+
}
|
|
238
241
|
input:disabled + span {
|
|
239
242
|
opacity: 0.8;
|
|
240
243
|
}
|
|
@@ -287,23 +290,30 @@ header {
|
|
|
287
290
|
top: 62px;
|
|
288
291
|
right: 10px;
|
|
289
292
|
left: auto;
|
|
290
|
-
padding: 20px;
|
|
291
293
|
border: 1px solid var(--colorSecondaryActionBorder);
|
|
292
|
-
text-align: left;
|
|
293
294
|
color: var(--colorText);
|
|
294
295
|
border-radius: var(--radius);
|
|
295
296
|
box-shadow: var(--boxShadow1);
|
|
296
297
|
background: var(--colorHeaderBackground);
|
|
297
298
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
299
|
+
> div {
|
|
300
|
+
display: inline-flex;
|
|
301
|
+
flex-direction: column;
|
|
302
|
+
padding: 20px;
|
|
303
|
+
cursor: auto;
|
|
304
|
+
gap: 12px;
|
|
305
|
+
text-align: left;
|
|
301
306
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
+
a {
|
|
308
|
+
color: var(--colorAccent);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.GroupByMethod {
|
|
312
|
+
display: flex;
|
|
313
|
+
align-items: center;
|
|
314
|
+
gap: 6px;
|
|
315
|
+
cursor: pointer;
|
|
316
|
+
}
|
|
307
317
|
}
|
|
308
318
|
}
|
|
309
319
|
|
package/src/Dashboard.js
CHANGED
|
@@ -96,9 +96,9 @@ function App() {
|
|
|
96
96
|
function Header() {
|
|
97
97
|
return (
|
|
98
98
|
r('header', null,
|
|
99
|
-
r('
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
r('object', {
|
|
100
|
+
data: 'Logo.svg',
|
|
101
|
+
type: 'image/svg+xml',
|
|
102
102
|
width: 120,
|
|
103
103
|
height: 22
|
|
104
104
|
}),
|
|
@@ -250,20 +250,24 @@ function SettingsMenu(id) {
|
|
|
250
250
|
}
|
|
251
251
|
},
|
|
252
252
|
|
|
253
|
-
r('
|
|
254
|
-
r('
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
253
|
+
r('div', null,
|
|
254
|
+
r('label', className(CSS.GroupByMethod),
|
|
255
|
+
r('input', {
|
|
256
|
+
ref: firstInputRef,
|
|
257
|
+
type: 'checkbox',
|
|
258
|
+
checked: store.groupByMethod,
|
|
259
|
+
onChange: store.toggleGroupByMethod
|
|
260
|
+
}),
|
|
261
|
+
r('span', null, t`Group by Method`)),
|
|
262
|
+
|
|
263
|
+
r('a', {
|
|
264
|
+
href: 'https://github.com/ericfortis/mockaton',
|
|
265
|
+
target: '_blank',
|
|
266
|
+
rel: 'noopener noreferrer'
|
|
267
|
+
}, t`Documentation`),
|
|
261
268
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
target: '_blank',
|
|
265
|
-
rel: 'noopener noreferrer'
|
|
266
|
-
}, t`Documentation`)))
|
|
269
|
+
r('p', null, `v${store.mockatonVersion}`)
|
|
270
|
+
)))
|
|
267
271
|
}
|
|
268
272
|
|
|
269
273
|
|
|
@@ -648,7 +652,7 @@ async function updatePayloadViewer(proxied, file, response) {
|
|
|
648
652
|
}))
|
|
649
653
|
else {
|
|
650
654
|
const body = await response.text() || t`/* Empty Response Body */`
|
|
651
|
-
if (mime === 'application/json')
|
|
655
|
+
if (mime === 'application/json')
|
|
652
656
|
payloadViewerCodeRef.elem.replaceChildren(SyntaxJSON(body))
|
|
653
657
|
else if (isXML(mime))
|
|
654
658
|
payloadViewerCodeRef.elem.replaceChildren(SyntaxXML(body))
|
package/src/DashboardStore.js
CHANGED
|
@@ -26,10 +26,13 @@ export const store = {
|
|
|
26
26
|
|
|
27
27
|
getSyncVersion: api.getSyncVersion,
|
|
28
28
|
|
|
29
|
+
mockatonVersion: '',
|
|
30
|
+
|
|
29
31
|
async fetchState() {
|
|
30
32
|
try {
|
|
31
33
|
const response = await api.getState()
|
|
32
34
|
if (!response.ok) throw response
|
|
35
|
+
store.mockatonVersion = response.headers.get('server').split(' ').pop()
|
|
33
36
|
Object.assign(store, await response.json())
|
|
34
37
|
store.render()
|
|
35
38
|
}
|
package/src/Filename.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const httpMethods = [ // @KeepSync
|
|
1
|
+
const httpMethods = [ // @KeepSync node:http.METHODS (this file is used on the client too)
|
|
2
2
|
'ACL', 'BIND', 'CHECKOUT', 'CONNECT', 'COPY', 'DELETE',
|
|
3
3
|
'GET', 'HEAD', 'LINK', 'LOCK', 'M-SEARCH', 'MERGE',
|
|
4
4
|
'MKACTIVITY', 'MKCALENDAR', 'MKCOL', 'MOVE', 'NOTIFY', 'OPTIONS',
|
|
@@ -28,7 +28,7 @@ export function validateFilename(file) {
|
|
|
28
28
|
if (!httpMethods.includes(method))
|
|
29
29
|
return `Unrecognized HTTP Method: "${method}"`
|
|
30
30
|
}
|
|
31
|
-
// TODO ThinkAbout 206 (reject, handle, or send in full?)
|
|
31
|
+
// TODO @ThinkAbout 206 (reject, handle, or send in full?)
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
export function parseFilename(file) {
|
|
@@ -53,7 +53,7 @@ function responseStatusIsValid(status) {
|
|
|
53
53
|
&& status >= 100
|
|
54
54
|
&& status <= 599
|
|
55
55
|
}
|
|
56
|
-
// TODO ThinkAbout allowing custom status codes
|
|
56
|
+
// TODO @ThinkAbout allowing custom status codes
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
export function makeMockFilename(url, method, status, ext) {
|
package/src/Logo.svg
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
2
|
<svg width="556" height="100" version="1.1" viewBox="0 0 556 100" xmlns="http://www.w3.org/2000/svg">
|
|
3
|
-
<style
|
|
3
|
+
<style>
|
|
4
4
|
@media (prefers-color-scheme: light) { :root { --color: #333 } }
|
|
5
5
|
@media (prefers-color-scheme: dark) { :root { --color: #eee } }
|
|
6
|
-
path {
|
|
6
|
+
path {
|
|
7
|
+
fill: #777;
|
|
8
|
+
fill: var(--color);
|
|
9
|
+
}
|
|
7
10
|
</style>
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
d="m392.75 1.373c-2.216 0-4 1.784-4 4v18.043h-5.3086c-2.216 0-4 1.784-4 4v4.793c0 2.216 1.784 4 4 4h5.3086v51.398c0 6.1465 3.7064 10.823 9.232 10.823h16.531c2.216 0 4-1.784 4-4v-4.793c0-2.216-1.784-4-4-4h-12.97v-49.428h9.8711c2.216 0 4-1.784 4-4v-4.793c0-2.216-1.784-4-4-4h-9.8711v-18.043c0-2.216-1.784-4-4-4zm122.96 23.896c-10.699 0-19.312 8.6137-19.312 19.312v49.812c0 2.216 1.784 4 4 4h4.7715c2.216 0 4-1.784 4-4v-45.648c0-6.9115 1.5651-12.477 8.4766-12.477h15.846c6.9115 0 8.4746 5.5651 8.4746 12.477v45.684c0 2.216 1.784 4 4 4h4.7715c2.216-1e-6 4-1.784 4-4v-49.848c0-10.699-8.6117-19.312-19.311-19.312zm-69.999 0c-10.699 0-19.312 8.6137-19.312 19.312v34.535c0 10.699 8.6137 19.312 19.312 19.312h19.717c10.699 0 19.311-8.6137 19.311-19.312v-34.535c0-10.699-8.6117-19.312-19.311-19.312zm1.9356 11h15.846c6.9115 0 8.4746 5.5651 8.4746 12.477v26.209c0 6.9115-1.5631 12.475-8.4746 12.475h-15.846c-6.9115 0-8.4766-5.5631-8.4766-12.475v-26.209c0-6.9115 1.5651-12.477 8.4766-12.477z"/>
|
|
16
|
-
</g>
|
|
11
|
+
<path
|
|
12
|
+
d="m13.75 1.8789c-5.9487 0.19352-10.865 4.5652-11.082 11.686v81.445c-1e-7 2.216 1.784 4 4 4h4.793c2.216 0 4-1.784 4-4v-64.982c0.02794-3.4488 3.0988-3.5551 4.2031-1.1562l16.615 59.059c1.4393 5.3711 5.1083 7.9633 8.7656 7.9473 3.6573 0.01603 7.3263-2.5762 8.7656-7.9473l16.615-59.059c1.1043-2.3989 4.1752-2.2925 4.2031 1.1562v64.982c0 2.216 1.784 4 4 4h4.793c2.216 0 4-1.784 4-4v-81.445c-0.17732-7.0807-5.1334-11.492-11.082-11.686-5.9487-0.19352-12.652 3.8309-15.609 13.619l-15.686 57.334-15.686-57.334c-2.9569-9.7882-9.6607-13.813-15.609-13.619zm239.19 0.074219c-2.216 0-4 1.784-4 4v89.057c0 2.216 1.784 4 4 4h4.793c2.216 0 3.9868-1.784 4-4l0.10644-17.94c0.0734-0.07237 12.175-13.75 12.175-13.75 5.6772 11.091 11.404 22.158 17.113 33.232 1.0168 1.9689 3.4217 2.7356 5.3906 1.7188l4.2578-2.1992c1.9689-1.0168 2.7356-3.4217 1.7188-5.3906-6.4691-12.585-12.958-25.16-19.442-37.738l17.223-19.771c1.4555-1.671 1.2803-4.189-0.39062-5.6445l-3.6133-3.1465c-0.73105-0.63679-1.6224-0.96212-2.5176-0.98633-1.151-0.03113-2.3063 0.43508-3.125 1.375l-28.896 33.174v-51.99c0-2.216-1.784-4-4-4zm-58.255 23.316c-10.699 0-19.312 8.6137-19.312 19.312v34.535c0 10.699 8.6137 19.312 19.312 19.312h19.717c10.699 0 19.311-8.6137 19.311-19.312l-0.125-7.8457c0-2.216-1.784-4-4-4h-4.6524c-2.216 0-4 1.784-4 4l3e-3 6.7888c3e-3 3.8063-1.5601 9.3694-8.4716 9.3694h-15.846c-6.9115 0-8.4766-5.5631-8.4766-12.475v-26.209c0-6.9115 1.5651-12.477 8.4766-12.477h15.846c6.6937 0 8.3697 5.2207 8.4687 11.828v2.2207c0 2.216 1.784 4 4 4h4.6524c2.216 0 4-1.784 4-4l0.125-5.7363c0-10.699-8.6117-19.312-19.311-19.312zm-72.182 0c-10.699 0-19.312 8.6137-19.312 19.312v34.535c0 10.699 8.6137 19.312 19.312 19.312h19.717c10.699 0 19.311-8.6137 19.311-19.312v-34.535c0-10.699-8.6117-19.312-19.311-19.312zm1.9356 11h15.846c6.9115 0 8.4746 5.5651 8.4746 12.477v26.209c0 6.9115-1.5631 12.475-8.4746 12.475h-15.846c-6.9115 0-8.4766-5.5631-8.4766-12.475v-26.209c0-6.9115 1.5651-12.477 8.4766-12.477z"/>
|
|
13
|
+
<path
|
|
14
|
+
d="m331.9 25.27c-10.699 0-19.312 8.6137-19.312 19.312v4.3682c0 2.216 1.784 4 4 4h4.7715c2.216 0 4-1.784 4-4v-0.20414c0-6.9115 1.5651-12.477 8.4766-12.477h15.846c6.9115 0 8.4746 5.5651 8.4746 12.477v7.0148h-28.059c-10.699 0-19.312 8.6117-19.312 19.311v4.0477c0 10.699 8.6137 19.313 19.312 19.312h17.812c2.216-1e-6 4-1.784 4-4v-4.7715c0-2.216-1.784-4-4-4h-13.648c-6.9115-2e-5 -12.477-1.5651-12.477-8.5649 0-6.9998 5.5651-8.5629 12.477-8.5629h23.895v25.897c0 2.216 1.784 4 4 4h4.7715c2.216-1e-6 4-1.784 4-4v-49.848c0-10.699-8.6117-19.312-19.311-19.312z"
|
|
15
|
+
opacity="0.75"/>
|
|
16
|
+
<path
|
|
17
|
+
d="m392.75 1.373c-2.216 0-4 1.784-4 4v18.043h-5.3086c-2.216 0-4 1.784-4 4v4.793c0 2.216 1.784 4 4 4h5.3086v51.398c0 6.1465 3.7064 10.823 9.232 10.823h16.531c2.216 0 4-1.784 4-4v-4.793c0-2.216-1.784-4-4-4h-12.97v-49.428h9.8711c2.216 0 4-1.784 4-4v-4.793c0-2.216-1.784-4-4-4h-9.8711v-18.043c0-2.216-1.784-4-4-4zm122.96 23.896c-10.699 0-19.312 8.6137-19.312 19.312v49.812c0 2.216 1.784 4 4 4h4.7715c2.216 0 4-1.784 4-4v-45.648c0-6.9115 1.5651-12.477 8.4766-12.477h15.846c6.9115 0 8.4746 5.5651 8.4746 12.477v45.684c0 2.216 1.784 4 4 4h4.7715c2.216-1e-6 4-1.784 4-4v-49.848c0-10.699-8.6117-19.312-19.311-19.312zm-69.999 0c-10.699 0-19.312 8.6137-19.312 19.312v34.535c0 10.699 8.6137 19.312 19.312 19.312h19.717c10.699 0 19.311-8.6137 19.311-19.312v-34.535c0-10.699-8.6117-19.312-19.311-19.312zm1.9356 11h15.846c6.9115 0 8.4746 5.5651 8.4746 12.477v26.209c0 6.9115-1.5631 12.475-8.4746 12.475h-15.846c-6.9115 0-8.4766-5.5631-8.4766-12.475v-26.209c0-6.9115 1.5651-12.477 8.4766-12.477z"/>
|
|
17
18
|
</svg>
|
package/src/MockBroker.js
CHANGED
|
@@ -22,7 +22,7 @@ export class MockBroker {
|
|
|
22
22
|
|
|
23
23
|
#sortMocks() {
|
|
24
24
|
this.mocks.sort()
|
|
25
|
-
const defaults = this.mocks.filter(
|
|
25
|
+
const defaults = this.mocks.filter(f => includesComment(f, DEFAULT_MOCK_COMMENT))
|
|
26
26
|
this.mocks = Array.from(new Set(defaults).union(new Set(this.mocks)))
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -55,18 +55,19 @@ export class MockBroker {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
toggle500() {
|
|
58
|
-
this.
|
|
59
|
-
if (
|
|
58
|
+
const shouldUnset = this.auto500 || this.status === 500
|
|
59
|
+
if (shouldUnset)
|
|
60
60
|
this.selectDefaultFile()
|
|
61
61
|
else {
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
64
|
-
this.selectFile(
|
|
62
|
+
const f500 = this.mocks.find(this.#is500)
|
|
63
|
+
if (f500)
|
|
64
|
+
this.selectFile(f500)
|
|
65
65
|
else {
|
|
66
66
|
this.auto500 = true
|
|
67
|
-
this.status = 500
|
|
67
|
+
this.status = 500
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
+
this.proxied = false
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
setDelayed(delayed) {
|
package/src/MockDispatcher.js
CHANGED
|
@@ -31,20 +31,20 @@ export async function dispatchMock(req, response) {
|
|
|
31
31
|
if (cookie.getCurrent())
|
|
32
32
|
response.setHeader('Set-Cookie', cookie.getCurrent())
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
response.statusCode = broker.auto500 ? 500 : broker.status // TESTME plugins can change it
|
|
34
|
+
response.statusCode = broker.auto500
|
|
35
|
+
? 500
|
|
36
|
+
: broker.status
|
|
38
37
|
const { mime, body } = broker.auto500
|
|
39
38
|
? { mime: '', body: '' }
|
|
40
39
|
: await applyPlugins(join(config.mocksDir, broker.file), req, response)
|
|
41
40
|
|
|
42
|
-
logger.accessMock(req.url, broker.file)
|
|
43
41
|
response.setHeader('Content-Type', mime)
|
|
44
42
|
response.setHeader('Content-Length', length(body))
|
|
45
43
|
|
|
46
44
|
setTimeout(() => response.end(isHead ? null : body),
|
|
47
45
|
Number(broker.delayed && calcDelay()))
|
|
46
|
+
|
|
47
|
+
logger.accessMock(req.url, broker.file)
|
|
48
48
|
}
|
|
49
49
|
catch (error) { // TESTME
|
|
50
50
|
if (error?.code === 'ENOENT') // mock-file has been deleted
|
|
@@ -77,7 +77,7 @@ export async function jsToJsonPlugin(filePath, req, response) {
|
|
|
77
77
|
|
|
78
78
|
function length(body) {
|
|
79
79
|
if (typeof body === 'string') return Buffer.byteLength(body)
|
|
80
|
-
if (Buffer.isBuffer(body)) return body.length
|
|
80
|
+
if (Buffer.isBuffer(body)) return body.length // TESTME
|
|
81
81
|
if (body instanceof Uint8Array) return body.byteLength
|
|
82
82
|
return 0
|
|
83
83
|
}
|
package/src/Mockaton.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createServer } from 'node:http'
|
|
2
|
+
import pkgJSON from '../package.json' with { type: 'json' }
|
|
2
3
|
|
|
3
4
|
import { API } from './ApiConstants.js'
|
|
4
5
|
import { logger } from './utils/logger.js'
|
|
@@ -11,7 +12,10 @@ import { setCorsHeaders, isPreflight } from './utils/http-cors.js'
|
|
|
11
12
|
import { watchMocksDir, watchStaticDir } from './Watcher.js'
|
|
12
13
|
import { apiPatchRequests, apiGetRequests } from './Api.js'
|
|
13
14
|
import { BodyReaderError, hasControlChars } from './utils/http-request.js'
|
|
14
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
setHeaders, sendNoContent, sendInternalServerError,
|
|
17
|
+
sendUnprocessable, sendTooLongURI, sendBadRequest
|
|
18
|
+
} from './utils/http-response.js'
|
|
15
19
|
|
|
16
20
|
|
|
17
21
|
export function Mockaton(options) {
|
|
@@ -41,7 +45,9 @@ export function Mockaton(options) {
|
|
|
41
45
|
|
|
42
46
|
async function onRequest(req, response) {
|
|
43
47
|
response.on('error', logger.warn)
|
|
44
|
-
|
|
48
|
+
|
|
49
|
+
setHeaders(response, ['Server', `Mockaton ${pkgJSON.version}`])
|
|
50
|
+
setHeaders(response, config.extraHeaders)
|
|
45
51
|
|
|
46
52
|
const url = req.url || ''
|
|
47
53
|
|
package/src/config.js
CHANGED
|
@@ -83,10 +83,8 @@ export function setup(options) {
|
|
|
83
83
|
logger.setLevel(config.logLevel)
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
|
|
87
86
|
export const isFileAllowed = f => !config.ignore.test(f)
|
|
88
87
|
|
|
89
88
|
export const calcDelay = () => config.delayJitter
|
|
90
89
|
? config.delay * (1 + Math.random() * config.delayJitter)
|
|
91
90
|
: config.delay
|
|
92
|
-
|
|
@@ -70,10 +70,8 @@ function filenameIsValid(file) {
|
|
|
70
70
|
|
|
71
71
|
export function unregisterMock(file) {
|
|
72
72
|
const broker = brokerByFilename(file)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const isEmpty = broker.unregister(file)
|
|
76
|
-
if (isEmpty) {
|
|
73
|
+
const hasNoMoreMocks = broker?.unregister(file)
|
|
74
|
+
if (hasNoMoreMocks) {
|
|
77
75
|
const { method, urlMask } = parseFilename(file)
|
|
78
76
|
delete collection[method][urlMask]
|
|
79
77
|
if (!Object.keys(collection[method]).length)
|
|
@@ -96,9 +94,7 @@ export function brokerByFilename(file) {
|
|
|
96
94
|
* worry about the primacy of array-like keys when iterating.
|
|
97
95
|
@returns {MockBroker | undefined} */
|
|
98
96
|
export function brokerByRoute(method, url) {
|
|
99
|
-
|
|
100
|
-
return
|
|
101
|
-
const brokers = Object.values(collection[method])
|
|
97
|
+
const brokers = Object.values(collection[method] || {})
|
|
102
98
|
for (let i = brokers.length - 1; i >= 0; i--)
|
|
103
99
|
if (brokers[i].urlMaskMatches(url))
|
|
104
100
|
return brokers[i]
|
package/src/staticCollection.js
CHANGED
|
@@ -29,7 +29,7 @@ export function init() {
|
|
|
29
29
|
|
|
30
30
|
/** @returns {boolean} registered */
|
|
31
31
|
export function registerMock(relativeFile) {
|
|
32
|
-
if (!isFileAllowed(basename(relativeFile)))
|
|
32
|
+
if (!isFileAllowed(basename(relativeFile)))
|
|
33
33
|
return false
|
|
34
34
|
|
|
35
35
|
const route = '/' + relativeFile
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import fs, { readFileSync } from 'node:fs'
|
|
2
|
+
|
|
2
3
|
import { logger } from './logger.js'
|
|
3
4
|
import { mimeFor } from './mime.js'
|
|
4
5
|
import { HEADER_502 } from '../ApiConstants.js'
|
|
5
6
|
|
|
6
7
|
|
|
8
|
+
export function setHeaders(response, headers) {
|
|
9
|
+
for (let i = 0; i < headers.length; i += 2)
|
|
10
|
+
response.setHeader(headers[i], headers[i + 1])
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
export function sendOK(response) {
|
|
8
14
|
logger.access(response)
|
|
9
15
|
response.end()
|