mockaton 7.1.0 → 7.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/README.md +3 -3
- package/package.json +2 -2
- package/src/Config.js +1 -1
- package/src/Mockaton.js +5 -5
- package/{Tests.js → src/Mockaton.test.js} +11 -13
- package/src/utils/http-cors.js +13 -13
- package/src/utils/http-cors.test.js +41 -6
- package/src/utils/http-response.js +5 -0
package/README.md
CHANGED
|
@@ -269,10 +269,10 @@ Config.corsAllowed = true
|
|
|
269
269
|
// Defaults when `corsAllowed === true`
|
|
270
270
|
Config.corsOrigins = ['*']
|
|
271
271
|
Config.corsMethods = ['GET', 'PUT', 'DELETE', 'POST', 'PATCH', 'HEAD', 'OPTIONS', 'TRACE', 'CONNECT']
|
|
272
|
-
Config.corsHeaders = []
|
|
272
|
+
Config.corsHeaders = ['content-type']
|
|
273
273
|
Config.corsCredentials = true
|
|
274
|
-
Config.corsMaxAge = 0
|
|
275
|
-
Config.corsExposedHeaders = []
|
|
274
|
+
Config.corsMaxAge = 0 // seconds to cache the preflight req
|
|
275
|
+
Config.corsExposedHeaders = [] // headers you need to access in client-side JS
|
|
276
276
|
```
|
|
277
277
|
|
|
278
278
|
## `Config.onReady`
|
package/package.json
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
"name": "mockaton",
|
|
3
3
|
"description": "A deterministic server-side for developing and testing frontend clients",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "7.1
|
|
5
|
+
"version": "7.2.1",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"types": "index.d.ts",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"repository": "https://github.com/ericfortis/mockaton",
|
|
10
10
|
"scripts": {
|
|
11
|
-
"test": "
|
|
11
|
+
"test": "node --test",
|
|
12
12
|
"demo": "./_usage_example.js"
|
|
13
13
|
}
|
|
14
14
|
}
|
package/src/Config.js
CHANGED
package/src/Mockaton.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createServer } from 'node:http'
|
|
|
3
3
|
import { API } from './ApiConstants.js'
|
|
4
4
|
import { dispatchMock } from './MockDispatcher.js'
|
|
5
5
|
import { Config, setup } from './Config.js'
|
|
6
|
+
import { sendNoContent } from './utils/http-response.js'
|
|
6
7
|
import * as mockBrokerCollection from './mockBrokersCollection.js'
|
|
7
8
|
import { dispatchStatic, isStatic } from './StaticDispatcher.js'
|
|
8
9
|
import { setCorsHeaders, isPreflight } from './utils/http-cors.js'
|
|
@@ -28,7 +29,6 @@ export function Mockaton(options) {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
async function onRequest(req, response) {
|
|
31
|
-
const { url, method } = req
|
|
32
32
|
response.setHeader('Server', 'Mockaton')
|
|
33
33
|
|
|
34
34
|
if (Config.corsAllowed)
|
|
@@ -41,11 +41,11 @@ async function onRequest(req, response) {
|
|
|
41
41
|
exposedHeaders: Config.extraHeaders
|
|
42
42
|
})
|
|
43
43
|
|
|
44
|
+
const { url, method } = req
|
|
45
|
+
|
|
46
|
+
if (isPreflight(req))
|
|
47
|
+
sendNoContent(response)
|
|
44
48
|
|
|
45
|
-
if (isPreflight(req)) {
|
|
46
|
-
response.statusCode = 204
|
|
47
|
-
response.end()
|
|
48
|
-
}
|
|
49
49
|
else if (method === 'GET' && apiGetRequests.has(url))
|
|
50
50
|
apiGetRequests.get(url)(req, response)
|
|
51
51
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env -S node --experimental-default-type=module
|
|
2
|
-
|
|
3
1
|
import { tmpdir } from 'node:os'
|
|
4
2
|
import { dirname } from 'node:path'
|
|
5
3
|
import { promisify } from 'node:util'
|
|
@@ -8,13 +6,13 @@ import { createServer } from 'node:http'
|
|
|
8
6
|
import { equal, deepEqual, match } from 'node:assert/strict'
|
|
9
7
|
import { writeFileSync, mkdtempSync, mkdirSync } from 'node:fs'
|
|
10
8
|
|
|
11
|
-
import { Config } from './
|
|
12
|
-
import { mimeFor } from './
|
|
13
|
-
import { Mockaton } from './
|
|
14
|
-
import { Commander } from './
|
|
15
|
-
import { parseFilename } from './
|
|
16
|
-
import {
|
|
17
|
-
import { API, DEFAULT_500_COMMENT, DEFAULT_MOCK_COMMENT } from './
|
|
9
|
+
import { Config } from './Config.js'
|
|
10
|
+
import { mimeFor } from './utils/mime.js'
|
|
11
|
+
import { Mockaton } from './Mockaton.js'
|
|
12
|
+
import { Commander } from './Commander.js'
|
|
13
|
+
import { parseFilename } from './Filename.js'
|
|
14
|
+
import { CorsHeader } from './utils/http-cors.js'
|
|
15
|
+
import { API, DEFAULT_500_COMMENT, DEFAULT_MOCK_COMMENT } from './ApiConstants.js'
|
|
18
16
|
|
|
19
17
|
|
|
20
18
|
const tmpDir = mkdtempSync(tmpdir()) + '/'
|
|
@@ -446,13 +444,13 @@ async function testCorsAllowed() {
|
|
|
446
444
|
const res = await request('/does-not-matter', {
|
|
447
445
|
method: 'OPTIONS',
|
|
448
446
|
headers: {
|
|
449
|
-
[
|
|
450
|
-
[
|
|
447
|
+
[CorsHeader.Origin]: 'http://example.com',
|
|
448
|
+
[CorsHeader.AccessControlRequestMethod]: 'GET'
|
|
451
449
|
}
|
|
452
450
|
})
|
|
453
451
|
equal(res.status, 204)
|
|
454
|
-
equal(res.headers.get(
|
|
455
|
-
equal(res.headers.get(
|
|
452
|
+
equal(res.headers.get(CorsHeader.AccessControlAllowOrigin), 'http://example.com')
|
|
453
|
+
equal(res.headers.get(CorsHeader.AccessControlAllowMethods), 'GET')
|
|
456
454
|
})
|
|
457
455
|
}
|
|
458
456
|
|
package/src/utils/http-cors.js
CHANGED
|
@@ -2,7 +2,7 @@ import { StandardMethods } from './http-request.js'
|
|
|
2
2
|
|
|
3
3
|
// https://www.w3.org/TR/2020/SPSD-cors-20200602/#resource-processing-model
|
|
4
4
|
|
|
5
|
-
export const
|
|
5
|
+
export const CorsHeader = {
|
|
6
6
|
// request
|
|
7
7
|
Origin: 'origin',
|
|
8
8
|
AccessControlRequestMethod: 'access-control-request-method',
|
|
@@ -16,13 +16,13 @@ export const PreflightHeader = {
|
|
|
16
16
|
AccessControlExposeHeaders: 'Access-Control-Expose-Headers', // '*' | Comma delimited
|
|
17
17
|
AccessControlAllowCredentials: 'Access-Control-Allow-Credentials' // 'true'
|
|
18
18
|
}
|
|
19
|
-
const
|
|
19
|
+
const CH = CorsHeader
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
export function isPreflight(req) {
|
|
23
23
|
return req.method === 'OPTIONS'
|
|
24
|
-
&& URL.canParse(req.headers[
|
|
25
|
-
&& StandardMethods.includes(req.headers[
|
|
24
|
+
&& URL.canParse(req.headers[CH.Origin])
|
|
25
|
+
&& StandardMethods.includes(req.headers[CH.AccessControlRequestMethod])
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
|
|
@@ -34,16 +34,16 @@ export function setCorsHeaders(req, response, {
|
|
|
34
34
|
credentials = false,
|
|
35
35
|
maxAge = 0
|
|
36
36
|
}) {
|
|
37
|
-
const reqOrigin = req.headers[
|
|
37
|
+
const reqOrigin = req.headers[CH.Origin]
|
|
38
38
|
const hasWildcard = origins.some(ao => ao === '*')
|
|
39
39
|
if (!reqOrigin || (!hasWildcard && !origins.includes(reqOrigin)))
|
|
40
40
|
return
|
|
41
|
-
response.setHeader(
|
|
41
|
+
response.setHeader(CH.AccessControlAllowOrigin, reqOrigin) // Never '*', so no need to `Vary` it
|
|
42
42
|
|
|
43
43
|
if (credentials)
|
|
44
|
-
response.setHeader(
|
|
44
|
+
response.setHeader(CH.AccessControlAllowCredentials, 'true')
|
|
45
45
|
|
|
46
|
-
if (req.headers[
|
|
46
|
+
if (req.headers[CH.AccessControlRequestMethod])
|
|
47
47
|
setPreflightSpecificHeaders(req, response, methods, headers, maxAge)
|
|
48
48
|
else
|
|
49
49
|
setActualRequestHeaders(response, exposedHeaders)
|
|
@@ -51,20 +51,20 @@ export function setCorsHeaders(req, response, {
|
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
function setPreflightSpecificHeaders(req, response, methods, headers, maxAge) {
|
|
54
|
-
const methodAskingFor = req.headers[
|
|
54
|
+
const methodAskingFor = req.headers[CH.AccessControlRequestMethod]
|
|
55
55
|
if (!methods.includes(methodAskingFor))
|
|
56
56
|
return
|
|
57
57
|
|
|
58
|
-
response.setHeader(
|
|
58
|
+
response.setHeader(CH.AccessControlAllowMethods, methodAskingFor)
|
|
59
59
|
if (headers.length)
|
|
60
|
-
response.setHeader(
|
|
60
|
+
response.setHeader(CH.AccessControlAllowHeaders, headers.join(','))
|
|
61
61
|
|
|
62
|
-
response.setHeader(
|
|
62
|
+
response.setHeader(CH.AccessControlMaxAge, maxAge)
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
|
|
66
66
|
function setActualRequestHeaders(response, exposedHeaders) {
|
|
67
67
|
// Exposed means the client-side JavaScript can read them
|
|
68
68
|
if (exposedHeaders.length)
|
|
69
|
-
response.setHeader(
|
|
69
|
+
response.setHeader(CH.AccessControlExposeHeaders, exposedHeaders.join(','))
|
|
70
70
|
}
|
|
@@ -2,7 +2,7 @@ import { equal } from 'node:assert/strict'
|
|
|
2
2
|
import { promisify } from 'node:util'
|
|
3
3
|
import { createServer } from 'node:http'
|
|
4
4
|
import { describe, it, after } from 'node:test'
|
|
5
|
-
import { isPreflight, setCorsHeaders,
|
|
5
|
+
import { isPreflight, setCorsHeaders, CorsHeader as PH } from './http-cors.js'
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
function headerIs(response, header, value) {
|
|
@@ -17,13 +17,13 @@ await describe('CORS', async () => {
|
|
|
17
17
|
let corsAllow = {}
|
|
18
18
|
|
|
19
19
|
const server = createServer((req, response) => {
|
|
20
|
+
setCorsHeaders(req, response, corsAllow)
|
|
20
21
|
if (isPreflight(req)) {
|
|
21
|
-
setCorsHeaders(req, response, corsAllow)
|
|
22
22
|
response.statusCode = 204
|
|
23
23
|
response.end()
|
|
24
|
-
return
|
|
25
24
|
}
|
|
26
|
-
|
|
25
|
+
else
|
|
26
|
+
response.end('NON_PREFLIGHT')
|
|
27
27
|
})
|
|
28
28
|
await promisify(server.listen).bind(server, 0, '127.0.0.1')()
|
|
29
29
|
after(() => {
|
|
@@ -33,6 +33,10 @@ await describe('CORS', async () => {
|
|
|
33
33
|
const { address, port } = server.address()
|
|
34
34
|
return fetch(`http://${address}:${port}/`, { method, headers })
|
|
35
35
|
}
|
|
36
|
+
function request(headers, method) {
|
|
37
|
+
const { address, port } = server.address()
|
|
38
|
+
return fetch(`http://${address}:${port}/`, { method, headers })
|
|
39
|
+
}
|
|
36
40
|
|
|
37
41
|
await describe('Identifies Preflight Requests', async () => {
|
|
38
42
|
const requiredRequestHeaders = {
|
|
@@ -41,7 +45,7 @@ await describe('CORS', async () => {
|
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
await it('Ignores non-OPTIONS requests', async () => {
|
|
44
|
-
const res = await
|
|
48
|
+
const res = await request(requiredRequestHeaders, 'POST')
|
|
45
49
|
equal(await res.text(), 'NON_PREFLIGHT')
|
|
46
50
|
})
|
|
47
51
|
|
|
@@ -90,6 +94,7 @@ await describe('CORS', async () => {
|
|
|
90
94
|
headerIs(p, PH.AccessControlAllowMethods, null)
|
|
91
95
|
headerIs(p, PH.AccessControlAllowCredentials, null)
|
|
92
96
|
headerIs(p, PH.AccessControlAllowHeaders, null)
|
|
97
|
+
headerIs(p, PH.AccessControlMaxAge, null)
|
|
93
98
|
})
|
|
94
99
|
|
|
95
100
|
await it('not in allowed origins', async () => {
|
|
@@ -186,5 +191,35 @@ await describe('CORS', async () => {
|
|
|
186
191
|
})
|
|
187
192
|
})
|
|
188
193
|
|
|
189
|
-
|
|
194
|
+
await describe('Non-Preflight (Actual Response) Headers', async () => {
|
|
195
|
+
await it('no origins allowed', async () => {
|
|
196
|
+
corsAllow = {
|
|
197
|
+
origins: [],
|
|
198
|
+
methods: ['GET']
|
|
199
|
+
}
|
|
200
|
+
const p = await request({
|
|
201
|
+
[PH.Origin]: NotAllowedDotCom
|
|
202
|
+
})
|
|
203
|
+
equal(p.status, 200)
|
|
204
|
+
headerIs(p, PH.AccessControlAllowOrigin, null)
|
|
205
|
+
headerIs(p, PH.AccessControlAllowCredentials, null)
|
|
206
|
+
headerIs(p, PH.AccessControlExposeHeaders, null)
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
await it('origin allowed', async () => {
|
|
210
|
+
corsAllow = {
|
|
211
|
+
origins: [AllowedDotCom],
|
|
212
|
+
methods: ['GET'],
|
|
213
|
+
credentials: true,
|
|
214
|
+
exposedHeaders: ['x-h1', 'x-h2']
|
|
215
|
+
}
|
|
216
|
+
const p = await request({
|
|
217
|
+
[PH.Origin]: AllowedDotCom
|
|
218
|
+
})
|
|
219
|
+
equal(p.status, 200)
|
|
220
|
+
headerIs(p, PH.AccessControlAllowOrigin, AllowedDotCom)
|
|
221
|
+
headerIs(p, PH.AccessControlAllowCredentials, 'true')
|
|
222
|
+
headerIs(p, PH.AccessControlExposeHeaders, 'x-h1,x-h2')
|
|
223
|
+
})
|
|
224
|
+
})
|
|
190
225
|
})
|
|
@@ -7,6 +7,11 @@ export function sendOK(response) {
|
|
|
7
7
|
response.end()
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
export function sendNoContent(response) {
|
|
11
|
+
response.statusCode = 204
|
|
12
|
+
response.end()
|
|
13
|
+
}
|
|
14
|
+
|
|
10
15
|
export function sendJSON(response, payload) {
|
|
11
16
|
response.setHeader('Content-Type', 'application/json')
|
|
12
17
|
response.end(JSON.stringify(payload))
|