mockaton 7.0.0 → 7.1.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 +26 -10
- package/Tests.js +23 -1
- package/index.d.ts +8 -0
- package/package.json +1 -1
- package/src/Config.js +39 -2
- package/src/Mockaton.js +20 -3
- package/src/utils/http-cors.js +70 -0
- package/src/utils/http-cors.test.js +190 -0
- package/src/utils/http-request.js +7 -1
package/README.md
CHANGED
|
@@ -12,8 +12,8 @@ my-mocks-dir/api/user/[user-id].GET.200.json
|
|
|
12
12
|
[This browser extension](https://github.com/ericfortis/devtools-ext-tar-http-requests)
|
|
13
13
|
can be used for downloading a TAR of your XHR requests following that convention.
|
|
14
14
|
|
|
15
|
-
##
|
|
16
|
-
-
|
|
15
|
+
## Benefits
|
|
16
|
+
- Avoids having to spin up and maintain hefty or complex backends when developing UIs.
|
|
17
17
|
- For a deterministic and comprehensive backend state. For example, having all the possible
|
|
18
18
|
state variants of a particular collection helps for spotting inadvertent bugs. And having those
|
|
19
19
|
assorted responses are not easy to trigger from the backend.
|
|
@@ -71,6 +71,7 @@ node my-mockaton.js
|
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
## Config Options
|
|
74
|
+
There’s a Config section below with more details.
|
|
74
75
|
```ts
|
|
75
76
|
interface Config {
|
|
76
77
|
mocksDir: string
|
|
@@ -87,10 +88,12 @@ interface Config {
|
|
|
87
88
|
extraMimes?: { [fileExt: string]: string }
|
|
88
89
|
extraHeaders?: []
|
|
89
90
|
|
|
91
|
+
corsAllowed?: boolean, // Defaults to false
|
|
92
|
+
// The options for customizing CORS are listed below
|
|
93
|
+
|
|
90
94
|
onReady?: (dashboardUrl: string) => void // Defaults to trying to open macOS and Win default browser.
|
|
91
95
|
}
|
|
92
96
|
```
|
|
93
|
-
There’s a Config section below with more details.
|
|
94
97
|
|
|
95
98
|
---
|
|
96
99
|
|
|
@@ -259,6 +262,19 @@ Config.extraMimes = {
|
|
|
259
262
|
}
|
|
260
263
|
```
|
|
261
264
|
|
|
265
|
+
## `Config.corsAllowed`
|
|
266
|
+
```js
|
|
267
|
+
Config.corsAllowed = true
|
|
268
|
+
|
|
269
|
+
// Defaults when `corsAllowed === true`
|
|
270
|
+
Config.corsOrigins = ['*']
|
|
271
|
+
Config.corsMethods = ['GET', 'PUT', 'DELETE', 'POST', 'PATCH', 'HEAD', 'OPTIONS', 'TRACE', 'CONNECT']
|
|
272
|
+
Config.corsHeaders = []
|
|
273
|
+
Config.corsCredentials = true
|
|
274
|
+
Config.corsMaxAge = 0
|
|
275
|
+
Config.corsExposedHeaders = []
|
|
276
|
+
```
|
|
277
|
+
|
|
262
278
|
## `Config.onReady`
|
|
263
279
|
This is a callback `(dashboardAddress: string) => void`, which defaults to
|
|
264
280
|
trying to open the dashboard in your default browser in macOS and Windows.
|
|
@@ -296,32 +312,32 @@ import { Commander } from 'mockaton'
|
|
|
296
312
|
|
|
297
313
|
|
|
298
314
|
const myMockatonAddr = 'http://localhost:2345'
|
|
299
|
-
const
|
|
315
|
+
const mockaton = new Commander(myMockatonAddr)
|
|
300
316
|
```
|
|
301
317
|
|
|
302
318
|
### Select a mock file for a route
|
|
303
319
|
```js
|
|
304
|
-
await
|
|
320
|
+
await mockaton.select('api/foo.200.GET.json')
|
|
305
321
|
```
|
|
306
322
|
### Select all mocks that have a particular comment
|
|
307
323
|
```js
|
|
308
|
-
await
|
|
324
|
+
await mockaton.bulkSelectByComment('(demo-a)')
|
|
309
325
|
```
|
|
310
326
|
|
|
311
327
|
### Set Route is Delayed Flag
|
|
312
328
|
```js
|
|
313
|
-
await
|
|
329
|
+
await mockaton.setRouteIsDelayed('GET', '/api/foo', true)
|
|
314
330
|
```
|
|
315
331
|
|
|
316
332
|
### Select a cookie
|
|
317
333
|
In `Config.cookies`, each key is the label used for selecting it.
|
|
318
334
|
```js
|
|
319
|
-
await
|
|
335
|
+
await mockaton.selectCookie('My Normal User')
|
|
320
336
|
```
|
|
321
337
|
|
|
322
338
|
### Set Fallback Proxy
|
|
323
339
|
```js
|
|
324
|
-
await
|
|
340
|
+
await mockaton.setProxyFallback('http://example.com')
|
|
325
341
|
```
|
|
326
342
|
Pass an empty string to disable it.
|
|
327
343
|
|
|
@@ -330,7 +346,7 @@ Re-initialize the collection. So if you added or removed mocks they
|
|
|
330
346
|
will be considered. The selected mocks, cookies, and delays go
|
|
331
347
|
back to default, but `Config.proxyFallback` is not affected.
|
|
332
348
|
```js
|
|
333
|
-
await
|
|
349
|
+
await mockaton.reset()
|
|
334
350
|
```
|
|
335
351
|
|
|
336
352
|
|
package/Tests.js
CHANGED
|
@@ -13,6 +13,7 @@ import { mimeFor } from './src/utils/mime.js'
|
|
|
13
13
|
import { Mockaton } from './src/Mockaton.js'
|
|
14
14
|
import { Commander } from './src/Commander.js'
|
|
15
15
|
import { parseFilename } from './src/Filename.js'
|
|
16
|
+
import { PreflightHeader } from './src/utils/http-cors.js'
|
|
16
17
|
import { API, DEFAULT_500_COMMENT, DEFAULT_MOCK_COMMENT } from './src/ApiConstants.js'
|
|
17
18
|
|
|
18
19
|
|
|
@@ -157,7 +158,9 @@ const server = Mockaton({
|
|
|
157
158
|
extraHeaders: ['Server', 'MockatonTester'],
|
|
158
159
|
extraMimes: {
|
|
159
160
|
my_custom_extension: 'my_custom_mime'
|
|
160
|
-
}
|
|
161
|
+
},
|
|
162
|
+
corsAllowed: true,
|
|
163
|
+
corsOrigins: ['http://example.com']
|
|
161
164
|
})
|
|
162
165
|
server.on('listening', runTests)
|
|
163
166
|
|
|
@@ -223,10 +226,12 @@ async function runTests() {
|
|
|
223
226
|
await testMockDispatching(...fixtureCustomMime, 'my_custom_mime')
|
|
224
227
|
await testJsFunctionMocks()
|
|
225
228
|
|
|
229
|
+
await testCorsAllowed()
|
|
226
230
|
await testItUpdatesUserRole()
|
|
227
231
|
await testStaticFileServing()
|
|
228
232
|
await testInvalidFilenamesAreIgnored()
|
|
229
233
|
await testEnableFallbackSoRoutesWithoutMocksGetRelayed()
|
|
234
|
+
|
|
230
235
|
server.close()
|
|
231
236
|
}
|
|
232
237
|
|
|
@@ -435,6 +440,23 @@ async function testEnableFallbackSoRoutesWithoutMocksGetRelayed() {
|
|
|
435
440
|
})
|
|
436
441
|
}
|
|
437
442
|
|
|
443
|
+
// TODO make API for changing CORS? so we can automate testing?
|
|
444
|
+
async function testCorsAllowed() {
|
|
445
|
+
await it('cors', async () => {
|
|
446
|
+
const res = await request('/does-not-matter', {
|
|
447
|
+
method: 'OPTIONS',
|
|
448
|
+
headers: {
|
|
449
|
+
[PreflightHeader.Origin]: 'http://example.com',
|
|
450
|
+
[PreflightHeader.AccessControlRequestMethod]: 'GET'
|
|
451
|
+
}
|
|
452
|
+
})
|
|
453
|
+
equal(res.status, 204)
|
|
454
|
+
equal(res.headers.get(PreflightHeader.AccessControlAllowOrigin), 'http://example.com')
|
|
455
|
+
equal(res.headers.get(PreflightHeader.AccessControlAllowMethods), 'GET')
|
|
456
|
+
})
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
|
|
438
460
|
// Utils
|
|
439
461
|
|
|
440
462
|
function write(filename, data) {
|
package/index.d.ts
CHANGED
|
@@ -15,6 +15,14 @@ interface Config {
|
|
|
15
15
|
extraHeaders?: [string, string][]
|
|
16
16
|
extraMimes?: { [fileExt: string]: string }
|
|
17
17
|
|
|
18
|
+
corsAllowed?: boolean,
|
|
19
|
+
corsOrigins: string[]
|
|
20
|
+
corsMethods: string[]
|
|
21
|
+
corsHeaders: string[]
|
|
22
|
+
corsExposedHeaders: string[]
|
|
23
|
+
corsCredentials: boolean
|
|
24
|
+
corsMaxAge: number
|
|
25
|
+
|
|
18
26
|
onReady?: (address: string) => void
|
|
19
27
|
}
|
|
20
28
|
|
package/package.json
CHANGED
package/src/Config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { isDirectory } from './utils/fs.js'
|
|
1
2
|
import { openInBrowser } from './utils/openInBrowser.js'
|
|
3
|
+
import { StandardMethods } from './utils/http-request.js'
|
|
2
4
|
import { validate, is, optional } from './utils/validate.js'
|
|
3
|
-
import { isDirectory } from './utils/fs.js'
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
export const Config = Object.seal({
|
|
@@ -18,6 +19,14 @@ export const Config = Object.seal({
|
|
|
18
19
|
extraHeaders: [],
|
|
19
20
|
extraMimes: {},
|
|
20
21
|
|
|
22
|
+
corsAllowed: false,
|
|
23
|
+
corsOrigins: ['*'],
|
|
24
|
+
corsMethods: StandardMethods,
|
|
25
|
+
corsHeaders: [],
|
|
26
|
+
corsExposedHeaders: [],
|
|
27
|
+
corsCredentials: true,
|
|
28
|
+
corsMaxAge: 0,
|
|
29
|
+
|
|
21
30
|
onReady: openInBrowser
|
|
22
31
|
})
|
|
23
32
|
|
|
@@ -36,11 +45,39 @@ export function setup(options) {
|
|
|
36
45
|
|
|
37
46
|
delay: ms => Number.isInteger(ms) && ms > 0,
|
|
38
47
|
cookies: is(Object),
|
|
39
|
-
extraHeaders: Array.isArray,
|
|
48
|
+
extraHeaders: val => Array.isArray(val) && val.length % 2 === 0,
|
|
40
49
|
extraMimes: is(Object),
|
|
41
50
|
|
|
51
|
+
corsAllowed: is(Boolean),
|
|
52
|
+
corsOrigins: validateCorsAllowedOrigins,
|
|
53
|
+
corsMethods: validateCorsAllowedMethods,
|
|
54
|
+
corsHeaders: Array.isArray,
|
|
55
|
+
corsExposedHeaders: Array.isArray,
|
|
56
|
+
corsCredentials: is(Boolean),
|
|
57
|
+
corsMaxAge: is(Number),
|
|
58
|
+
|
|
42
59
|
onReady: is(Function)
|
|
43
60
|
})
|
|
61
|
+
|
|
62
|
+
if (!Config.corsAllowed) // TESTME
|
|
63
|
+
Config.corsOrigins = []
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
function validateCorsAllowedOrigins(arr) {
|
|
68
|
+
if (!Array.isArray(arr))
|
|
69
|
+
return false
|
|
70
|
+
|
|
71
|
+
if (arr.length === 1 && arr[0] === '*')
|
|
72
|
+
return true
|
|
73
|
+
|
|
74
|
+
return arr.every(o => URL.canParse(o))
|
|
44
75
|
}
|
|
45
76
|
|
|
46
77
|
|
|
78
|
+
function validateCorsAllowedMethods(arr) {
|
|
79
|
+
if (!Array.isArray(arr))
|
|
80
|
+
return false
|
|
81
|
+
|
|
82
|
+
return arr.every(m => StandardMethods.includes(m))
|
|
83
|
+
}
|
package/src/Mockaton.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { createServer } from 'node:http'
|
|
2
2
|
|
|
3
3
|
import { API } from './ApiConstants.js'
|
|
4
|
-
import { Config, setup } from './Config.js'
|
|
5
4
|
import { dispatchMock } from './MockDispatcher.js'
|
|
5
|
+
import { Config, setup } from './Config.js'
|
|
6
6
|
import * as mockBrokerCollection from './mockBrokersCollection.js'
|
|
7
7
|
import { dispatchStatic, isStatic } from './StaticDispatcher.js'
|
|
8
|
+
import { setCorsHeaders, isPreflight } from './utils/http-cors.js'
|
|
8
9
|
import { apiPatchRequests, apiGetRequests } from './Api.js'
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
export function Mockaton(options) {
|
|
12
13
|
setup(options)
|
|
13
14
|
mockBrokerCollection.init()
|
|
15
|
+
|
|
14
16
|
const server = createServer(onRequest)
|
|
15
17
|
server.listen(Config.port, Config.host, (error) => {
|
|
16
18
|
const { address, port } = server.address()
|
|
@@ -26,10 +28,25 @@ export function Mockaton(options) {
|
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
async function onRequest(req, response) {
|
|
31
|
+
const { url, method } = req
|
|
29
32
|
response.setHeader('Server', 'Mockaton')
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
if (Config.corsAllowed)
|
|
35
|
+
setCorsHeaders(req, response, {
|
|
36
|
+
origins: Config.corsOrigins,
|
|
37
|
+
headers: Config.corsHeaders,
|
|
38
|
+
methods: Config.corsMethods,
|
|
39
|
+
maxAge: Config.corsMaxAge,
|
|
40
|
+
credentials: Config.corsCredentials,
|
|
41
|
+
exposedHeaders: Config.extraHeaders
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if (isPreflight(req)) {
|
|
46
|
+
response.statusCode = 204
|
|
47
|
+
response.end()
|
|
48
|
+
}
|
|
49
|
+
else if (method === 'GET' && apiGetRequests.has(url))
|
|
33
50
|
apiGetRequests.get(url)(req, response)
|
|
34
51
|
|
|
35
52
|
else if (method === 'PATCH' && apiPatchRequests.has(url))
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { StandardMethods } from './http-request.js'
|
|
2
|
+
|
|
3
|
+
// https://www.w3.org/TR/2020/SPSD-cors-20200602/#resource-processing-model
|
|
4
|
+
|
|
5
|
+
export const PreflightHeader = {
|
|
6
|
+
// request
|
|
7
|
+
Origin: 'origin',
|
|
8
|
+
AccessControlRequestMethod: 'access-control-request-method',
|
|
9
|
+
AccessControlRequestHeaders: 'access-control-request-headers', // Comma separated
|
|
10
|
+
|
|
11
|
+
// response
|
|
12
|
+
AccessControlMaxAge: 'Access-Control-Max-Age',
|
|
13
|
+
AccessControlAllowOrigin: 'Access-Control-Allow-Origin', // '*' | Space delimited | null
|
|
14
|
+
AccessControlAllowMethods: 'Access-Control-Allow-Methods', // '*' | Comma delimited
|
|
15
|
+
AccessControlAllowHeaders: 'Access-Control-Allow-Headers', // '*' | Comma delimited
|
|
16
|
+
AccessControlExposeHeaders: 'Access-Control-Expose-Headers', // '*' | Comma delimited
|
|
17
|
+
AccessControlAllowCredentials: 'Access-Control-Allow-Credentials' // 'true'
|
|
18
|
+
}
|
|
19
|
+
const PH = PreflightHeader
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
export function isPreflight(req) {
|
|
23
|
+
return req.method === 'OPTIONS'
|
|
24
|
+
&& URL.canParse(req.headers[PH.Origin])
|
|
25
|
+
&& StandardMethods.includes(req.headers[PH.AccessControlRequestMethod])
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
export function setCorsHeaders(req, response, {
|
|
30
|
+
origins = [],
|
|
31
|
+
methods = [],
|
|
32
|
+
headers = [],
|
|
33
|
+
exposedHeaders = [],
|
|
34
|
+
credentials = false,
|
|
35
|
+
maxAge = 0
|
|
36
|
+
}) {
|
|
37
|
+
const reqOrigin = req.headers[PH.Origin]
|
|
38
|
+
const hasWildcard = origins.some(ao => ao === '*')
|
|
39
|
+
if (!reqOrigin || (!hasWildcard && !origins.includes(reqOrigin)))
|
|
40
|
+
return
|
|
41
|
+
response.setHeader(PH.AccessControlAllowOrigin, reqOrigin) // Never '*', so no need to `Vary` it
|
|
42
|
+
|
|
43
|
+
if (credentials)
|
|
44
|
+
response.setHeader(PH.AccessControlAllowCredentials, 'true')
|
|
45
|
+
|
|
46
|
+
if (req.headers[PH.AccessControlRequestMethod])
|
|
47
|
+
setPreflightSpecificHeaders(req, response, methods, headers, maxAge)
|
|
48
|
+
else
|
|
49
|
+
setActualRequestHeaders(response, exposedHeaders)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
function setPreflightSpecificHeaders(req, response, methods, headers, maxAge) {
|
|
54
|
+
const methodAskingFor = req.headers[PH.AccessControlRequestMethod]
|
|
55
|
+
if (!methods.includes(methodAskingFor))
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
response.setHeader(PH.AccessControlAllowMethods, methodAskingFor)
|
|
59
|
+
if (headers.length)
|
|
60
|
+
response.setHeader(PH.AccessControlAllowHeaders, headers.join(','))
|
|
61
|
+
|
|
62
|
+
response.setHeader(PH.AccessControlMaxAge, maxAge)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
function setActualRequestHeaders(response, exposedHeaders) {
|
|
67
|
+
// Exposed means the client-side JavaScript can read them
|
|
68
|
+
if (exposedHeaders.length)
|
|
69
|
+
response.setHeader(PH.AccessControlExposeHeaders, exposedHeaders.join(','))
|
|
70
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { equal } from 'node:assert/strict'
|
|
2
|
+
import { promisify } from 'node:util'
|
|
3
|
+
import { createServer } from 'node:http'
|
|
4
|
+
import { describe, it, after } from 'node:test'
|
|
5
|
+
import { isPreflight, setCorsHeaders, PreflightHeader as PH } from './http-cors.js'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
function headerIs(response, header, value) {
|
|
9
|
+
equal(response.headers.get(header), value)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const FooDotCom = 'http://foo.com'
|
|
13
|
+
const AllowedDotCom = 'http://allowed.com'
|
|
14
|
+
const NotAllowedDotCom = 'http://not-allowed.com'
|
|
15
|
+
|
|
16
|
+
await describe('CORS', async () => {
|
|
17
|
+
let corsAllow = {}
|
|
18
|
+
|
|
19
|
+
const server = createServer((req, response) => {
|
|
20
|
+
if (isPreflight(req)) {
|
|
21
|
+
setCorsHeaders(req, response, corsAllow)
|
|
22
|
+
response.statusCode = 204
|
|
23
|
+
response.end()
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
response.end('NON_PREFLIGHT')
|
|
27
|
+
})
|
|
28
|
+
await promisify(server.listen).bind(server, 0, '127.0.0.1')()
|
|
29
|
+
after(() => {
|
|
30
|
+
server.close()
|
|
31
|
+
})
|
|
32
|
+
function preflight(headers, method = 'OPTIONS') {
|
|
33
|
+
const { address, port } = server.address()
|
|
34
|
+
return fetch(`http://${address}:${port}/`, { method, headers })
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
await describe('Identifies Preflight Requests', async () => {
|
|
38
|
+
const requiredRequestHeaders = {
|
|
39
|
+
[PH.Origin]: 'http://locahost:9999',
|
|
40
|
+
[PH.AccessControlRequestMethod]: 'POST'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await it('Ignores non-OPTIONS requests', async () => {
|
|
44
|
+
const res = await preflight(requiredRequestHeaders, 'POST')
|
|
45
|
+
equal(await res.text(), 'NON_PREFLIGHT')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
await it(`Ignores non-parseable req ${PH.Origin} header`, async () => {
|
|
49
|
+
const headers = {
|
|
50
|
+
...requiredRequestHeaders,
|
|
51
|
+
[PH.Origin]: 'non-url'
|
|
52
|
+
}
|
|
53
|
+
const res = await preflight(headers)
|
|
54
|
+
equal(await res.text(), 'NON_PREFLIGHT')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
await it(`Ignores missing method in ${PH.AccessControlRequestMethod} header`, async () => {
|
|
58
|
+
const headers = { ...requiredRequestHeaders }
|
|
59
|
+
delete headers[PH.AccessControlRequestMethod]
|
|
60
|
+
const res = await preflight(headers)
|
|
61
|
+
equal(await res.text(), 'NON_PREFLIGHT')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
await it(`Ignores non-standard method in ${PH.AccessControlRequestMethod} header`, async () => {
|
|
65
|
+
const headers = {
|
|
66
|
+
...requiredRequestHeaders,
|
|
67
|
+
[PH.AccessControlRequestMethod]: 'NON_STANDARD'
|
|
68
|
+
}
|
|
69
|
+
const res = await preflight(headers)
|
|
70
|
+
equal(await res.text(), 'NON_PREFLIGHT')
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
await it('204 valid preflights', async () => {
|
|
74
|
+
const res = await preflight(requiredRequestHeaders)
|
|
75
|
+
equal(res.status, 204)
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
await describe('Preflight Response Headers', async () => {
|
|
80
|
+
await it('no origins allowed', async () => {
|
|
81
|
+
corsAllow = {
|
|
82
|
+
origins: [],
|
|
83
|
+
methods: ['GET']
|
|
84
|
+
}
|
|
85
|
+
const p = await preflight({
|
|
86
|
+
[PH.Origin]: FooDotCom,
|
|
87
|
+
[PH.AccessControlRequestMethod]: 'GET'
|
|
88
|
+
})
|
|
89
|
+
headerIs(p, PH.AccessControlAllowOrigin, null)
|
|
90
|
+
headerIs(p, PH.AccessControlAllowMethods, null)
|
|
91
|
+
headerIs(p, PH.AccessControlAllowCredentials, null)
|
|
92
|
+
headerIs(p, PH.AccessControlAllowHeaders, null)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
await it('not in allowed origins', async () => {
|
|
96
|
+
corsAllow = {
|
|
97
|
+
origins: [AllowedDotCom],
|
|
98
|
+
methods: ['GET']
|
|
99
|
+
}
|
|
100
|
+
const p = await preflight({
|
|
101
|
+
[PH.Origin]: NotAllowedDotCom,
|
|
102
|
+
[PH.AccessControlRequestMethod]: 'GET'
|
|
103
|
+
})
|
|
104
|
+
headerIs(p, PH.AccessControlAllowOrigin, null)
|
|
105
|
+
headerIs(p, PH.AccessControlAllowMethods, null)
|
|
106
|
+
headerIs(p, PH.AccessControlAllowCredentials, null)
|
|
107
|
+
headerIs(p, PH.AccessControlAllowHeaders, null)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
await it('origin and method match', async () => {
|
|
111
|
+
corsAllow = {
|
|
112
|
+
origins: [AllowedDotCom],
|
|
113
|
+
methods: ['GET']
|
|
114
|
+
}
|
|
115
|
+
const p = await preflight({
|
|
116
|
+
[PH.Origin]: AllowedDotCom,
|
|
117
|
+
[PH.AccessControlRequestMethod]: 'GET'
|
|
118
|
+
})
|
|
119
|
+
headerIs(p, PH.AccessControlAllowOrigin, AllowedDotCom)
|
|
120
|
+
headerIs(p, PH.AccessControlAllowMethods, 'GET')
|
|
121
|
+
headerIs(p, PH.AccessControlAllowCredentials, null)
|
|
122
|
+
headerIs(p, PH.AccessControlAllowHeaders, null)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
await it('origin matches from multiple', async () => {
|
|
126
|
+
corsAllow = {
|
|
127
|
+
origins: [AllowedDotCom, FooDotCom],
|
|
128
|
+
methods: ['GET']
|
|
129
|
+
}
|
|
130
|
+
const p = await preflight({
|
|
131
|
+
[PH.Origin]: AllowedDotCom,
|
|
132
|
+
[PH.AccessControlRequestMethod]: 'GET'
|
|
133
|
+
})
|
|
134
|
+
headerIs(p, PH.AccessControlAllowOrigin, AllowedDotCom)
|
|
135
|
+
headerIs(p, PH.AccessControlAllowMethods, 'GET')
|
|
136
|
+
headerIs(p, PH.AccessControlAllowCredentials, null)
|
|
137
|
+
headerIs(p, PH.AccessControlAllowHeaders, null)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
await it('wildcard origin', async () => {
|
|
141
|
+
corsAllow = {
|
|
142
|
+
origins: ['*'],
|
|
143
|
+
methods: ['GET']
|
|
144
|
+
}
|
|
145
|
+
const p = await preflight({
|
|
146
|
+
[PH.Origin]: FooDotCom,
|
|
147
|
+
[PH.AccessControlRequestMethod]: 'GET'
|
|
148
|
+
})
|
|
149
|
+
headerIs(p, PH.AccessControlAllowOrigin, FooDotCom)
|
|
150
|
+
headerIs(p, PH.AccessControlAllowMethods, 'GET')
|
|
151
|
+
headerIs(p, PH.AccessControlAllowCredentials, null)
|
|
152
|
+
headerIs(p, PH.AccessControlAllowHeaders, null)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
await it(`wildcard and credentials`, async () => {
|
|
156
|
+
corsAllow = {
|
|
157
|
+
origins: ['*'],
|
|
158
|
+
methods: ['GET'],
|
|
159
|
+
credentials: true
|
|
160
|
+
}
|
|
161
|
+
const p = await preflight({
|
|
162
|
+
[PH.Origin]: FooDotCom,
|
|
163
|
+
[PH.AccessControlRequestMethod]: 'GET'
|
|
164
|
+
})
|
|
165
|
+
headerIs(p, PH.AccessControlAllowOrigin, FooDotCom)
|
|
166
|
+
headerIs(p, PH.AccessControlAllowMethods, 'GET')
|
|
167
|
+
headerIs(p, PH.AccessControlAllowCredentials, 'true')
|
|
168
|
+
headerIs(p, PH.AccessControlAllowHeaders, null)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
await it(`wildcard, credentials, and headers`, async () => {
|
|
172
|
+
corsAllow = {
|
|
173
|
+
origins: ['*'],
|
|
174
|
+
methods: ['GET'],
|
|
175
|
+
credentials: true,
|
|
176
|
+
headers: ['content-type', 'my-header']
|
|
177
|
+
}
|
|
178
|
+
const p = await preflight({
|
|
179
|
+
[PH.Origin]: FooDotCom,
|
|
180
|
+
[PH.AccessControlRequestMethod]: 'GET'
|
|
181
|
+
})
|
|
182
|
+
headerIs(p, PH.AccessControlAllowOrigin, FooDotCom)
|
|
183
|
+
headerIs(p, PH.AccessControlAllowMethods, 'GET')
|
|
184
|
+
headerIs(p, PH.AccessControlAllowCredentials, 'true')
|
|
185
|
+
headerIs(p, PH.AccessControlAllowHeaders, 'content-type,my-header')
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
// TODO Actual request response headers
|
|
190
|
+
})
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
export const StandardMethods = [
|
|
2
|
+
'GET', 'PUT', 'DELETE', 'POST', 'PATCH',
|
|
3
|
+
'HEAD', 'OPTIONS', 'TRACE', 'CONNECT'
|
|
4
|
+
]
|
|
5
|
+
|
|
6
|
+
|
|
1
7
|
export class JsonBodyParserError extends Error {}
|
|
2
8
|
|
|
3
9
|
export function parseJSON(req) {
|
|
@@ -33,4 +39,4 @@ export function parseJSON(req) {
|
|
|
33
39
|
}
|
|
34
40
|
}
|
|
35
41
|
})
|
|
36
|
-
}
|
|
42
|
+
}
|