mockaton 8.16.4 → 8.16.6
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 +31 -12
- package/dev-config.js +18 -0
- package/index.js +1 -1
- package/package.json +1 -1
- package/src/Api.js +13 -13
- package/src/ApiConstants.js +1 -1
- package/src/Dashboard.css +38 -4
- package/src/Dashboard.js +16 -1
- package/src/MockDispatcher.js +27 -2
- package/src/Mockaton.js +6 -5
- package/src/StaticDispatcher.js +5 -57
- package/src/Watcher.js +2 -2
- package/src/config.js +1 -1
- package/src/mockBrokersCollection.js +6 -6
- package/src/staticCollection.js +56 -0
- package/src/MockDispatcherPlugins.js +0 -25
package/README.md
CHANGED
|
@@ -62,14 +62,20 @@ api/videos.GET.<b>500</b>.txt # Internal Server Error
|
|
|
62
62
|
|
|
63
63
|
<br/>
|
|
64
64
|
|
|
65
|
-
##
|
|
66
|
-
No need to mock everything. You can forward requests to your backend for routes
|
|
67
|
-
you don’t have mocks for, or routes that have the ☁️ **Cloud Checkbox** checked.
|
|
65
|
+
## Scraping mocks from your Backend
|
|
68
66
|
|
|
67
|
+
### Option 1: Browser Extension
|
|
68
|
+
This [browser extension](https://github.com/ericfortis/download-http-requests-browser-ext)
|
|
69
|
+
lets you download all the HTTP responses following the filename convention.
|
|
69
70
|
|
|
70
|
-
###
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
### Option 2: Fallback to Your Backend
|
|
72
|
+
This option could be a bit elaborate if your backend uses third-party auth,
|
|
73
|
+
because in that case you’d have to inject the cookies manually.
|
|
74
|
+
|
|
75
|
+
At any rate, you can forward requests to your backend for routes you don’t have
|
|
76
|
+
mocks for, or routes that have the ☁️ **Cloud Checkbox** checked. By checking
|
|
77
|
+
✅ **Save Mocks**, you can collect the responses that hit your backend. They
|
|
78
|
+
will be saved in your `config.mocksDir` following the filename convention.
|
|
73
79
|
|
|
74
80
|
|
|
75
81
|
<br/>
|
|
@@ -226,8 +232,17 @@ export default function listColors() {
|
|
|
226
232
|
```
|
|
227
233
|
</details>
|
|
228
234
|
|
|
229
|
-
**What if I need to serve a static .js?**
|
|
230
|
-
|
|
235
|
+
**What if I need to serve a static .js or .ts?**
|
|
236
|
+
|
|
237
|
+
**Option A:** Put it in your `config.staticDir` without the filename extension convention (i.e., no `.GET.200.js`)
|
|
238
|
+
|
|
239
|
+
**Option B:** Read it and return it. For example:
|
|
240
|
+
```js
|
|
241
|
+
export default function (_, response) {
|
|
242
|
+
response.setHeader('Content-Type', 'application/javascript')
|
|
243
|
+
return readFileSync('./some-dir/foo.js', 'utf8')
|
|
244
|
+
}
|
|
245
|
+
```
|
|
231
246
|
|
|
232
247
|
<br/>
|
|
233
248
|
|
|
@@ -331,10 +346,6 @@ api/foo/bar.GET.200.json
|
|
|
331
346
|
This is the only required field. The directory must exist.
|
|
332
347
|
|
|
333
348
|
### `staticDir?: string`
|
|
334
|
-
- Use Case 1: If you have a bunch of static assets you don’t want to add `.GET.200.ext`
|
|
335
|
-
- Use Case 2: For a standalone demo server. For example,
|
|
336
|
-
build your frontend bundle, and serve it from Mockaton.
|
|
337
|
-
|
|
338
349
|
Files under `config.staticDir` don’t use the filename convention, and
|
|
339
350
|
they take precedence over corresponding `GET` mocks in `config.mocksDir`.
|
|
340
351
|
For example, if you have two files for `GET /foo/bar.jpg`
|
|
@@ -344,6 +355,14 @@ my-static-dir<b>/foo/bar.jpg</b>
|
|
|
344
355
|
my-mocks-dir<b>/foo/bar.jpg</b>.GET.200.jpg // Unreachable
|
|
345
356
|
</pre>
|
|
346
357
|
|
|
358
|
+
This `config.staticDir` is not actually needed besides serving partial content
|
|
359
|
+
(e.g., videos). At any rate, it’s convenient for serving 200 GET requests without
|
|
360
|
+
having to add the filename extension convention (i.e., no `.GET.200.ext`).
|
|
361
|
+
For example, for using Mockaton as a standalone demo server. For that, you
|
|
362
|
+
can build your frontend bundle and put its built assets in this folder.
|
|
363
|
+
|
|
364
|
+
<br/>
|
|
365
|
+
|
|
347
366
|
### `ignore?: RegExp`
|
|
348
367
|
Defaults to `/(\.DS_Store|~)$/`
|
|
349
368
|
|
package/dev-config.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { join } from 'node:path'
|
|
2
|
+
import { jwtCookie } from './index.js'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export const devConfig = {
|
|
6
|
+
port: 2345,
|
|
7
|
+
mocksDir: join(import.meta.dirname, 'fixtures-mocks'),
|
|
8
|
+
staticDir: join(import.meta.dirname, 'fixtures-static-mocks'),
|
|
9
|
+
cookies: {
|
|
10
|
+
'My Admin User': 'my-cookie=1;Path=/;SameSite=strict',
|
|
11
|
+
'My Normal User': 'my-cookie=0;Path=/;SameSite=strict',
|
|
12
|
+
'My JWT': jwtCookie('my-cookie', {
|
|
13
|
+
email: 'john.doe@example.com',
|
|
14
|
+
picture: 'https://cdn.auth0.com/avatars/jd.png'
|
|
15
|
+
}),
|
|
16
|
+
'None': ''
|
|
17
|
+
}
|
|
18
|
+
}
|
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { Mockaton } from './src/Mockaton.js'
|
|
2
2
|
export { Commander } from './src/Commander.js'
|
|
3
|
-
export { jsToJsonPlugin } from './src/MockDispatcherPlugins.js'
|
|
4
3
|
|
|
5
4
|
export { jwtCookie } from './src/utils/jwt.js'
|
|
6
5
|
export { parseJSON } from './src/utils/http-request.js'
|
|
6
|
+
export { jsToJsonPlugin } from './src/MockDispatcher.js'
|
package/package.json
CHANGED
package/src/Api.js
CHANGED
|
@@ -7,11 +7,11 @@ import { join } from 'node:path'
|
|
|
7
7
|
import { cookie } from './cookie.js'
|
|
8
8
|
import { parseJSON } from './utils/http-request.js'
|
|
9
9
|
import { uiSyncVersion } from './Watcher.js'
|
|
10
|
+
import * as staticCollection from './staticCollection.js'
|
|
10
11
|
import * as mockBrokersCollection from './mockBrokersCollection.js'
|
|
11
12
|
import { config, ConfigValidator } from './config.js'
|
|
12
13
|
import { DF, API, LONG_POLL_SERVER_TIMEOUT } from './ApiConstants.js'
|
|
13
14
|
import { sendOK, sendJSON, sendUnprocessableContent, sendFile } from './utils/http-response.js'
|
|
14
|
-
import { getStaticFilesCollection, findStaticBrokerByRoute, initStaticCollection } from './StaticDispatcher.js'
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
const dashboardAssets = [
|
|
@@ -25,9 +25,7 @@ const dashboardAssets = [
|
|
|
25
25
|
|
|
26
26
|
export const apiGetRequests = new Map([
|
|
27
27
|
[API.dashboard, serveDashboard],
|
|
28
|
-
...dashboardAssets.map(f => [
|
|
29
|
-
API.dashboard + f, serveDashboardAsset(f)
|
|
30
|
-
]),
|
|
28
|
+
...dashboardAssets.map(f => [API.dashboard + f, serveDashboardAsset(f)]),
|
|
31
29
|
[API.cors, getIsCorsAllowed],
|
|
32
30
|
[API.static, listStaticFiles],
|
|
33
31
|
[API.mocks, listMockBrokers],
|
|
@@ -68,12 +66,14 @@ function serveDashboardAsset(f) {
|
|
|
68
66
|
|
|
69
67
|
function listCookies(_, response) { sendJSON(response, cookie.list()) }
|
|
70
68
|
function listComments(_, response) { sendJSON(response, mockBrokersCollection.extractAllComments()) }
|
|
71
|
-
|
|
69
|
+
|
|
70
|
+
function listStaticFiles(_, response) { sendJSON(response, staticCollection.all()) }
|
|
71
|
+
function listMockBrokers(_, response) { sendJSON(response, mockBrokersCollection.all()) }
|
|
72
|
+
|
|
72
73
|
function getGlobalDelay(_, response) { sendJSON(response, config.delay) }
|
|
73
|
-
function listMockBrokers(_, response) { sendJSON(response, mockBrokersCollection.getAll()) }
|
|
74
74
|
function getProxyFallback(_, response) { sendJSON(response, config.proxyFallback) }
|
|
75
|
-
function getIsCorsAllowed(_, response) { sendJSON(response, config.corsAllowed) }
|
|
76
75
|
function getCollectProxied(_, response) { sendJSON(response, config.collectProxied) }
|
|
76
|
+
function getIsCorsAllowed(_, response) { sendJSON(response, config.corsAllowed) }
|
|
77
77
|
|
|
78
78
|
function longPollClientSyncVersion(req, response) {
|
|
79
79
|
if (uiSyncVersion.version !== Number(req.headers[DF.syncVersion])) {
|
|
@@ -101,7 +101,7 @@ function longPollClientSyncVersion(req, response) {
|
|
|
101
101
|
|
|
102
102
|
function reinitialize(_, response) {
|
|
103
103
|
mockBrokersCollection.init()
|
|
104
|
-
|
|
104
|
+
staticCollection.init() // TESTME
|
|
105
105
|
sendOK(response)
|
|
106
106
|
}
|
|
107
107
|
|
|
@@ -115,7 +115,7 @@ async function selectCookie(req, response) {
|
|
|
115
115
|
|
|
116
116
|
async function selectMock(req, response) {
|
|
117
117
|
const file = await parseJSON(req)
|
|
118
|
-
const broker = mockBrokersCollection.
|
|
118
|
+
const broker = mockBrokersCollection.brokerByFilename(file)
|
|
119
119
|
if (!broker || !broker.hasMock(file))
|
|
120
120
|
sendUnprocessableContent(response, `Missing Mock: ${file}`)
|
|
121
121
|
else {
|
|
@@ -127,7 +127,7 @@ async function selectMock(req, response) {
|
|
|
127
127
|
async function setRouteIsDelayed(req, response) {
|
|
128
128
|
const body = await parseJSON(req)
|
|
129
129
|
const delayed = body[DF.delayed]
|
|
130
|
-
const broker = mockBrokersCollection.
|
|
130
|
+
const broker = mockBrokersCollection.brokerByRoute(
|
|
131
131
|
body[DF.routeMethod],
|
|
132
132
|
body[DF.routeUrlMask])
|
|
133
133
|
|
|
@@ -144,7 +144,7 @@ async function setRouteIsDelayed(req, response) {
|
|
|
144
144
|
async function setRouteIsProxied(req, response) { // TESTME
|
|
145
145
|
const body = await parseJSON(req)
|
|
146
146
|
const proxied = body[DF.proxied]
|
|
147
|
-
const broker = mockBrokersCollection.
|
|
147
|
+
const broker = mockBrokersCollection.brokerByRoute(
|
|
148
148
|
body[DF.routeMethod],
|
|
149
149
|
body[DF.routeUrlMask])
|
|
150
150
|
|
|
@@ -201,7 +201,7 @@ async function setGlobalDelay(req, response) { // TESTME
|
|
|
201
201
|
async function setStaticRouteStatusCode(req, response) {
|
|
202
202
|
const body = await parseJSON(req)
|
|
203
203
|
const status = Number(body[DF.statusCode])
|
|
204
|
-
const broker =
|
|
204
|
+
const broker = staticCollection.brokerByRoute(body[DF.routeUrlMask])
|
|
205
205
|
|
|
206
206
|
if (!broker) // TESTME
|
|
207
207
|
sendUnprocessableContent(response, `Route does not exist: ${body[DF.routeUrlMask]}`)
|
|
@@ -217,7 +217,7 @@ async function setStaticRouteStatusCode(req, response) {
|
|
|
217
217
|
async function setStaticRouteIsDelayed(req, response) {
|
|
218
218
|
const body = await parseJSON(req)
|
|
219
219
|
const delayed = body[DF.delayed]
|
|
220
|
-
const broker =
|
|
220
|
+
const broker = staticCollection.brokerByRoute(body[DF.routeUrlMask])
|
|
221
221
|
|
|
222
222
|
if (!broker) // TESTME
|
|
223
223
|
sendUnprocessableContent(response, `Route does not exist: ${body[DF.routeUrlMask]}`)
|
package/src/ApiConstants.js
CHANGED
|
@@ -31,7 +31,7 @@ export const DF = { // Dashboard Fields (XHR)
|
|
|
31
31
|
export const DEFAULT_500_COMMENT = '(Mockaton 500)'
|
|
32
32
|
export const DEFAULT_MOCK_COMMENT = '(default)'
|
|
33
33
|
export const EXT_FOR_UNKNOWN_MIME = 'unknown'
|
|
34
|
-
|
|
35
34
|
export const LONG_POLL_SERVER_TIMEOUT = 8_000
|
|
36
35
|
|
|
36
|
+
|
|
37
37
|
export const HEADER_FOR_502 = 'mockaton502'
|
package/src/Dashboard.css
CHANGED
|
@@ -506,10 +506,8 @@ table {
|
|
|
506
506
|
|
|
507
507
|
.SpinnerClock {
|
|
508
508
|
display: flex;
|
|
509
|
-
width:
|
|
510
|
-
|
|
511
|
-
margin-top: 92px;
|
|
512
|
-
justify-self: center;
|
|
509
|
+
width: 48px;
|
|
510
|
+
margin-top: 6px;
|
|
513
511
|
fill: none;
|
|
514
512
|
stroke: var(--colorSecondaryAction);
|
|
515
513
|
stroke-width: 2px;
|
|
@@ -539,6 +537,42 @@ table {
|
|
|
539
537
|
filter: saturate(0);
|
|
540
538
|
}
|
|
541
539
|
|
|
540
|
+
.ErrorToast {
|
|
541
|
+
position: fixed;
|
|
542
|
+
bottom: 12px;
|
|
543
|
+
left: 12px;
|
|
544
|
+
z-index: 9999;
|
|
545
|
+
cursor: pointer;
|
|
546
|
+
background: var(--colorRed);
|
|
547
|
+
color: white;
|
|
548
|
+
padding: 12px 16px;
|
|
549
|
+
border-radius: var(--radius);
|
|
550
|
+
box-shadow: var(--boxShadow1);
|
|
551
|
+
opacity: 0;
|
|
552
|
+
transform: translateY(20px);
|
|
553
|
+
animation: _kfToastIn 240ms forwards;
|
|
554
|
+
|
|
555
|
+
&:hover {
|
|
556
|
+
&::after {
|
|
557
|
+
top: 0;
|
|
558
|
+
left: calc(100% - 6px);
|
|
559
|
+
position: absolute;
|
|
560
|
+
content: '×';
|
|
561
|
+
vertical-align: middle;
|
|
562
|
+
padding: 12px;
|
|
563
|
+
border-radius: var(--radius);
|
|
564
|
+
background: black;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
@keyframes _kfToastIn {
|
|
570
|
+
to {
|
|
571
|
+
opacity: 1;
|
|
572
|
+
transform: translateY(0);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
542
576
|
|
|
543
577
|
/*
|
|
544
578
|
* Prism
|
package/src/Dashboard.js
CHANGED
|
@@ -39,6 +39,7 @@ const Strings = {
|
|
|
39
39
|
const CSS = {
|
|
40
40
|
BulkSelector: 'BulkSelector',
|
|
41
41
|
DelayToggler: 'DelayToggler',
|
|
42
|
+
ErrorToast: 'ErrorToast',
|
|
42
43
|
FallbackBackend: 'FallbackBackend',
|
|
43
44
|
Field: 'Field',
|
|
44
45
|
GlobalDelayField: 'GlobalDelayField',
|
|
@@ -592,10 +593,24 @@ function mockSelectorFor(method, urlMask) {
|
|
|
592
593
|
|
|
593
594
|
function onError(error) {
|
|
594
595
|
if (error?.message === 'Failed to fetch')
|
|
595
|
-
|
|
596
|
+
showErrorToast('Looks like the Mockaton server is not running')
|
|
596
597
|
console.error(error)
|
|
597
598
|
}
|
|
598
599
|
|
|
600
|
+
function showErrorToast(msg) {
|
|
601
|
+
document.getElementsByClassName(CSS.ErrorToast)[0]?.remove()
|
|
602
|
+
document.body.appendChild(
|
|
603
|
+
r('div', {
|
|
604
|
+
className: CSS.ErrorToast,
|
|
605
|
+
onClick() {
|
|
606
|
+
const toast = this
|
|
607
|
+
document.startViewTransition(() => {
|
|
608
|
+
toast.remove()
|
|
609
|
+
})
|
|
610
|
+
}
|
|
611
|
+
}, msg))
|
|
612
|
+
}
|
|
613
|
+
|
|
599
614
|
function TimerIcon() {
|
|
600
615
|
return (
|
|
601
616
|
s('svg', { viewBox: '0 0 24 24' },
|
package/src/MockDispatcher.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { join } from 'node:path'
|
|
2
|
+
import { readFileSync } from 'node:fs'
|
|
3
|
+
import { pathToFileURL } from 'node:url'
|
|
2
4
|
|
|
3
5
|
import { proxy } from './ProxyRelay.js'
|
|
4
6
|
import { cookie } from './cookie.js'
|
|
5
|
-
import {
|
|
7
|
+
import { mimeFor } from './utils/mime.js'
|
|
6
8
|
import { config, calcDelay } from './config.js'
|
|
7
9
|
import { BodyReaderError } from './utils/http-request.js'
|
|
8
10
|
import * as mockBrokerCollection from './mockBrokersCollection.js'
|
|
@@ -11,7 +13,7 @@ import { sendInternalServerError, sendNotFound, sendUnprocessableContent } from
|
|
|
11
13
|
|
|
12
14
|
export async function dispatchMock(req, response) {
|
|
13
15
|
try {
|
|
14
|
-
const broker = mockBrokerCollection.
|
|
16
|
+
const broker = mockBrokerCollection.brokerByRoute(req.method, req.url)
|
|
15
17
|
if (!broker || broker.proxied) {
|
|
16
18
|
if (config.proxyFallback)
|
|
17
19
|
await proxy(req, response, Number(broker?.delayed && calcDelay()))
|
|
@@ -50,3 +52,26 @@ export async function dispatchMock(req, response) {
|
|
|
50
52
|
sendInternalServerError(response, error)
|
|
51
53
|
}
|
|
52
54
|
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
async function applyPlugins(filePath, req, response) {
|
|
58
|
+
for (const [regex, plugin] of config.plugins)
|
|
59
|
+
if (regex.test(filePath))
|
|
60
|
+
return await plugin(filePath, req, response)
|
|
61
|
+
return {
|
|
62
|
+
mime: mimeFor(filePath),
|
|
63
|
+
body: readFileSync(filePath)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
export async function jsToJsonPlugin(filePath, req, response) {
|
|
69
|
+
const jsExport = (await import(pathToFileURL(filePath) + '?' + Date.now())).default // date for cache busting
|
|
70
|
+
const body = typeof jsExport === 'function'
|
|
71
|
+
? await jsExport(req, response)
|
|
72
|
+
: JSON.stringify(jsExport, null, 2)
|
|
73
|
+
return {
|
|
74
|
+
mime: response.getHeader('Content-Type') || mimeFor('.json'), // jsFunc are allowed to set it
|
|
75
|
+
body
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/Mockaton.js
CHANGED
|
@@ -3,12 +3,13 @@ import { createServer } from 'node:http'
|
|
|
3
3
|
import { API } from './ApiConstants.js'
|
|
4
4
|
import { config, setup } from './config.js'
|
|
5
5
|
import { dispatchMock } from './MockDispatcher.js'
|
|
6
|
+
import { dispatchStatic } from './StaticDispatcher.js'
|
|
7
|
+
import * as staticCollection from './staticCollection.js'
|
|
6
8
|
import { BodyReaderError } from './utils/http-request.js'
|
|
7
9
|
import * as mockBrokerCollection from './mockBrokersCollection.js'
|
|
8
10
|
import { setCorsHeaders, isPreflight } from './utils/http-cors.js'
|
|
11
|
+
import { watchMocksDir, watchStaticDir } from './Watcher.js'
|
|
9
12
|
import { apiPatchRequests, apiGetRequests } from './Api.js'
|
|
10
|
-
import { watchMocksDir, watchStaticMocksDir } from './Watcher.js'
|
|
11
|
-
import { dispatchStatic, initStaticCollection, findStaticBrokerByRoute } from './StaticDispatcher.js'
|
|
12
13
|
import { sendNoContent, sendInternalServerError, sendUnprocessableContent } from './utils/http-response.js'
|
|
13
14
|
|
|
14
15
|
|
|
@@ -17,9 +18,9 @@ process.on('unhandledRejection', error => { throw error })
|
|
|
17
18
|
export function Mockaton(options) {
|
|
18
19
|
setup(options)
|
|
19
20
|
mockBrokerCollection.init()
|
|
20
|
-
|
|
21
|
+
staticCollection.init()
|
|
21
22
|
watchMocksDir()
|
|
22
|
-
|
|
23
|
+
watchStaticDir()
|
|
23
24
|
|
|
24
25
|
return createServer(onRequest).listen(config.port, config.host, function (error) {
|
|
25
26
|
if (error) {
|
|
@@ -54,7 +55,7 @@ async function onRequest(req, response) {
|
|
|
54
55
|
else if (method === 'GET' && apiGetRequests.has(url))
|
|
55
56
|
apiGetRequests.get(url)(req, response)
|
|
56
57
|
|
|
57
|
-
else if (method === 'GET' &&
|
|
58
|
+
else if (method === 'GET' && staticCollection.brokerByRoute(url))
|
|
58
59
|
await dispatchStatic(req, response)
|
|
59
60
|
|
|
60
61
|
else
|
package/src/StaticDispatcher.js
CHANGED
|
@@ -1,65 +1,14 @@
|
|
|
1
|
+
import { join } from 'node:path'
|
|
1
2
|
import { readFileSync } from 'node:fs'
|
|
2
|
-
import { join, basename } from 'node:path'
|
|
3
3
|
|
|
4
4
|
import { mimeFor } from './utils/mime.js'
|
|
5
|
-
import {
|
|
6
|
-
import { config,
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class StaticBroker {
|
|
11
|
-
constructor(route) {
|
|
12
|
-
this.route = route
|
|
13
|
-
this.delayed = false
|
|
14
|
-
this.status = 200 // 200 or 404
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
setDelayed(value) { this.delayed = value }
|
|
18
|
-
setStatus(value) { this.status = value }
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** @type {{ [route: string]: StaticBroker }} */
|
|
22
|
-
let collection = {}
|
|
23
|
-
|
|
24
|
-
export function initStaticCollection() {
|
|
25
|
-
collection = {}
|
|
26
|
-
listFilesRecursively(config.staticDir)
|
|
27
|
-
.sort()
|
|
28
|
-
.forEach(registerStaticMock)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
/** @returns {boolean} registered */
|
|
33
|
-
export function registerStaticMock(relativeFile) {
|
|
34
|
-
if (!isFileAllowed(basename(relativeFile)))
|
|
35
|
-
return false
|
|
36
|
-
|
|
37
|
-
const route = '/' + relativeFile
|
|
38
|
-
if (findStaticBrokerByRoute(route))
|
|
39
|
-
return false
|
|
40
|
-
|
|
41
|
-
collection[route] = new StaticBroker(route)
|
|
42
|
-
return true
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
export function unregisterStaticMock(relativeFile) {
|
|
47
|
-
delete collection['/' + relativeFile]
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
/** @returns {StaticBroker | undefined} */
|
|
52
|
-
export function findStaticBrokerByRoute(route) {
|
|
53
|
-
return collection[route] || collection[join(route, 'index.html')]
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function getStaticFilesCollection() {
|
|
57
|
-
return collection
|
|
58
|
-
}
|
|
5
|
+
import { brokerByRoute } from './staticCollection.js'
|
|
6
|
+
import { config, calcDelay } from './config.js'
|
|
7
|
+
import { sendNotFound, sendPartialContent } from './utils/http-response.js'
|
|
59
8
|
|
|
60
9
|
|
|
61
10
|
export async function dispatchStatic(req, response) {
|
|
62
|
-
const broker =
|
|
11
|
+
const broker = brokerByRoute(req.url)
|
|
63
12
|
|
|
64
13
|
setTimeout(async () => {
|
|
65
14
|
if (!broker || broker.status === 404) { // TESTME
|
|
@@ -75,4 +24,3 @@ export async function dispatchStatic(req, response) {
|
|
|
75
24
|
}
|
|
76
25
|
}, Number(broker.delayed && calcDelay()))
|
|
77
26
|
}
|
|
78
|
-
|
package/src/Watcher.js
CHANGED
|
@@ -5,7 +5,7 @@ import { EventEmitter } from 'node:events'
|
|
|
5
5
|
import { config } from './config.js'
|
|
6
6
|
import { isFile } from './utils/fs.js'
|
|
7
7
|
import * as mockBrokerCollection from './mockBrokersCollection.js'
|
|
8
|
-
import { registerStaticMock, unregisterStaticMock } from './
|
|
8
|
+
import { registerStaticMock, unregisterStaticMock } from './staticCollection.js'
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
/** # AR = Add or Remove Mock Event */
|
|
@@ -40,7 +40,7 @@ export function watchMocksDir() {
|
|
|
40
40
|
})
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export function
|
|
43
|
+
export function watchStaticDir() {
|
|
44
44
|
const dir = config.staticDir
|
|
45
45
|
if (!dir)
|
|
46
46
|
return
|
package/src/config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { realpathSync } from 'node:fs'
|
|
2
2
|
import { isDirectory } from './utils/fs.js'
|
|
3
3
|
import { openInBrowser } from './utils/openInBrowser.js'
|
|
4
|
-
import { jsToJsonPlugin } from './
|
|
4
|
+
import { jsToJsonPlugin } from './MockDispatcher.js'
|
|
5
5
|
import { optional, is, validate } from './utils/validate.js'
|
|
6
6
|
import { SUPPORTED_METHODS } from './utils/http-request.js'
|
|
7
7
|
import { validateCorsAllowedMethods, validateCorsAllowedOrigins } from './utils/http-cors.js'
|
|
@@ -23,6 +23,8 @@ import { parseFilename, filenameIsValid } from './Filename.js'
|
|
|
23
23
|
*/
|
|
24
24
|
let collection = {}
|
|
25
25
|
|
|
26
|
+
export const all = () => collection
|
|
27
|
+
|
|
26
28
|
export function init() {
|
|
27
29
|
collection = {}
|
|
28
30
|
cookie.init(config.cookies)
|
|
@@ -39,7 +41,7 @@ export function init() {
|
|
|
39
41
|
|
|
40
42
|
/** @returns {boolean} registered */
|
|
41
43
|
export function registerMock(file, isFromWatcher = false) {
|
|
42
|
-
if (
|
|
44
|
+
if (brokerByFilename(file)?.hasMock(file)
|
|
43
45
|
|| !isFileAllowed(basename(file)) // TESTME
|
|
44
46
|
|| !filenameIsValid(file))
|
|
45
47
|
return false
|
|
@@ -62,7 +64,7 @@ export function registerMock(file, isFromWatcher = false) {
|
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
export function unregisterMock(file) {
|
|
65
|
-
const broker =
|
|
67
|
+
const broker = brokerByFilename(file)
|
|
66
68
|
if (!broker)
|
|
67
69
|
return
|
|
68
70
|
const isEmpty = broker.unregister(file)
|
|
@@ -74,11 +76,9 @@ export function unregisterMock(file) {
|
|
|
74
76
|
}
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
export const getAll = () => collection
|
|
78
|
-
|
|
79
79
|
|
|
80
80
|
/** @returns {MockBroker | undefined} */
|
|
81
|
-
export function
|
|
81
|
+
export function brokerByFilename(file) {
|
|
82
82
|
const { method, urlMask } = parseFilename(file)
|
|
83
83
|
if (collection[method])
|
|
84
84
|
return collection[method][urlMask]
|
|
@@ -91,7 +91,7 @@ export function findBrokerByFilename(file) {
|
|
|
91
91
|
* BTW, `urlMasks` always start with "/", so there’s no need to
|
|
92
92
|
* worry about the primacy of array-like keys when iterating.
|
|
93
93
|
@returns {MockBroker | undefined} */
|
|
94
|
-
export function
|
|
94
|
+
export function brokerByRoute(method, url) {
|
|
95
95
|
if (!collection[method])
|
|
96
96
|
return
|
|
97
97
|
const brokers = Object.values(collection[method])
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { join, basename } from 'node:path'
|
|
2
|
+
import { listFilesRecursively } from './utils/fs.js'
|
|
3
|
+
import { config, isFileAllowed } from './config.js'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class StaticBroker {
|
|
7
|
+
constructor(route) {
|
|
8
|
+
this.route = route
|
|
9
|
+
this.delayed = false
|
|
10
|
+
this.status = 200 // 200 or 404
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
setDelayed(value) { this.delayed = value }
|
|
14
|
+
setStatus(value) { this.status = value }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** @type {{ [route: string]: StaticBroker }} */
|
|
18
|
+
let collection = {}
|
|
19
|
+
|
|
20
|
+
export const all = () => collection
|
|
21
|
+
|
|
22
|
+
export function init() {
|
|
23
|
+
collection = {}
|
|
24
|
+
listFilesRecursively(config.staticDir)
|
|
25
|
+
.sort()
|
|
26
|
+
.forEach(registerStaticMock)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
/** @returns {boolean} registered */
|
|
31
|
+
export function registerStaticMock(relativeFile) {
|
|
32
|
+
if (!isFileAllowed(basename(relativeFile)))
|
|
33
|
+
return false
|
|
34
|
+
|
|
35
|
+
const route = '/' + relativeFile
|
|
36
|
+
if (brokerByRoute(route))
|
|
37
|
+
return false
|
|
38
|
+
|
|
39
|
+
collection[route] = new StaticBroker(route)
|
|
40
|
+
return true
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
export function unregisterStaticMock(relativeFile) {
|
|
45
|
+
delete collection['/' + relativeFile]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
/** @returns {StaticBroker | undefined} */
|
|
50
|
+
export function brokerByRoute(route) {
|
|
51
|
+
return collection[route] || collection[join(route, 'index.html')]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { readFileSync as read } from 'node:fs'
|
|
2
|
-
import { mimeFor } from './utils/mime.js'
|
|
3
|
-
import { config } from './config.js'
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export async function applyPlugins(filePath, req, response) {
|
|
7
|
-
for (const [regex, plugin] of config.plugins)
|
|
8
|
-
if (regex.test(filePath))
|
|
9
|
-
return await plugin(filePath, req, response)
|
|
10
|
-
return {
|
|
11
|
-
mime: mimeFor(filePath),
|
|
12
|
-
body: read(filePath)
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function jsToJsonPlugin(filePath, req, response) {
|
|
17
|
-
const jsExport = (await import(filePath + '?' + Date.now())).default // date for cache busting
|
|
18
|
-
const body = typeof jsExport === 'function'
|
|
19
|
-
? await jsExport(req, response)
|
|
20
|
-
: JSON.stringify(jsExport, null, 2)
|
|
21
|
-
return {
|
|
22
|
-
mime: response.getHeader('Content-Type') || mimeFor('.json'), // jsFunc are allowed to set it
|
|
23
|
-
body
|
|
24
|
-
}
|
|
25
|
-
}
|