mockaton 12.7.1 → 13.0.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 +53 -17
- package/index.d.ts +2 -13
- package/package.json +4 -9
- package/src/client/ApiCommander.js +7 -11
- package/src/client/ApiConstants.js +1 -4
- package/src/client/Filename.js +22 -22
- package/src/client/app-header.js +6 -6
- package/src/client/app-payload-viewer.js +52 -25
- package/src/client/app-store.js +11 -64
- package/src/client/app-store.test.js +15 -1
- package/src/client/app.css +54 -44
- package/src/client/app.js +28 -100
- package/src/client/{app-icons.js → graphics.js} +1 -1
- package/src/client/watcherDev.js +7 -7
- package/src/server/Api.js +4 -38
- package/src/server/MockBroker.js +16 -13
- package/src/server/MockDispatcher.js +18 -4
- package/src/server/Mockaton.js +1 -8
- package/src/server/Mockaton.test.config.js +1 -3
- package/src/server/Mockaton.test.js +98 -194
- package/src/server/ProxyRelay.js +26 -15
- package/src/server/Watcher.js +1 -33
- package/src/server/cli.js +9 -13
- package/src/server/cli.test.js +4 -5
- package/src/server/config.js +0 -7
- package/src/server/mockBrokersCollection.js +11 -13
- package/src/server/utils/mime.js +0 -1
- package/src/server/StaticDispatcher.js +0 -36
- package/src/server/staticCollection.js +0 -56
package/README.md
CHANGED
|
@@ -10,20 +10,6 @@ for testing difficult to reproduce backend states.
|
|
|
10
10
|
|
|
11
11
|
## [Documentation ↗](https://mockaton.com) | [Changelog ↗](https://mockaton.com/changelog)
|
|
12
12
|
|
|
13
|
-
## Overview
|
|
14
|
-
With Mockaton, you don’t need to write code for wiring up your
|
|
15
|
-
mocks. Instead, a given directory is scanned for filenames
|
|
16
|
-
following a convention similar to the URLs.
|
|
17
|
-
|
|
18
|
-
For example, for [/api/company/123](#), the filename could be:
|
|
19
|
-
|
|
20
|
-
<pre>
|
|
21
|
-
<code>my-mocks-dir/<b>api/company</b>/[id].GET.200.json</code>
|
|
22
|
-
</pre>
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
## Dashboard
|
|
26
|
-
|
|
27
13
|
<picture>
|
|
28
14
|
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/ericfortis/mockaton/refs/heads/main/pixaton-tests/tests/macos/pic-for-readme.vp762x762.light.gold.png">
|
|
29
15
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ericfortis/mockaton/refs/heads/main/pixaton-tests/tests/macos/pic-for-readme.vp762x762.dark.gold.png">
|
|
@@ -34,9 +20,8 @@ For example, for [/api/company/123](#), the filename could be:
|
|
|
34
20
|
|
|
35
21
|
|
|
36
22
|
## Quick Start (Docker)
|
|
37
|
-
This will spin up Mockaton with the sample
|
|
38
|
-
included in this repo mounted on the container. Mentioned
|
|
39
|
-
and [mockaton-static-mocks/](./mockaton-static-mocks).
|
|
23
|
+
This will spin up Mockaton with the sample directory
|
|
24
|
+
included in this repo mounted on the container. Mentioned dir is: [mockaton-mocks/](./mockaton-mocks).
|
|
40
25
|
|
|
41
26
|
```sh
|
|
42
27
|
git clone https://github.com/ericfortis/mockaton.git --depth 1
|
|
@@ -49,3 +34,54 @@ Test it:
|
|
|
49
34
|
```shell
|
|
50
35
|
curl localhost:2020/api/user
|
|
51
36
|
```
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## Overview
|
|
40
|
+
With Mockaton, you don’t need to write code for wiring up your
|
|
41
|
+
mocks. Instead, a given directory is scanned for filenames
|
|
42
|
+
following a convention similar to the URLs.
|
|
43
|
+
|
|
44
|
+
For example, for [/api/company/123](#), the file could be:
|
|
45
|
+
|
|
46
|
+
<code>my_mocks_dir/<b>api/company/[id]</b>.GET.200.json</code>
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"name": "Acme, Inc."
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Or, you can write it in TypeScript (it will be sent as JSON).
|
|
54
|
+
|
|
55
|
+
<code>my_mocks_dir/<b>api/company/[id]</b>.GET.200.ts</code>
|
|
56
|
+
```ts
|
|
57
|
+
export default {
|
|
58
|
+
name: 'Acme, Inc.'
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Similarly, you can handle logic with [Functional Mocks](https://mockaton.com/functional-mocks):
|
|
63
|
+
|
|
64
|
+
<code>my_mocks_dir/<b>api/company/[companyId]/user/[userId]</b>.GET.200.ts</code>
|
|
65
|
+
```ts
|
|
66
|
+
import { IncomingMessage, OutgoingMessage } from 'node:http'
|
|
67
|
+
import { parseSplats } from 'mockaton'
|
|
68
|
+
|
|
69
|
+
export default async function (req: IncomingMessage, response: OutgoingMessage) {
|
|
70
|
+
const { companyId, userId } = parseSplats(req.url, import.meta.filename)
|
|
71
|
+
const foo = await getFoo()
|
|
72
|
+
return JSON.stringify({
|
|
73
|
+
foo,
|
|
74
|
+
companyId,
|
|
75
|
+
userId,
|
|
76
|
+
name: 'Acme, Inc.'
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Browser Extension
|
|
82
|
+
[Browser Extension](https://mockaton.com/scraping) for scraping responses from your backend.
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
## API
|
|
86
|
+
Programmatic [Control API](https://mockaton.com/api).
|
|
87
|
+
|
package/index.d.ts
CHANGED
|
@@ -11,7 +11,6 @@ export type Plugin = (
|
|
|
11
11
|
|
|
12
12
|
export interface Config {
|
|
13
13
|
mocksDir?: string
|
|
14
|
-
staticDir?: string
|
|
15
14
|
ignore?: RegExp
|
|
16
15
|
watcherEnabled?: boolean
|
|
17
16
|
watcherDebounceMs?: number
|
|
@@ -74,7 +73,8 @@ export type ClientMockBroker = {
|
|
|
74
73
|
mocks: string[]
|
|
75
74
|
file: string
|
|
76
75
|
status: number
|
|
77
|
-
|
|
76
|
+
isStatic: boolean
|
|
77
|
+
autoStatus: number
|
|
78
78
|
delayed: boolean
|
|
79
79
|
proxied: boolean
|
|
80
80
|
}
|
|
@@ -84,19 +84,8 @@ export type ClientBrokersByMethod = {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
export type ClientStaticBroker = {
|
|
88
|
-
route: string
|
|
89
|
-
delayed: boolean
|
|
90
|
-
status: number
|
|
91
|
-
}
|
|
92
|
-
export type ClientStaticBrokers = {
|
|
93
|
-
[route: string]: ClientStaticBroker
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
87
|
export interface State {
|
|
98
88
|
brokersByMethod: ClientBrokersByMethod
|
|
99
|
-
staticBrokers: ClientStaticBrokers
|
|
100
89
|
|
|
101
90
|
cookies: [label: string, selected: boolean][]
|
|
102
91
|
comments: string[]
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "mockaton",
|
|
3
3
|
"description": "HTTP Mock Server",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "13.0.0",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
8
8
|
"import": "./index.js",
|
|
@@ -17,18 +17,13 @@
|
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"homepage": "https://mockaton.com",
|
|
19
19
|
"repository": "https://github.com/ericfortis/mockaton",
|
|
20
|
-
"keywords": [
|
|
21
|
-
"mock-server",
|
|
22
|
-
"rest-api",
|
|
23
|
-
"mock",
|
|
24
|
-
"api",
|
|
25
|
-
"testing"
|
|
26
|
-
],
|
|
20
|
+
"keywords": ["mock-server"],
|
|
27
21
|
"bin": {
|
|
28
22
|
"mockaton": "src/server/cli.js"
|
|
29
23
|
},
|
|
30
24
|
"scripts": {
|
|
31
|
-
"start": "make start"
|
|
25
|
+
"start": "make start",
|
|
26
|
+
"test": "make test"
|
|
32
27
|
},
|
|
33
28
|
"engines": {
|
|
34
29
|
"node": ">=22.18 <23 || >=23.6"
|
|
@@ -40,7 +40,9 @@ export class Commander {
|
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
/** @returns {JsonPromise<ClientMockBroker>} */
|
|
43
|
-
|
|
43
|
+
toggleStatus = (status, method, urlMask) => this.#patch(API.toggleStatus, [status, method, urlMask])
|
|
44
|
+
|
|
45
|
+
// TODO change Status or Toggle404?
|
|
44
46
|
|
|
45
47
|
/** @returns {JsonPromise<ClientMockBroker>} */
|
|
46
48
|
setRouteIsProxied = (method, urlMask, proxied) => this.#patch(API.proxied, [method, urlMask, proxied])
|
|
@@ -49,19 +51,13 @@ export class Commander {
|
|
|
49
51
|
setRouteIsDelayed = (method, urlMask, delayed) => this.#patch(API.delay, [method, urlMask, delayed])
|
|
50
52
|
|
|
51
53
|
|
|
52
|
-
setStaticRouteStatus = (urlMask, status) => this.#patch(API.staticStatus, [urlMask, status])
|
|
53
|
-
|
|
54
|
-
setStaticRouteIsDelayed = (urlMask, delayed) => this.#patch(API.delayStatic, [urlMask, delayed])
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
54
|
/** @returns {JsonPromise<State>} */
|
|
59
55
|
getState = () => fetch(this.addr + API.state)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
/**
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
/**
|
|
63
59
|
* SSE - Streams an incremental version when a mock is added, deleted, or renamed
|
|
64
|
-
* @returns {Promise<Response>}
|
|
60
|
+
* @returns {Promise<Response>}
|
|
65
61
|
*/
|
|
66
62
|
getSyncVersion = () => fetch(this.addr + API.syncVersion)
|
|
67
63
|
}
|
|
@@ -4,13 +4,11 @@ const MOUNT = '/mockaton'
|
|
|
4
4
|
|
|
5
5
|
export const API = {
|
|
6
6
|
dashboard: MOUNT,
|
|
7
|
-
|
|
8
7
|
bulkSelect: MOUNT + '/bulk-select-by-comment',
|
|
9
8
|
collectProxied: MOUNT + '/collect-proxied',
|
|
10
9
|
cookies: MOUNT + '/cookies',
|
|
11
10
|
cors: MOUNT + '/cors',
|
|
12
11
|
delay: MOUNT + '/delay',
|
|
13
|
-
delayStatic: MOUNT + '/delay-static',
|
|
14
12
|
fallback: MOUNT + '/fallback',
|
|
15
13
|
globalDelay: MOUNT + '/global-delay',
|
|
16
14
|
globalDelayJitter: MOUNT + '/global-delay-jitter',
|
|
@@ -18,10 +16,9 @@ export const API = {
|
|
|
18
16
|
reset: MOUNT + '/reset',
|
|
19
17
|
select: MOUNT + '/select',
|
|
20
18
|
state: MOUNT + '/state',
|
|
21
|
-
staticStatus: MOUNT + '/static-status',
|
|
22
19
|
syncVersion: MOUNT + '/sync-version',
|
|
23
20
|
throws: MOUNT + '/throws',
|
|
24
|
-
|
|
21
|
+
toggleStatus: MOUNT + '/toggle-status',
|
|
25
22
|
watchHotReload: MOUNT + '/watch-hot-reload',
|
|
26
23
|
watchMocks: MOUNT + '/watch-mocks',
|
|
27
24
|
}
|
package/src/client/Filename.js
CHANGED
|
@@ -19,29 +19,29 @@ export function includesComment(file, search) {
|
|
|
19
19
|
return extractComments(file).some(c => c.includes(search))
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
export function validateFilename(file) {
|
|
22
|
+
export function parseFilename(file) {
|
|
24
23
|
const tokens = file.replace(reComments, '').split('.')
|
|
25
|
-
if (tokens.length < 4)
|
|
26
|
-
return 'Invalid Filename Convention'
|
|
27
24
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (!METHODS.includes(method))
|
|
33
|
-
return `Unrecognized HTTP Method: "${method}"`
|
|
34
|
-
}
|
|
25
|
+
const followsConvention = tokens.length > 3
|
|
26
|
+
&& responseStatusIsValid(Number(tokens.at(-2)))
|
|
27
|
+
&& METHODS.includes(tokens.at(-3))
|
|
28
|
+
const isStatic = !followsConvention
|
|
35
29
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
30
|
+
return isStatic
|
|
31
|
+
? {
|
|
32
|
+
isStatic,
|
|
33
|
+
ext: tokens.pop() || '',
|
|
34
|
+
status: 200,
|
|
35
|
+
method: 'GET',
|
|
36
|
+
urlMask: '/' + file
|
|
37
|
+
}
|
|
38
|
+
: {
|
|
39
|
+
isStatic,
|
|
40
|
+
ext: tokens.pop(),
|
|
41
|
+
status: Number(tokens.pop()),
|
|
42
|
+
method: tokens.pop(),
|
|
43
|
+
urlMask: '/' + removeTrailingSlash(tokens.join('.'))
|
|
44
|
+
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
export function removeTrailingSlash(url = '') {
|
|
@@ -61,9 +61,9 @@ function responseStatusIsValid(status) {
|
|
|
61
61
|
&& status <= 599
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
export function makeMockFilename(url, method, status, ext) {
|
|
64
|
+
export function makeMockFilename(url, method, status, ext, comment = '') {
|
|
65
65
|
const urlMask = replaceIds(removeTrailingSlash(url))
|
|
66
|
-
return [urlMask, method, status, ext].join('.')
|
|
66
|
+
return [urlMask + comment, method, status, ext].join('.')
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
const reUuidV4 = /([0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})/gi
|
package/src/client/app-header.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createElement as r, t, defineClassNames } from './dom-utils.js'
|
|
2
|
+
import { Logo, HelpIcon } from './graphics.js'
|
|
2
3
|
import { store } from './app-store.js'
|
|
3
|
-
import { Logo, HelpIcon } from './app-icons.js'
|
|
4
4
|
|
|
5
5
|
import CSS from './app.css' with { type: 'css' }
|
|
6
6
|
defineClassNames(CSS)
|
|
@@ -47,8 +47,8 @@ function GlobalDelayField() {
|
|
|
47
47
|
step: 100,
|
|
48
48
|
autocomplete: 'none',
|
|
49
49
|
value: store.delay,
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
onWheel: [onWheel, { passive: true }],
|
|
51
|
+
onChange
|
|
52
52
|
})))
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -73,13 +73,13 @@ function GlobalDelayJitterField() {
|
|
|
73
73
|
r('span', null, t`Max Jitter %`),
|
|
74
74
|
r('input', {
|
|
75
75
|
type: 'number',
|
|
76
|
+
autocomplete: 'none',
|
|
76
77
|
min: 0,
|
|
77
78
|
max: 300,
|
|
78
79
|
step: 10,
|
|
79
|
-
autocomplete: 'none',
|
|
80
80
|
value: (store.delayJitter * 100).toFixed(0),
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
onWheel: [onWheel, { passive: true }],
|
|
82
|
+
onChange
|
|
83
83
|
})))
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -7,25 +7,29 @@ import CSS from './app.css' with { type: 'css' }
|
|
|
7
7
|
defineClassNames(CSS)
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
const
|
|
11
|
-
const
|
|
10
|
+
const titleRef = {}
|
|
11
|
+
const codeRef = {}
|
|
12
12
|
|
|
13
13
|
export function PayloadViewer() {
|
|
14
14
|
return (
|
|
15
15
|
r('div', { className: CSS.PayloadViewer },
|
|
16
16
|
|
|
17
17
|
r('div', { className: CSS.SubToolbar },
|
|
18
|
-
r('h2', { ref:
|
|
18
|
+
r('h2', { ref: titleRef },
|
|
19
19
|
!store.hasChosenLink && t`Preview`)),
|
|
20
20
|
|
|
21
21
|
r('pre', null,
|
|
22
|
-
r('code', { ref:
|
|
22
|
+
r('code', { ref: codeRef },
|
|
23
23
|
!store.hasChosenLink && t`Click a link to preview it`))))
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
function PayloadViewerTitle(file, statusText) {
|
|
28
|
-
const { method, status, ext } = parseFilename(file)
|
|
28
|
+
const { method, status, ext, isStatic } = parseFilename(file)
|
|
29
|
+
|
|
30
|
+
if (isStatic)
|
|
31
|
+
return r('span', null, file)
|
|
32
|
+
|
|
29
33
|
const fileNameWithComments = file.split('.').slice(0, -3).join('.')
|
|
30
34
|
return (
|
|
31
35
|
r('span', null,
|
|
@@ -66,8 +70,8 @@ export async function previewMock() {
|
|
|
66
70
|
previewMock.controller = new AbortController
|
|
67
71
|
|
|
68
72
|
const spinnerTimer = setTimeout(() => {
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
titleRef.elem.replaceChildren(t`Fetching…`)
|
|
74
|
+
codeRef.elem.replaceChildren(PayloadViewerProgressBar())
|
|
71
75
|
}, SPINNER_DELAY)
|
|
72
76
|
|
|
73
77
|
try {
|
|
@@ -83,7 +87,7 @@ export async function previewMock() {
|
|
|
83
87
|
catch (error) {
|
|
84
88
|
clearTimeout(spinnerTimer)
|
|
85
89
|
store.onError(error)
|
|
86
|
-
|
|
90
|
+
codeRef.elem.replaceChildren()
|
|
87
91
|
}
|
|
88
92
|
}
|
|
89
93
|
|
|
@@ -91,30 +95,53 @@ export async function previewMock() {
|
|
|
91
95
|
async function updatePayloadViewer(proxied, file, response) {
|
|
92
96
|
const mime = response.headers.get('content-type') || ''
|
|
93
97
|
|
|
94
|
-
|
|
98
|
+
titleRef.elem.replaceChildren(proxied
|
|
95
99
|
? PayloadViewerTitleWhenProxied(response)
|
|
96
100
|
: PayloadViewerTitle(file, response.statusText))
|
|
97
101
|
|
|
98
|
-
|
|
99
|
-
|
|
102
|
+
// All branches naively assume GET 200
|
|
103
|
+
if (mime.startsWith('image/'))
|
|
104
|
+
codeRef.elem.replaceChildren(r('img', {
|
|
100
105
|
src: URL.createObjectURL(await response.blob())
|
|
101
106
|
}))
|
|
102
|
-
else {
|
|
103
|
-
const body = await response.text() || t`/* Empty Response Body */`
|
|
104
|
-
if (mime === 'application/json')
|
|
105
|
-
payloadViewerCodeRef.elem.replaceChildren(SyntaxJSON(body))
|
|
106
|
-
else if (isXML(mime))
|
|
107
|
-
payloadViewerCodeRef.elem.replaceChildren(SyntaxXML(body))
|
|
108
|
-
else
|
|
109
|
-
payloadViewerCodeRef.elem.textContent = body
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
107
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
108
|
+
else if (mime.startsWith('video/'))
|
|
109
|
+
codeRef.elem.replaceChildren(r('video', {
|
|
110
|
+
src: store.chosenLink.urlMask,
|
|
111
|
+
controls: true
|
|
112
|
+
}))
|
|
113
|
+
|
|
114
|
+
else if (mime.startsWith('audio/'))
|
|
115
|
+
codeRef.elem.replaceChildren(r('audio', {
|
|
116
|
+
src: store.chosenLink.urlMask,
|
|
117
|
+
controls: true
|
|
118
|
+
}))
|
|
117
119
|
|
|
120
|
+
else if (['text/html', 'application/pdf'].includes(mime))
|
|
121
|
+
codeRef.elem.replaceChildren(r('iframe', {
|
|
122
|
+
src: store.chosenLink.urlMask // using a blob is would need to allow inline styles etc in CSP
|
|
123
|
+
}))
|
|
124
|
+
|
|
125
|
+
else if (mime === 'application/json')
|
|
126
|
+
codeRef.elem.replaceChildren(SyntaxJSON(await bodyAsText()))
|
|
127
|
+
|
|
128
|
+
else if (['text/xml', 'application/xml'].includes(mime))
|
|
129
|
+
codeRef.elem.replaceChildren(SyntaxXML(await bodyAsText()))
|
|
130
|
+
|
|
131
|
+
else if (mime.startsWith('text/'))
|
|
132
|
+
codeRef.elem.textContent = await bodyAsText()
|
|
133
|
+
|
|
134
|
+
else
|
|
135
|
+
codeRef.elem.replaceChildren(r('a', {
|
|
136
|
+
href: URL.createObjectURL(await response.blob()),
|
|
137
|
+
download: store.chosenLink.urlMask
|
|
138
|
+
}, t`Download`))
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
function bodyAsText() {
|
|
142
|
+
return response.text() || t`/* Empty Response Body */`
|
|
143
|
+
}
|
|
144
|
+
}
|
|
118
145
|
|
|
119
146
|
|
|
120
147
|
function SyntaxJSON(json) {
|
package/src/client/app-store.js
CHANGED
|
@@ -12,7 +12,6 @@ export const store = {
|
|
|
12
12
|
renderRow(method, urlMask) {},
|
|
13
13
|
|
|
14
14
|
brokersByMethod: /** @type ClientBrokersByMethod */ {},
|
|
15
|
-
staticBrokers: /** @type ClientStaticBrokers */ {},
|
|
16
15
|
|
|
17
16
|
cookies: [],
|
|
18
17
|
comments: [],
|
|
@@ -148,19 +147,6 @@ export const store = {
|
|
|
148
147
|
return r
|
|
149
148
|
},
|
|
150
149
|
|
|
151
|
-
staticBrokersAsRows() {
|
|
152
|
-
const rows = Object.values(store.staticBrokers)
|
|
153
|
-
.map(b => new StaticBrokerRowModel(b))
|
|
154
|
-
.sort((a, b) => a.urlMask.localeCompare(b.urlMask))
|
|
155
|
-
const urlMasksDittoed = dittoSplitPaths(rows.map(r => r.urlMask))
|
|
156
|
-
rows.forEach((r, i) => {
|
|
157
|
-
r.setUrlMaskDittoed(urlMasksDittoed[i])
|
|
158
|
-
r.setIsNew(!store._dittoCache.has(r.key))
|
|
159
|
-
store._dittoCache.set(r.key, urlMasksDittoed[i])
|
|
160
|
-
})
|
|
161
|
-
return rows
|
|
162
|
-
},
|
|
163
|
-
|
|
164
150
|
previewLink(method, urlMask) {
|
|
165
151
|
store.setChosenLink(method, urlMask)
|
|
166
152
|
store.renderRow(method, urlMask)
|
|
@@ -175,8 +161,8 @@ export const store = {
|
|
|
175
161
|
})
|
|
176
162
|
},
|
|
177
163
|
|
|
178
|
-
|
|
179
|
-
store._request(() => api.
|
|
164
|
+
toggleStatus(status, method, urlMask) {
|
|
165
|
+
store._request(() => api.toggleStatus(status, method, urlMask), async response => {
|
|
180
166
|
store.setBroker(await response.json())
|
|
181
167
|
store.setChosenLink(method, urlMask)
|
|
182
168
|
store.renderRow(method, urlMask)
|
|
@@ -195,18 +181,6 @@ export const store = {
|
|
|
195
181
|
store._request(() => api.setRouteIsDelayed(method, urlMask, checked), async response => {
|
|
196
182
|
store.setBroker(await response.json())
|
|
197
183
|
})
|
|
198
|
-
},
|
|
199
|
-
|
|
200
|
-
setDelayedStatic(route, checked) {
|
|
201
|
-
store._request(() => api.setStaticRouteIsDelayed(route, checked), () => {
|
|
202
|
-
store.staticBrokers[route].delayed = checked
|
|
203
|
-
})
|
|
204
|
-
},
|
|
205
|
-
|
|
206
|
-
setStaticRouteStatus(route, status) {
|
|
207
|
-
store._request(() => api.setStaticRouteStatus(route, status), () => {
|
|
208
|
-
store.staticBrokers[route].status = status
|
|
209
|
-
})
|
|
210
184
|
}
|
|
211
185
|
}
|
|
212
186
|
|
|
@@ -303,28 +277,25 @@ export class BrokerRowModel {
|
|
|
303
277
|
}
|
|
304
278
|
|
|
305
279
|
get status() { return this.#broker.status }
|
|
306
|
-
get
|
|
280
|
+
get autoStatus() { return this.#broker.autoStatus }
|
|
281
|
+
get isStatic() { return this.#broker.isStatic }
|
|
307
282
|
get delayed() { return this.#broker.delayed }
|
|
308
|
-
get proxied() { return this.#
|
|
283
|
+
get proxied() { return this.#broker.proxied && this.#canProxy }
|
|
309
284
|
get selectedFile() { return this.#broker.file }
|
|
310
|
-
get selectedIdx() {
|
|
311
|
-
|
|
312
|
-
}
|
|
313
|
-
get selectedFileIs4xx() {
|
|
314
|
-
return this.status >= 400 && this.status < 500
|
|
315
|
-
}
|
|
285
|
+
get selectedIdx() { return this.opts.findIndex(([, , selected]) => selected) }
|
|
286
|
+
get selectedFileIs4xx() { return this.status >= 400 && this.status < 500 }
|
|
316
287
|
|
|
317
288
|
#makeOptions() {
|
|
318
289
|
const opts = this.#broker.mocks.map(f => [
|
|
319
290
|
f,
|
|
320
291
|
this.#optionLabelFor(f),
|
|
321
|
-
!this.
|
|
292
|
+
!this.autoStatus && !this.proxied && f === this.selectedFile
|
|
322
293
|
])
|
|
323
294
|
|
|
324
|
-
if (this.
|
|
295
|
+
if (this.autoStatus)
|
|
325
296
|
opts.push([
|
|
326
|
-
'
|
|
327
|
-
t`Auto500`,
|
|
297
|
+
'__AUTO_STATUS__',
|
|
298
|
+
this.autoStatus === 404 ? t`Auto404` : t`Auto500`,
|
|
328
299
|
true
|
|
329
300
|
])
|
|
330
301
|
else if (this.proxied)
|
|
@@ -346,27 +317,3 @@ export class BrokerRowModel {
|
|
|
346
317
|
].filter(Boolean).join(' ')
|
|
347
318
|
}
|
|
348
319
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
class StaticBrokerRowModel {
|
|
352
|
-
isNew = false
|
|
353
|
-
key = ''
|
|
354
|
-
method = 'GET'
|
|
355
|
-
urlMaskDittoed = ['', '']
|
|
356
|
-
#broker = /** @type ClientStaticBroker */ {}
|
|
357
|
-
|
|
358
|
-
/** @param {ClientStaticBroker} broker */
|
|
359
|
-
constructor(broker) {
|
|
360
|
-
this.#broker = broker
|
|
361
|
-
this.key = 'sbrm' + '::' + this.method + '::' + broker.route
|
|
362
|
-
}
|
|
363
|
-
setUrlMaskDittoed(urlMaskDittoed) {
|
|
364
|
-
this.urlMaskDittoed = urlMaskDittoed
|
|
365
|
-
}
|
|
366
|
-
setIsNew(isNew) {
|
|
367
|
-
this.isNew = isNew
|
|
368
|
-
}
|
|
369
|
-
get urlMask() { return this.#broker.route }
|
|
370
|
-
get delayed() { return this.#broker.delayed }
|
|
371
|
-
get status() { return this.#broker.status }
|
|
372
|
-
}
|
|
@@ -31,7 +31,7 @@ test('dittoSplitPaths', () => {
|
|
|
31
31
|
test('BrokerRowModel', () => {
|
|
32
32
|
test('has Auto500 when is autogenerated 500', () => {
|
|
33
33
|
const broker = {
|
|
34
|
-
|
|
34
|
+
autoStatus: 500,
|
|
35
35
|
file: 'api/user.GET.200.json',
|
|
36
36
|
mocks: ['api/user.GET.200.json']
|
|
37
37
|
}
|
|
@@ -43,6 +43,20 @@ test('BrokerRowModel', () => {
|
|
|
43
43
|
])
|
|
44
44
|
})
|
|
45
45
|
|
|
46
|
+
test('has Auto 404 when is autogenerated 404', () => {
|
|
47
|
+
const broker = {
|
|
48
|
+
autoStatus: 404,
|
|
49
|
+
file: 'index.html',
|
|
50
|
+
mocks: ['index.html']
|
|
51
|
+
}
|
|
52
|
+
const row = new BrokerRowModel(broker, false)
|
|
53
|
+
const opts = row.opts.map(([, n, selected]) => [n, selected])
|
|
54
|
+
deepEqual(opts, [
|
|
55
|
+
['200 html', false],
|
|
56
|
+
[t`Auto404`, true],
|
|
57
|
+
])
|
|
58
|
+
})
|
|
59
|
+
|
|
46
60
|
test('filename has extension except when empty or unknown', () => {
|
|
47
61
|
const broker = {
|
|
48
62
|
file: `api/user0.GET.200.empty`,
|