mockaton 7.0.0 → 7.2.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 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
- ## What do I use it for?
16
- - I’m a frontend dev, so I don’t have to spin up and maintain hefty or complex backends.
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 = ['content-type']
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 commander = new Commander(myMockatonAddr)
315
+ const mockaton = new Commander(myMockatonAddr)
300
316
  ```
301
317
 
302
318
  ### Select a mock file for a route
303
319
  ```js
304
- await commander.select('api/foo.200.GET.json')
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 commander.bulkSelectByComment('(demo-a)')
324
+ await mockaton.bulkSelectByComment('(demo-a)')
309
325
  ```
310
326
 
311
327
  ### Set Route is Delayed Flag
312
328
  ```js
313
- await commander.setRouteIsDelayed('GET', '/api/foo', true)
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 commander.selectCookie('My Normal User')
335
+ await mockaton.selectCookie('My Normal User')
320
336
  ```
321
337
 
322
338
  ### Set Fallback Proxy
323
339
  ```js
324
- await commander.setProxyFallback('http://example.com')
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 commander.reset()
349
+ await mockaton.reset()
334
350
  ```
335
351
 
336
352
 
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
@@ -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.0.0",
5
+ "version": "7.2.0",
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": "./Tests.js",
11
+ "test": "node --test",
12
12
  "demo": "./_usage_example.js"
13
13
  }
14
14
  }
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: ['content-type'],
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,19 @@
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
+ import { sendNoContent } from './utils/http-response.js'
6
7
  import * as mockBrokerCollection from './mockBrokersCollection.js'
7
8
  import { dispatchStatic, isStatic } from './StaticDispatcher.js'
9
+ import { setCorsHeaders, isPreflight } from './utils/http-cors.js'
8
10
  import { apiPatchRequests, apiGetRequests } from './Api.js'
9
11
 
10
12
 
11
13
  export function Mockaton(options) {
12
14
  setup(options)
13
15
  mockBrokerCollection.init()
16
+
14
17
  const server = createServer(onRequest)
15
18
  server.listen(Config.port, Config.host, (error) => {
16
19
  const { address, port } = server.address()
@@ -28,8 +31,22 @@ export function Mockaton(options) {
28
31
  async function onRequest(req, response) {
29
32
  response.setHeader('Server', 'Mockaton')
30
33
 
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
+
31
44
  const { url, method } = req
32
- if (method === 'GET' && apiGetRequests.has(url))
45
+
46
+ if (isPreflight(req))
47
+ sendNoContent(response)
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))
@@ -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,12 +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 './src/Config.js'
12
- import { mimeFor } from './src/utils/mime.js'
13
- import { Mockaton } from './src/Mockaton.js'
14
- import { Commander } from './src/Commander.js'
15
- import { parseFilename } from './src/Filename.js'
16
- import { API, DEFAULT_500_COMMENT, DEFAULT_MOCK_COMMENT } from './src/ApiConstants.js'
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 { PreflightHeader } from './utils/http-cors.js'
15
+ import { API, DEFAULT_500_COMMENT, DEFAULT_MOCK_COMMENT } from './ApiConstants.js'
17
16
 
18
17
 
19
18
  const tmpDir = mkdtempSync(tmpdir()) + '/'
@@ -157,7 +156,9 @@ const server = Mockaton({
157
156
  extraHeaders: ['Server', 'MockatonTester'],
158
157
  extraMimes: {
159
158
  my_custom_extension: 'my_custom_mime'
160
- }
159
+ },
160
+ corsAllowed: true,
161
+ corsOrigins: ['http://example.com']
161
162
  })
162
163
  server.on('listening', runTests)
163
164
 
@@ -223,10 +224,12 @@ async function runTests() {
223
224
  await testMockDispatching(...fixtureCustomMime, 'my_custom_mime')
224
225
  await testJsFunctionMocks()
225
226
 
227
+ await testCorsAllowed()
226
228
  await testItUpdatesUserRole()
227
229
  await testStaticFileServing()
228
230
  await testInvalidFilenamesAreIgnored()
229
231
  await testEnableFallbackSoRoutesWithoutMocksGetRelayed()
232
+
230
233
  server.close()
231
234
  }
232
235
 
@@ -435,6 +438,23 @@ async function testEnableFallbackSoRoutesWithoutMocksGetRelayed() {
435
438
  })
436
439
  }
437
440
 
441
+ // TODO make API for changing CORS? so we can automate testing?
442
+ async function testCorsAllowed() {
443
+ await it('cors', async () => {
444
+ const res = await request('/does-not-matter', {
445
+ method: 'OPTIONS',
446
+ headers: {
447
+ [PreflightHeader.Origin]: 'http://example.com',
448
+ [PreflightHeader.AccessControlRequestMethod]: 'GET'
449
+ }
450
+ })
451
+ equal(res.status, 204)
452
+ equal(res.headers.get(PreflightHeader.AccessControlAllowOrigin), 'http://example.com')
453
+ equal(res.headers.get(PreflightHeader.AccessControlAllowMethods), 'GET')
454
+ })
455
+ }
456
+
457
+
438
458
  // Utils
439
459
 
440
460
  function write(filename, data) {
@@ -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,225 @@
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
+ setCorsHeaders(req, response, corsAllow)
21
+ if (isPreflight(req)) {
22
+ response.statusCode = 204
23
+ response.end()
24
+ }
25
+ else
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
+ function request(headers, method) {
37
+ const { address, port } = server.address()
38
+ return fetch(`http://${address}:${port}/`, { method, headers })
39
+ }
40
+
41
+ await describe('Identifies Preflight Requests', async () => {
42
+ const requiredRequestHeaders = {
43
+ [PH.Origin]: 'http://locahost:9999',
44
+ [PH.AccessControlRequestMethod]: 'POST'
45
+ }
46
+
47
+ await it('Ignores non-OPTIONS requests', async () => {
48
+ const res = await request(requiredRequestHeaders, 'POST')
49
+ equal(await res.text(), 'NON_PREFLIGHT')
50
+ })
51
+
52
+ await it(`Ignores non-parseable req ${PH.Origin} header`, async () => {
53
+ const headers = {
54
+ ...requiredRequestHeaders,
55
+ [PH.Origin]: 'non-url'
56
+ }
57
+ const res = await preflight(headers)
58
+ equal(await res.text(), 'NON_PREFLIGHT')
59
+ })
60
+
61
+ await it(`Ignores missing method in ${PH.AccessControlRequestMethod} header`, async () => {
62
+ const headers = { ...requiredRequestHeaders }
63
+ delete headers[PH.AccessControlRequestMethod]
64
+ const res = await preflight(headers)
65
+ equal(await res.text(), 'NON_PREFLIGHT')
66
+ })
67
+
68
+ await it(`Ignores non-standard method in ${PH.AccessControlRequestMethod} header`, async () => {
69
+ const headers = {
70
+ ...requiredRequestHeaders,
71
+ [PH.AccessControlRequestMethod]: 'NON_STANDARD'
72
+ }
73
+ const res = await preflight(headers)
74
+ equal(await res.text(), 'NON_PREFLIGHT')
75
+ })
76
+
77
+ await it('204 valid preflights', async () => {
78
+ const res = await preflight(requiredRequestHeaders)
79
+ equal(res.status, 204)
80
+ })
81
+ })
82
+
83
+ await describe('Preflight Response Headers', async () => {
84
+ await it('no origins allowed', async () => {
85
+ corsAllow = {
86
+ origins: [],
87
+ methods: ['GET']
88
+ }
89
+ const p = await preflight({
90
+ [PH.Origin]: FooDotCom,
91
+ [PH.AccessControlRequestMethod]: 'GET'
92
+ })
93
+ headerIs(p, PH.AccessControlAllowOrigin, null)
94
+ headerIs(p, PH.AccessControlAllowMethods, null)
95
+ headerIs(p, PH.AccessControlAllowCredentials, null)
96
+ headerIs(p, PH.AccessControlAllowHeaders, null)
97
+ headerIs(p, PH.AccessControlMaxAge, null)
98
+ })
99
+
100
+ await it('not in allowed origins', async () => {
101
+ corsAllow = {
102
+ origins: [AllowedDotCom],
103
+ methods: ['GET']
104
+ }
105
+ const p = await preflight({
106
+ [PH.Origin]: NotAllowedDotCom,
107
+ [PH.AccessControlRequestMethod]: 'GET'
108
+ })
109
+ headerIs(p, PH.AccessControlAllowOrigin, null)
110
+ headerIs(p, PH.AccessControlAllowMethods, null)
111
+ headerIs(p, PH.AccessControlAllowCredentials, null)
112
+ headerIs(p, PH.AccessControlAllowHeaders, null)
113
+ })
114
+
115
+ await it('origin and method match', async () => {
116
+ corsAllow = {
117
+ origins: [AllowedDotCom],
118
+ methods: ['GET']
119
+ }
120
+ const p = await preflight({
121
+ [PH.Origin]: AllowedDotCom,
122
+ [PH.AccessControlRequestMethod]: 'GET'
123
+ })
124
+ headerIs(p, PH.AccessControlAllowOrigin, AllowedDotCom)
125
+ headerIs(p, PH.AccessControlAllowMethods, 'GET')
126
+ headerIs(p, PH.AccessControlAllowCredentials, null)
127
+ headerIs(p, PH.AccessControlAllowHeaders, null)
128
+ })
129
+
130
+ await it('origin matches from multiple', async () => {
131
+ corsAllow = {
132
+ origins: [AllowedDotCom, FooDotCom],
133
+ methods: ['GET']
134
+ }
135
+ const p = await preflight({
136
+ [PH.Origin]: AllowedDotCom,
137
+ [PH.AccessControlRequestMethod]: 'GET'
138
+ })
139
+ headerIs(p, PH.AccessControlAllowOrigin, AllowedDotCom)
140
+ headerIs(p, PH.AccessControlAllowMethods, 'GET')
141
+ headerIs(p, PH.AccessControlAllowCredentials, null)
142
+ headerIs(p, PH.AccessControlAllowHeaders, null)
143
+ })
144
+
145
+ await it('wildcard origin', async () => {
146
+ corsAllow = {
147
+ origins: ['*'],
148
+ methods: ['GET']
149
+ }
150
+ const p = await preflight({
151
+ [PH.Origin]: FooDotCom,
152
+ [PH.AccessControlRequestMethod]: 'GET'
153
+ })
154
+ headerIs(p, PH.AccessControlAllowOrigin, FooDotCom)
155
+ headerIs(p, PH.AccessControlAllowMethods, 'GET')
156
+ headerIs(p, PH.AccessControlAllowCredentials, null)
157
+ headerIs(p, PH.AccessControlAllowHeaders, null)
158
+ })
159
+
160
+ await it(`wildcard and credentials`, async () => {
161
+ corsAllow = {
162
+ origins: ['*'],
163
+ methods: ['GET'],
164
+ credentials: true
165
+ }
166
+ const p = await preflight({
167
+ [PH.Origin]: FooDotCom,
168
+ [PH.AccessControlRequestMethod]: 'GET'
169
+ })
170
+ headerIs(p, PH.AccessControlAllowOrigin, FooDotCom)
171
+ headerIs(p, PH.AccessControlAllowMethods, 'GET')
172
+ headerIs(p, PH.AccessControlAllowCredentials, 'true')
173
+ headerIs(p, PH.AccessControlAllowHeaders, null)
174
+ })
175
+
176
+ await it(`wildcard, credentials, and headers`, async () => {
177
+ corsAllow = {
178
+ origins: ['*'],
179
+ methods: ['GET'],
180
+ credentials: true,
181
+ headers: ['content-type', 'my-header']
182
+ }
183
+ const p = await preflight({
184
+ [PH.Origin]: FooDotCom,
185
+ [PH.AccessControlRequestMethod]: 'GET'
186
+ })
187
+ headerIs(p, PH.AccessControlAllowOrigin, FooDotCom)
188
+ headerIs(p, PH.AccessControlAllowMethods, 'GET')
189
+ headerIs(p, PH.AccessControlAllowCredentials, 'true')
190
+ headerIs(p, PH.AccessControlAllowHeaders, 'content-type,my-header')
191
+ })
192
+ })
193
+
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
+ })
225
+ })
@@ -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
+ }
@@ -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))