mockaton 5.0.3 → 6.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 +10 -17
- package/package.json +1 -1
- package/sample-mocks/api/user.GET.200.json +1 -0
- package/src/Config.js +1 -1
- package/src/Dashboard.css +4 -4
- package/src/Dashboard.js +2 -12
- package/src/MockBroker.js +7 -15
- package/src/MockDispatcher.js +1 -7
- package/src/Route.js +16 -37
- package/sample-mocks/api/user/.GET.200.json +0 -1
- package/sample-mocks/api/user/edit-name.PATCH.md +0 -9
package/README.md
CHANGED
|
@@ -48,10 +48,10 @@ interface Config {
|
|
|
48
48
|
host?: string, // defaults to 'localhost'
|
|
49
49
|
port?: number // defaults to 0, which means auto-assigned
|
|
50
50
|
delay?: number // defaults to 1200 (ms)
|
|
51
|
-
onReady?: (dashboardUrl: string) => void // defaults to
|
|
51
|
+
onReady?: (dashboardUrl: string) => void // defaults to trying to open macOS default browser. pass a noop to prevent opening the dashboard
|
|
52
52
|
cookies?: object
|
|
53
53
|
proxyFallback?: string // e.g. http://localhost:9999 Target for relaying routes without mocks
|
|
54
|
-
allowedExt?: RegExp // /\.(
|
|
54
|
+
allowedExt?: RegExp // /\.(json|js|txt)$/ Just for excluding temporary editor files (e.g. JetBrains appends a ~)
|
|
55
55
|
extraHeaders?: []
|
|
56
56
|
}
|
|
57
57
|
```
|
|
@@ -149,18 +149,20 @@ permitted](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file)
|
|
|
149
149
|
but since that’s part of the query string, it’s ignored anyway.
|
|
150
150
|
|
|
151
151
|
|
|
152
|
-
|
|
153
152
|
### Default (index-like) route
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
153
|
+
**Option A.** Place it outside the directory:
|
|
154
|
+
```
|
|
155
|
+
api/foo/
|
|
156
|
+
api/foo.GET.200.json
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Option B.** Omit the filename:
|
|
157
160
|
```text
|
|
158
161
|
api/foo/.GET.200.json
|
|
159
|
-
api/foo/?bar=[bar].GET.200.json
|
|
160
|
-
api/foo/(my comment).GET.200.json
|
|
161
162
|
```
|
|
162
163
|
|
|
163
164
|
|
|
165
|
+
|
|
164
166
|
## `Config.cookies`
|
|
165
167
|
The selected cookie is sent in every response in the `Set-Cookie` header.
|
|
166
168
|
|
|
@@ -197,15 +199,6 @@ Config.extraHeaders = [
|
|
|
197
199
|
]
|
|
198
200
|
```
|
|
199
201
|
|
|
200
|
-
## Documenting Contracts (.md)
|
|
201
|
-
This is handy for documenting request payload parameters. The
|
|
202
|
-
dashboard prints the Markdown document as plain text (I know, I know).
|
|
203
|
-
|
|
204
|
-
```text
|
|
205
|
-
api/foo/[user-id].POST.md
|
|
206
|
-
api/foo/[user-id].POST.201.json
|
|
207
|
-
```
|
|
208
|
-
|
|
209
202
|
---
|
|
210
203
|
|
|
211
204
|
## API
|
package/package.json
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"This mock is for /api/user"
|
package/src/Config.js
CHANGED
|
@@ -12,7 +12,7 @@ export const Config = {
|
|
|
12
12
|
cookies: {}, // defaults to the first kv
|
|
13
13
|
onReady: openInBrowser,
|
|
14
14
|
proxyFallback: '', // e.g. http://localhost:9999
|
|
15
|
-
allowedExt: /\.(
|
|
15
|
+
allowedExt: /\.(json|js|txt)$/, // Just for excluding temporary editor files (e.g. JetBrains appends a ~)
|
|
16
16
|
extraHeaders: []
|
|
17
17
|
}
|
|
18
18
|
|
package/src/Dashboard.css
CHANGED
|
@@ -107,6 +107,10 @@ menu {
|
|
|
107
107
|
top: 0;
|
|
108
108
|
width: 50%;
|
|
109
109
|
margin-left: 16px;
|
|
110
|
+
|
|
111
|
+
h2 {
|
|
112
|
+
padding-top: 20px;
|
|
113
|
+
}
|
|
110
114
|
|
|
111
115
|
pre {
|
|
112
116
|
tab-size: 2;
|
|
@@ -119,10 +123,6 @@ menu {
|
|
|
119
123
|
background: var(--colorLightGrey);
|
|
120
124
|
font-family: monospace;
|
|
121
125
|
}
|
|
122
|
-
|
|
123
|
-
&.Documentation {
|
|
124
|
-
margin-bottom: 12px;
|
|
125
|
-
}
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
|
package/src/Dashboard.js
CHANGED
|
@@ -19,7 +19,6 @@ const Strings = {
|
|
|
19
19
|
const CSS = {
|
|
20
20
|
DelayToggler: 'DelayToggler',
|
|
21
21
|
InternalServerErrorToggler: 'InternalServerErrorToggler',
|
|
22
|
-
Documentation: 'Documentation',
|
|
23
22
|
MockSelector: 'MockSelector',
|
|
24
23
|
PayloadViewer: 'PayloadViewer',
|
|
25
24
|
PreviewLink: 'PreviewLink',
|
|
@@ -30,7 +29,6 @@ const CSS = {
|
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
const r = createElement
|
|
33
|
-
const refDocumentation = useRef()
|
|
34
32
|
const refPayloadViewer = useRef()
|
|
35
33
|
const refPayloadFile = useRef()
|
|
36
34
|
|
|
@@ -64,7 +62,6 @@ function DevPanel(brokersByMethod, cookies, comments) {
|
|
|
64
62
|
r('table', null, Object.entries(brokersByMethod).map(([method, brokers]) =>
|
|
65
63
|
r(SectionByMethod, { method, brokers }))),
|
|
66
64
|
r('div', { className: CSS.PayloadViewer },
|
|
67
|
-
r('pre', { ref: refDocumentation, className: CSS.Documentation }),
|
|
68
65
|
r('h2', { ref: refPayloadFile }, Strings.mock),
|
|
69
66
|
r('pre', { ref: refPayloadViewer }, Strings.click_link_to_preview)))))
|
|
70
67
|
}
|
|
@@ -134,14 +131,14 @@ function SectionByMethod({ method, brokers }) {
|
|
|
134
131
|
.filter(([, broker]) => broker.mocks.length > 1) // Excludes Markdown only routes (>1 because of the autogen500)
|
|
135
132
|
.map(([urlMask, broker]) =>
|
|
136
133
|
r('tr', null,
|
|
137
|
-
r('td', null, r(PreviewLink, { method, urlMask
|
|
134
|
+
r('td', null, r(PreviewLink, { method, urlMask })),
|
|
138
135
|
r('td', null, r(MockSelector, { broker })),
|
|
139
136
|
r('td', null, r(DelayToggler, { broker })),
|
|
140
137
|
r('td', null, r(InternalServerErrorToggler, { broker }))
|
|
141
138
|
))))
|
|
142
139
|
}
|
|
143
140
|
|
|
144
|
-
function PreviewLink({ method, urlMask
|
|
141
|
+
function PreviewLink({ method, urlMask }) {
|
|
145
142
|
return (
|
|
146
143
|
r('a', {
|
|
147
144
|
className: CSS.PreviewLink,
|
|
@@ -150,13 +147,6 @@ function PreviewLink({ method, urlMask, documentation }) {
|
|
|
150
147
|
async onClick(event) {
|
|
151
148
|
event.preventDefault()
|
|
152
149
|
try {
|
|
153
|
-
if (documentation) {
|
|
154
|
-
const r = await fetch(documentation)
|
|
155
|
-
refDocumentation.current.innerText = await r.text()
|
|
156
|
-
}
|
|
157
|
-
else
|
|
158
|
-
refDocumentation.current.innerText = ''
|
|
159
|
-
|
|
160
150
|
const spinner = setTimeout(() => refPayloadViewer.current.innerText = Strings.fetching, 180)
|
|
161
151
|
const res = await fetch(this.href, {
|
|
162
152
|
method: this.getAttribute('data-method')
|
package/src/MockBroker.js
CHANGED
|
@@ -5,19 +5,15 @@ import { isDirectory } from './utils/fs.js'
|
|
|
5
5
|
import { DEFAULT_500_COMMENT } from './ApiConstants.js'
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
// MockBroker is a state for a particular route. It knows the available
|
|
9
|
-
//
|
|
10
|
-
// file, and its delay. Also, knows if the route has documentation (md)
|
|
8
|
+
// MockBroker is a state for a particular route. It knows the available mock files
|
|
9
|
+
// that can be served for the route, the currently selected file, and its delay.
|
|
11
10
|
export class MockBroker {
|
|
12
11
|
#route
|
|
13
12
|
|
|
14
13
|
constructor(file) {
|
|
15
14
|
this.#route = new Route(file)
|
|
16
15
|
this.method = this.#route.method
|
|
17
|
-
|
|
18
|
-
this.documentation = '' // .md
|
|
19
|
-
|
|
20
|
-
this.mocks = [] // *.json,txt,js
|
|
16
|
+
this.mocks = []
|
|
21
17
|
this.currentMock = {
|
|
22
18
|
file: '',
|
|
23
19
|
delay: 0
|
|
@@ -27,13 +23,9 @@ export class MockBroker {
|
|
|
27
23
|
}
|
|
28
24
|
|
|
29
25
|
register(file) {
|
|
30
|
-
if (
|
|
31
|
-
this.
|
|
32
|
-
|
|
33
|
-
if (!this.mocks.length)
|
|
34
|
-
this.currentMock.file = file // The first mock file option for a particular route becomes the default
|
|
35
|
-
this.mocks.push(file)
|
|
36
|
-
}
|
|
26
|
+
if (!this.mocks.length)
|
|
27
|
+
this.currentMock.file = file // The first mock file option for a particular route becomes the default
|
|
28
|
+
this.mocks.push(file)
|
|
37
29
|
}
|
|
38
30
|
|
|
39
31
|
urlMaskMatches(url) { return this.#route.urlMaskMatches(url) }
|
|
@@ -77,7 +69,7 @@ export class MockBroker {
|
|
|
77
69
|
}
|
|
78
70
|
|
|
79
71
|
#registerTemp500() {
|
|
80
|
-
const { urlMask, method } = Route.parseFilename(this.mocks[0]
|
|
72
|
+
const { urlMask, method } = Route.parseFilename(this.mocks[0])
|
|
81
73
|
let mask = urlMask
|
|
82
74
|
if (isDirectory(join(Config.mocksDir, urlMask)))
|
|
83
75
|
mask = urlMask + '/'
|
package/src/MockDispatcher.js
CHANGED
|
@@ -7,16 +7,10 @@ import { Config } from './Config.js'
|
|
|
7
7
|
import { mimeFor } from './utils/mime.js'
|
|
8
8
|
import * as mockBrokerCollection from './mockBrokersCollection.js'
|
|
9
9
|
import { JsonBodyParserError } from './utils/http-request.js'
|
|
10
|
-
import { sendInternalServerError, sendNotFound,
|
|
10
|
+
import { sendInternalServerError, sendNotFound, sendBadRequest } from './utils/http-response.js'
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
export async function dispatchMock(req, response) {
|
|
14
|
-
/* Serve Documentation */
|
|
15
|
-
if (req.method === 'GET' && req.url.endsWith('.md')) {
|
|
16
|
-
sendFile(response, join(Config.mocksDir, decodeURIComponent(req.url)))
|
|
17
|
-
return
|
|
18
|
-
}
|
|
19
|
-
|
|
20
14
|
const broker = mockBrokerCollection.getBrokerForUrl(req.method, req.url)
|
|
21
15
|
if (!broker) {
|
|
22
16
|
if (Config.proxyFallback)
|
package/src/Route.js
CHANGED
|
@@ -44,43 +44,22 @@ export class Route {
|
|
|
44
44
|
|
|
45
45
|
static parseFilename(file) {
|
|
46
46
|
const tokens = file.replace(Route.reComments, '').split('.')
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
method,
|
|
64
|
-
status: 200
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function parseMock(tokens) {
|
|
69
|
-
if (tokens.length < 4)
|
|
70
|
-
return { error: 'Invalid Filename Convention' }
|
|
71
|
-
|
|
72
|
-
const status = Number(tokens.at(-2))
|
|
73
|
-
if (!responseStatusIsValid(status))
|
|
74
|
-
return { error: `Invalid HTTP Response Status: "${status}"` }
|
|
75
|
-
|
|
76
|
-
const method = tokens.at(-3)
|
|
77
|
-
if (!httpMethods.includes(method))
|
|
78
|
-
return { error: `Unrecognized HTTP Method: "${method}"` }
|
|
79
|
-
|
|
80
|
-
return {
|
|
81
|
-
urlMask: '/' + removeTrailingSlash(tokens.at(-4)),
|
|
82
|
-
method,
|
|
83
|
-
status
|
|
47
|
+
if (tokens.length < 4)
|
|
48
|
+
return { error: 'Invalid Filename Convention' }
|
|
49
|
+
|
|
50
|
+
const status = Number(tokens.at(-2))
|
|
51
|
+
if (!responseStatusIsValid(status))
|
|
52
|
+
return { error: `Invalid HTTP Response Status: "${status}"` }
|
|
53
|
+
|
|
54
|
+
const method = tokens.at(-3)
|
|
55
|
+
if (!httpMethods.includes(method))
|
|
56
|
+
return { error: `Unrecognized HTTP Method: "${method}"` }
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
urlMask: '/' + removeTrailingSlash(tokens.at(-4)),
|
|
60
|
+
method,
|
|
61
|
+
status
|
|
62
|
+
}
|
|
84
63
|
}
|
|
85
64
|
}
|
|
86
65
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"This is an index-like route (i.e. /api/user). This file has no name, only the extension convention is needed for the index path."
|