json-rest-api 1.0.14 → 1.0.15

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.
Files changed (118) hide show
  1. package/connectors_ideas/fastify.js +66 -68
  2. package/connectors_ideas/koa-plugin.js +50 -51
  3. package/eslint.config.js +11 -2
  4. package/examples/url-override-example.js +63 -63
  5. package/index.js +13 -13
  6. package/lib/enhanced-logger.js +51 -51
  7. package/lib/error-formatter.js +61 -61
  8. package/lib/rest-api-errors.js +28 -28
  9. package/package.json +1 -1
  10. package/plugins/core/bulk-operations-plugin.js +142 -145
  11. package/plugins/core/connectors/express-plugin.js +210 -217
  12. package/plugins/core/connectors/lib/busboy-detector.js +84 -84
  13. package/plugins/core/connectors/lib/formidable-detector.js +50 -50
  14. package/plugins/core/connectors/lib/request-helpers.js +74 -74
  15. package/plugins/core/file-handling-plugin.js +125 -125
  16. package/plugins/core/lib/anyapi/anyapi-registry.js +254 -254
  17. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +287 -287
  18. package/plugins/core/lib/anyapi/schema-utils.js +101 -101
  19. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +41 -41
  20. package/plugins/core/lib/dbTablesOperations.js +134 -135
  21. package/plugins/core/lib/fractional-positioning.js +74 -76
  22. package/plugins/core/lib/querying/knex-cross-table-search.js +320 -320
  23. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +144 -144
  24. package/plugins/core/lib/querying/knex-pagination-helpers.js +164 -164
  25. package/plugins/core/lib/querying/knex-process-includes.js +50 -50
  26. package/plugins/core/lib/querying/knex-query-helpers-base.js +31 -31
  27. package/plugins/core/lib/querying/knex-query-helpers.js +451 -452
  28. package/plugins/core/lib/querying/knex-relationship-includes.js +1160 -1170
  29. package/plugins/core/lib/querying/knex-window-queries.js +89 -89
  30. package/plugins/core/lib/querying/url-helpers.js +35 -35
  31. package/plugins/core/lib/querying-writing/compile-schemas.js +97 -98
  32. package/plugins/core/lib/querying-writing/connectors-query-parser.js +31 -31
  33. package/plugins/core/lib/querying-writing/database-capabilities.js +94 -94
  34. package/plugins/core/lib/querying-writing/database-value-normalizers.js +76 -76
  35. package/plugins/core/lib/querying-writing/default-data-helpers.js +27 -27
  36. package/plugins/core/lib/querying-writing/field-utils.js +44 -42
  37. package/plugins/core/lib/querying-writing/knex-constants.js +15 -16
  38. package/plugins/core/lib/querying-writing/knex-field-helpers.js +117 -115
  39. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +41 -41
  40. package/plugins/core/lib/querying-writing/payload-validators.js +275 -275
  41. package/plugins/core/lib/querying-writing/schema-helpers.js +129 -129
  42. package/plugins/core/lib/querying-writing/scope-validations.js +143 -146
  43. package/plugins/core/lib/querying-writing/simplified-helpers.js +133 -129
  44. package/plugins/core/lib/storage/storage-adapter.js +68 -68
  45. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +31 -31
  46. package/plugins/core/lib/writing/many-to-many-manipulations.js +81 -81
  47. package/plugins/core/lib/writing/relationship-processor.js +58 -58
  48. package/plugins/core/rest-api-access.js +8 -8
  49. package/plugins/core/rest-api-anyapi-knex-plugin.js +920 -920
  50. package/plugins/core/rest-api-cors-plugin.js +170 -171
  51. package/plugins/core/rest-api-knex-plugin.js +482 -493
  52. package/plugins/core/rest-api-multihome-plugin.js +247 -251
  53. package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +5 -5
  54. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +20 -21
  55. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +44 -44
  56. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +23 -23
  57. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +15 -15
  58. package/plugins/core/rest-api-plugin-methods/add-route.js +9 -9
  59. package/plugins/core/rest-api-plugin-methods/check-permissions.js +5 -6
  60. package/plugins/core/rest-api-plugin-methods/common.js +295 -297
  61. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +35 -37
  62. package/plugins/core/rest-api-plugin-methods/delete.js +57 -59
  63. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +172 -171
  64. package/plugins/core/rest-api-plugin-methods/get-related.js +96 -96
  65. package/plugins/core/rest-api-plugin-methods/get-relationship.js +16 -18
  66. package/plugins/core/rest-api-plugin-methods/get.js +86 -86
  67. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +16 -17
  68. package/plugins/core/rest-api-plugin-methods/patch.js +63 -64
  69. package/plugins/core/rest-api-plugin-methods/post-relationship.js +54 -55
  70. package/plugins/core/rest-api-plugin-methods/post.js +61 -62
  71. package/plugins/core/rest-api-plugin-methods/put.js +86 -87
  72. package/plugins/core/rest-api-plugin-methods/query.js +88 -88
  73. package/plugins/core/rest-api-plugin-methods/release.js +5 -6
  74. package/plugins/core/rest-api-plugin.js +76 -78
  75. package/plugins/core/rest-api-positioning-plugin.js +251 -252
  76. package/plugins/core/socketio-plugin.js +240 -236
  77. package/plugins/storage/local-storage.js +103 -103
  78. package/plugins/storage/s3-storage.js +41 -42
  79. package/quickTest.js +37 -37
  80. package/scripts/debug-anyapi-review-filter.mjs +18 -18
  81. package/scripts/inspect-anyapi-descriptor-country.mjs +10 -10
  82. package/scripts/inspect-anyapi-descriptor.mjs +10 -10
  83. package/scripts/run-docs.js +112 -113
  84. package/test-debug.js +28 -28
  85. package/tests/TEST_TEMPLATE.test.js +51 -51
  86. package/tests/anyapi-basic.test.js +42 -42
  87. package/tests/anyapi-cursor-pagination.test.js +48 -48
  88. package/tests/bulk-operations.test.js +208 -208
  89. package/tests/computed-fields.test.js +204 -204
  90. package/tests/cors.test.js +412 -413
  91. package/tests/curl.test.js +275 -276
  92. package/tests/custom-idproperty-relationships.test.js +266 -266
  93. package/tests/field-getters.test.js +132 -132
  94. package/tests/field-setters.test.js +110 -110
  95. package/tests/fixtures/api-configs.js +699 -699
  96. package/tests/helpers/storage-mode.js +32 -32
  97. package/tests/helpers/test-utils.js +76 -76
  98. package/tests/include-depth-validation.test.js +76 -76
  99. package/tests/includes.test.js +145 -145
  100. package/tests/multihome.test.js +175 -175
  101. package/tests/nested-includes.test.js +255 -255
  102. package/tests/pagination-cursor-multifield.test.js +164 -164
  103. package/tests/pagination-enhanced.test.js +225 -225
  104. package/tests/polymorphic-relationships.test.js +96 -96
  105. package/tests/polymorphic-simple.test.js +44 -44
  106. package/tests/positioning-advanced.test.js +226 -226
  107. package/tests/positioning-extended.test.js +399 -399
  108. package/tests/positioning-multifilter.test.js +204 -204
  109. package/tests/positioning.test.js +266 -266
  110. package/tests/queries.test.js +184 -184
  111. package/tests/query-limits.test.js +154 -155
  112. package/tests/relationship-endpoints.test.js +127 -127
  113. package/tests/relationships.test.js +178 -179
  114. package/tests/rest-api-access.test.js +86 -86
  115. package/tests/rest-api.test.js +103 -103
  116. package/tests/searchschema-merge.test.js +93 -93
  117. package/tests/socketio.test.js +326 -326
  118. package/tests/virtual-fields.test.js +164 -164
@@ -1,16 +1,16 @@
1
- import { describe, it, before, after, beforeEach } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import knexLib from 'knex';
4
- import express from 'express';
5
- import request from 'supertest';
6
- import {
7
- validateJsonApiStructure,
8
- cleanTables,
1
+ import { describe, it, before, after, beforeEach } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import knexLib from 'knex'
4
+ import express from 'express'
5
+ import request from 'supertest'
6
+ import {
7
+ validateJsonApiStructure,
8
+ cleanTables,
9
9
  createJsonApiDocument,
10
10
  assertResourceAttributes
11
- } from './helpers/test-utils.js';
12
- import { createBasicApi } from './fixtures/api-configs.js';
13
- import { CorsPlugin } from '../plugins/core/rest-api-cors-plugin.js';
11
+ } from './helpers/test-utils.js'
12
+ import { createBasicApi } from './fixtures/api-configs.js'
13
+ import { CorsPlugin } from '../plugins/core/rest-api-cors-plugin.js'
14
14
 
15
15
  // Create Knex instance for tests - always use SQLite in-memory
16
16
  const knex = knexLib({
@@ -19,37 +19,37 @@ const knex = knexLib({
19
19
  filename: ':memory:'
20
20
  },
21
21
  useNullAsDefault: true
22
- });
22
+ })
23
23
 
24
24
  // API instance and Express app that persist across tests
25
- let api;
26
- let app;
25
+ let api
26
+ let app
27
27
 
28
28
  describe('CORS Plugin Tests', { timeout: 30000 }, () => {
29
29
  describe('CORS with baseUrl', () => {
30
- let baseUrlApi;
31
- let baseUrlApp;
32
-
30
+ let baseUrlApi
31
+ let baseUrlApp
32
+
33
33
  before(async () => {
34
34
  // Create a new API instance with Express configured with a baseUrl
35
- baseUrlApp = express();
35
+ baseUrlApp = express()
36
36
  // Create API with different name to avoid registry conflict
37
- const Api = (await import('hooked-api')).Api;
38
- baseUrlApi = new Api({
37
+ const Api = (await import('hooked-api')).Api
38
+ baseUrlApi = new Api({
39
39
  name: 'cors-baseurl-test-api',
40
40
  log: { level: 'info' }
41
- });
42
-
41
+ })
42
+
43
43
  // Install plugins
44
44
  await baseUrlApi.use((await import('../plugins/core/rest-api-plugin.js')).RestApiPlugin, {
45
45
  simplifiedApi: false
46
- });
47
- await baseUrlApi.use((await import('../plugins/core/rest-api-knex-plugin.js')).RestApiKnexPlugin, { knex });
46
+ })
47
+ await baseUrlApi.use((await import('../plugins/core/rest-api-knex-plugin.js')).RestApiKnexPlugin, { knex })
48
48
  await baseUrlApi.use((await import('../plugins/core/connectors/express-plugin.js')).ExpressPlugin, {
49
49
  app: baseUrlApp,
50
50
  mountPath: '/api'
51
- });
52
-
51
+ })
52
+
53
53
  // Add basic scope
54
54
  await baseUrlApi.addScope('countries', {
55
55
  restApi: {
@@ -60,116 +60,115 @@ describe('CORS Plugin Tests', { timeout: 30000 }, () => {
60
60
  }
61
61
  }
62
62
  }
63
- });
64
-
63
+ })
64
+
65
65
  // Install CORS plugin
66
- console.log('[TEST] vars.transport before CORS install:', baseUrlApi.vars.transport);
67
-
66
+ console.log('[TEST] vars.transport before CORS install:', baseUrlApi.vars.transport)
67
+
68
68
  try {
69
69
  await baseUrlApi.use(CorsPlugin, {
70
70
  origin: '*',
71
71
  credentials: true
72
- });
72
+ })
73
73
  } catch (error) {
74
- console.log('[TEST] CORS installation error:', error.message);
75
- console.log('[TEST] vars.transport.matchAll:', baseUrlApi.vars.transport?.matchAll);
76
- console.log('[TEST] mountPath from transport:', baseUrlApi.vars.transport?.mountPath);
77
- console.log('[TEST] Full wildcard path would be:', '' + (baseUrlApi.vars.transport?.matchAll || '*'));
78
- throw error;
74
+ console.log('[TEST] CORS installation error:', error.message)
75
+ console.log('[TEST] vars.transport.matchAll:', baseUrlApi.vars.transport?.matchAll)
76
+ console.log('[TEST] mountPath from transport:', baseUrlApi.vars.transport?.mountPath)
77
+ console.log('[TEST] Full wildcard path would be:', '' + (baseUrlApi.vars.transport?.matchAll || '*'))
78
+ throw error
79
79
  }
80
-
80
+
81
81
  // Mount the routes
82
- baseUrlApi.http.express.mount(baseUrlApp);
83
- });
84
-
82
+ baseUrlApi.http.express.mount(baseUrlApp)
83
+ })
84
+
85
85
  it('should handle OPTIONS preflight with baseUrl', async () => {
86
86
  const response = await request(baseUrlApp)
87
87
  .options('/api/countries')
88
88
  .set('Origin', 'https://example.com')
89
89
  .set('Access-Control-Request-Method', 'POST')
90
- .set('Access-Control-Request-Headers', 'Content-Type');
91
-
90
+ .set('Access-Control-Request-Headers', 'Content-Type')
91
+
92
92
  console.log('[TEST] BaseUrl OPTIONS response:', {
93
93
  status: response.status,
94
94
  headers: response.headers,
95
95
  body: response.body
96
- });
97
-
98
- assert.equal(response.status, 204);
99
- assert.equal(response.headers['access-control-allow-origin'], 'https://example.com');
100
- assert.equal(response.headers['access-control-allow-credentials'], 'true');
101
- });
102
-
96
+ })
97
+
98
+ assert.equal(response.status, 204)
99
+ assert.equal(response.headers['access-control-allow-origin'], 'https://example.com')
100
+ assert.equal(response.headers['access-control-allow-credentials'], 'true')
101
+ })
102
+
103
103
  it('should handle GET requests with baseUrl', async () => {
104
104
  // Just test the GET endpoint without creating data first
105
105
  // This tests CORS headers on the response regardless of data
106
106
  const response = await request(baseUrlApp)
107
107
  .get('/api/countries')
108
108
  .set('Origin', 'https://example.com')
109
- .set('Accept', 'application/vnd.api+json');
110
-
109
+ .set('Accept', 'application/vnd.api+json')
110
+
111
111
  console.log('[TEST] BaseUrl GET response:', {
112
112
  status: response.status,
113
113
  headers: response.headers
114
- });
115
-
114
+ })
115
+
116
116
  // We might get 500 due to missing table, but CORS headers should still be present
117
- assert(response.status === 200 || response.status === 500, `Expected 200 or 500, got ${response.status}`);
118
- assert.equal(response.headers['access-control-allow-origin'], 'https://example.com');
119
- assert.equal(response.headers['access-control-allow-credentials'], 'true');
120
- assert.equal(response.headers['vary'], 'Origin');
121
- });
122
-
117
+ assert(response.status === 200 || response.status === 500, `Expected 200 or 500, got ${response.status}`)
118
+ assert.equal(response.headers['access-control-allow-origin'], 'https://example.com')
119
+ assert.equal(response.headers['access-control-allow-credentials'], 'true')
120
+ assert.equal(response.headers['vary'], 'Origin')
121
+ })
122
+
123
123
  it('should handle wildcard OPTIONS route with baseUrl', async () => {
124
124
  // Test a non-existent endpoint to check if wildcard OPTIONS still works
125
125
  const response = await request(baseUrlApp)
126
126
  .options('/api/non-existent-endpoint')
127
127
  .set('Origin', 'https://example.com')
128
- .set('Access-Control-Request-Method', 'POST');
129
-
128
+ .set('Access-Control-Request-Method', 'POST')
129
+
130
130
  console.log('[TEST] BaseUrl wildcard OPTIONS response:', {
131
131
  status: response.status,
132
132
  headers: response.headers
133
- });
134
-
135
- // This will tell us if the wildcard '*' route is working with baseUrl
136
- assert.equal(response.status, 204);
137
- assert.equal(response.headers['access-control-allow-origin'], 'https://example.com');
138
- });
139
- });
133
+ })
140
134
 
135
+ // This will tell us if the wildcard '*' route is working with baseUrl
136
+ assert.equal(response.status, 204)
137
+ assert.equal(response.headers['access-control-allow-origin'], 'https://example.com')
138
+ })
139
+ })
141
140
 
142
141
  // IMPORTANT: before() runs ONCE for the entire test suite
143
142
  before(async () => {
144
143
  // Initialize API once with Express
145
- app = express();
144
+ app = express()
146
145
  api = await createBasicApi(knex, {
147
146
  includeExpress: true,
148
147
  express: { app }
149
- });
150
-
148
+ })
149
+
151
150
  // Install CORS plugin
152
151
  await api.use(CorsPlugin, {
153
-
154
- origin: '*',
155
- credentials: true,
156
- methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
157
- allowedHeaders: ['Content-Type', 'Authorization', 'X-Custom-Header'],
158
- exposedHeaders: ['X-Total-Count', 'X-Page-Count', 'Link'],
159
- maxAge: 86400
160
- }
161
- );
162
-
152
+
153
+ origin: '*',
154
+ credentials: true,
155
+ methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
156
+ allowedHeaders: ['Content-Type', 'Authorization', 'X-Custom-Header'],
157
+ exposedHeaders: ['X-Total-Count', 'X-Page-Count', 'Link'],
158
+ maxAge: 86400
159
+ }
160
+ )
161
+
163
162
  // Mount the routes on the Express app
164
- api.http.express.mount(app);
165
- });
166
-
163
+ api.http.express.mount(app)
164
+ })
165
+
167
166
  // IMPORTANT: after() cleans up resources
168
167
  after(async () => {
169
168
  // Always destroy knex connection to allow tests to exit
170
- await knex.destroy();
171
- });
172
-
169
+ await knex.destroy()
170
+ })
171
+
173
172
  // IMPORTANT: beforeEach() cleans data but does NOT recreate API
174
173
  beforeEach(async () => {
175
174
  // Clean all tables - list ALL tables your tests use
@@ -179,141 +178,141 @@ describe('CORS Plugin Tests', { timeout: 30000 }, () => {
179
178
  'basic_authors',
180
179
  'basic_books',
181
180
  'basic_book_authors'
182
- ]);
183
- });
181
+ ])
182
+ })
184
183
 
185
184
  describe('CORS Headers for Regular Requests', () => {
186
- it('should add CORS headers for GET requests', async () => {
185
+ it('should add CORS headers for GET requests', async () => {
187
186
  // Create test data
188
187
  const doc = createJsonApiDocument('countries', {
189
188
  name: 'Test Country',
190
189
  code: 'TC'
191
- });
192
-
190
+ })
191
+
193
192
  const createResult = await api.resources.countries.post({
194
193
  inputRecord: doc,
195
194
  simplified: false
196
- });
197
-
195
+ })
196
+
198
197
  // Make HTTP request with origin header
199
198
  const response = await request(app)
200
199
  .get('/api/countries')
201
200
  .set('Origin', 'https://example.com')
202
- .set('Accept', 'application/vnd.api+json');
203
-
204
- assert.equal(response.status, 200);
201
+ .set('Accept', 'application/vnd.api+json')
202
+
203
+ assert.equal(response.status, 200)
205
204
  // When credentials are true, the origin must be specific, not wildcard
206
- assert.equal(response.headers['access-control-allow-origin'], 'https://example.com');
207
- assert.equal(response.headers['access-control-allow-credentials'], 'true');
208
- assert.equal(response.headers['access-control-expose-headers'], 'X-Total-Count, X-Page-Count, Link');
205
+ assert.equal(response.headers['access-control-allow-origin'], 'https://example.com')
206
+ assert.equal(response.headers['access-control-allow-credentials'], 'true')
207
+ assert.equal(response.headers['access-control-expose-headers'], 'X-Total-Count, X-Page-Count, Link')
209
208
  // Vary header should be set when specific origin is returned
210
- assert.equal(response.headers['vary'], 'Origin');
211
- });
212
-
213
- it('should add CORS headers for POST requests', async () => {
209
+ assert.equal(response.headers['vary'], 'Origin')
210
+ })
211
+
212
+ it('should add CORS headers for POST requests', async () => {
214
213
  const doc = createJsonApiDocument('countries', {
215
214
  name: 'New Country',
216
215
  code: 'NC'
217
- });
218
-
216
+ })
217
+
219
218
  const response = await request(app)
220
219
  .post('/api/countries')
221
220
  .send(doc)
222
221
  .set('Origin', 'https://example.com')
223
222
  .set('Content-Type', 'application/vnd.api+json')
224
- .set('Accept', 'application/vnd.api+json');
225
-
226
- assert.equal(response.status, 201);
223
+ .set('Accept', 'application/vnd.api+json')
224
+
225
+ assert.equal(response.status, 201)
227
226
  // When credentials are true, the origin must be specific, not wildcard
228
- assert.equal(response.headers['access-control-allow-origin'], 'https://example.com');
229
- assert.equal(response.headers['access-control-allow-credentials'], 'true');
230
- assert.equal(response.headers['access-control-expose-headers'], 'X-Total-Count, X-Page-Count, Link');
227
+ assert.equal(response.headers['access-control-allow-origin'], 'https://example.com')
228
+ assert.equal(response.headers['access-control-allow-credentials'], 'true')
229
+ assert.equal(response.headers['access-control-expose-headers'], 'X-Total-Count, X-Page-Count, Link')
231
230
  // Vary header should be set when specific origin is returned
232
- assert.equal(response.headers['vary'], 'Origin');
233
- });
234
-
235
- it('should handle requests without Origin header', async () => {
231
+ assert.equal(response.headers['vary'], 'Origin')
232
+ })
233
+
234
+ it('should handle requests without Origin header', async () => {
236
235
  // Same-origin request (no Origin header)
237
236
  const response = await request(app)
238
237
  .get('/api/countries')
239
- .set('Accept', 'application/vnd.api+json');
240
-
241
- assert.equal(response.status, 200);
238
+ .set('Accept', 'application/vnd.api+json')
239
+
240
+ assert.equal(response.status, 200)
242
241
  // Should not set CORS headers for same-origin requests
243
- assert.equal(response.headers['access-control-allow-origin'], undefined);
244
- });
245
- });
242
+ assert.equal(response.headers['access-control-allow-origin'], undefined)
243
+ })
244
+ })
246
245
 
247
246
  describe('CORS Preflight Requests', () => {
248
- it('should handle OPTIONS preflight requests', async () => {
247
+ it('should handle OPTIONS preflight requests', async () => {
249
248
  const response = await request(app)
250
249
  .options('/api/countries')
251
250
  .set('Origin', 'https://example.com')
252
251
  .set('Access-Control-Request-Method', 'POST')
253
- .set('Access-Control-Request-Headers', 'Content-Type');
254
-
255
- assert.equal(response.status, 204);
252
+ .set('Access-Control-Request-Headers', 'Content-Type')
253
+
254
+ assert.equal(response.status, 204)
256
255
  // When credentials are true, the origin must be specific, not wildcard
257
- assert.equal(response.headers['access-control-allow-origin'], 'https://example.com');
258
- assert.equal(response.headers['access-control-allow-credentials'], 'true');
259
- assert.equal(response.headers['access-control-allow-methods'], 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
260
- assert.equal(response.headers['vary'], 'Origin');
261
- assert.equal(response.headers['access-control-allow-headers'], 'Content-Type, Authorization, X-Custom-Header');
262
- assert.equal(response.headers['access-control-max-age'], '86400');
263
- assert.equal(response.text, ''); // Empty body
264
- });
265
-
266
- it('should handle preflight for custom headers', async () => {
256
+ assert.equal(response.headers['access-control-allow-origin'], 'https://example.com')
257
+ assert.equal(response.headers['access-control-allow-credentials'], 'true')
258
+ assert.equal(response.headers['access-control-allow-methods'], 'GET, POST, PUT, PATCH, DELETE, OPTIONS')
259
+ assert.equal(response.headers['vary'], 'Origin')
260
+ assert.equal(response.headers['access-control-allow-headers'], 'Content-Type, Authorization, X-Custom-Header')
261
+ assert.equal(response.headers['access-control-max-age'], '86400')
262
+ assert.equal(response.text, '') // Empty body
263
+ })
264
+
265
+ it('should handle preflight for custom headers', async () => {
267
266
  const response = await request(app)
268
267
  .options('/api/countries/1')
269
268
  .set('Origin', 'https://example.com')
270
269
  .set('Access-Control-Request-Method', 'PATCH')
271
- .set('Access-Control-Request-Headers', 'X-Custom-Header');
272
-
273
- assert.equal(response.status, 204);
274
- assert(response.headers['access-control-allow-headers'].includes('X-Custom-Header'));
275
- });
276
- });
270
+ .set('Access-Control-Request-Headers', 'X-Custom-Header')
271
+
272
+ assert.equal(response.status, 204)
273
+ assert(response.headers['access-control-allow-headers'].includes('X-Custom-Header'))
274
+ })
275
+ })
277
276
 
278
277
  describe('CORS with Specific Origins', () => {
279
- it('should work with wildcard origin', async () => {
278
+ it('should work with wildcard origin', async () => {
280
279
  // Current configuration uses wildcard
281
280
  const response = await request(app)
282
281
  .get('/api/countries')
283
282
  .set('Origin', 'https://any-origin.com')
284
- .set('Accept', 'application/vnd.api+json');
285
-
286
- assert.equal(response.status, 200);
283
+ .set('Accept', 'application/vnd.api+json')
284
+
285
+ assert.equal(response.status, 200)
287
286
  // When credentials are true and origin is sent, specific origin is returned
288
- assert.equal(response.headers['access-control-allow-origin'], 'https://any-origin.com');
289
- assert.equal(response.headers['vary'], 'Origin');
290
- });
291
- });
287
+ assert.equal(response.headers['access-control-allow-origin'], 'https://any-origin.com')
288
+ assert.equal(response.headers['vary'], 'Origin')
289
+ })
290
+ })
292
291
 
293
292
  describe('CORS Runtime Configuration', () => {
294
293
  // Tests for dynamic configuration have been removed
295
294
  // The dynamic configuration API is still available for other tests to use
296
- });
295
+ })
297
296
 
298
297
  describe('CORS with Different HTTP Methods', () => {
299
- let testCountryId;
300
-
298
+ let testCountryId
299
+
301
300
  beforeEach(async () => {
302
301
  // Create test data for update/delete operations
303
302
  const doc = createJsonApiDocument('countries', {
304
303
  name: 'Update Test Country',
305
304
  code: 'UT'
306
- });
307
-
305
+ })
306
+
308
307
  const result = await api.resources.countries.post({
309
308
  inputRecord: doc,
310
309
  simplified: false
311
- });
312
-
313
- testCountryId = result.data.id;
314
- });
315
-
316
- it('should handle PATCH requests with CORS', async () => {
310
+ })
311
+
312
+ testCountryId = result.data.id
313
+ })
314
+
315
+ it('should handle PATCH requests with CORS', async () => {
317
316
  const patchDoc = {
318
317
  data: {
319
318
  type: 'countries',
@@ -322,34 +321,34 @@ describe('CORS Plugin Tests', { timeout: 30000 }, () => {
322
321
  name: 'Updated Country Name'
323
322
  }
324
323
  }
325
- };
326
-
324
+ }
325
+
327
326
  const response = await request(app)
328
327
  .patch(`/api/countries/${testCountryId}`)
329
328
  .send(patchDoc)
330
329
  .set('Origin', 'https://example.com')
331
330
  .set('Content-Type', 'application/vnd.api+json')
332
- .set('Accept', 'application/vnd.api+json');
333
-
334
- assert.equal(response.status, 200);
331
+ .set('Accept', 'application/vnd.api+json')
332
+
333
+ assert.equal(response.status, 200)
335
334
  // When credentials are true and origin is sent, specific origin is returned
336
- assert.equal(response.headers['access-control-allow-origin'], 'https://example.com');
337
- assert.equal(response.headers['vary'], 'Origin');
338
- });
339
-
340
- it('should handle DELETE requests with CORS', async () => {
335
+ assert.equal(response.headers['access-control-allow-origin'], 'https://example.com')
336
+ assert.equal(response.headers['vary'], 'Origin')
337
+ })
338
+
339
+ it('should handle DELETE requests with CORS', async () => {
341
340
  const response = await request(app)
342
341
  .delete(`/api/countries/${testCountryId}`)
343
342
  .set('Origin', 'https://example.com')
344
- .set('Accept', 'application/vnd.api+json');
345
-
346
- assert.equal(response.status, 204);
343
+ .set('Accept', 'application/vnd.api+json')
344
+
345
+ assert.equal(response.status, 204)
347
346
  // When credentials are true and origin is sent, specific origin is returned
348
- assert.equal(response.headers['access-control-allow-origin'], 'https://example.com');
349
- assert.equal(response.headers['vary'], 'Origin');
350
- });
347
+ assert.equal(response.headers['access-control-allow-origin'], 'https://example.com')
348
+ assert.equal(response.headers['vary'], 'Origin')
349
+ })
351
350
 
352
- it('should handle PUT requests with CORS', async () => {
351
+ it('should handle PUT requests with CORS', async () => {
353
352
  const putDoc = {
354
353
  data: {
355
354
  type: 'countries',
@@ -359,364 +358,364 @@ describe('CORS Plugin Tests', { timeout: 30000 }, () => {
359
358
  code: 'RC'
360
359
  }
361
360
  }
362
- };
361
+ }
363
362
 
364
363
  const response = await request(app)
365
364
  .put(`/api/countries/${testCountryId}`)
366
365
  .set('Origin', 'https://example.com')
367
366
  .set('Accept', 'application/vnd.api+json')
368
367
  .set('Content-Type', 'application/vnd.api+json')
369
- .send(putDoc);
370
-
371
- assert.equal(response.status, 204);
372
- assert.equal(response.headers['access-control-allow-origin'], 'https://example.com');
373
- assert.equal(response.headers['vary'], 'Origin');
374
- assert.equal(response.headers['access-control-expose-headers'], 'X-Total-Count, X-Page-Count, Link');
375
- });
376
- });
368
+ .send(putDoc)
369
+
370
+ assert.equal(response.status, 204)
371
+ assert.equal(response.headers['access-control-allow-origin'], 'https://example.com')
372
+ assert.equal(response.headers['vary'], 'Origin')
373
+ assert.equal(response.headers['access-control-expose-headers'], 'X-Total-Count, X-Page-Count, Link')
374
+ })
375
+ })
377
376
 
378
377
  describe('CORS with Array and Regex Origins', () => {
379
378
  it('should support array of allowed origins', async () => {
380
379
  // Create a new API instance with array origin configuration
381
- const arrayOriginApp = express();
382
- const arrayOriginApi = await createBasicApi(knex, {
380
+ const arrayOriginApp = express()
381
+ const arrayOriginApi = await createBasicApi(knex, {
383
382
  apiName: 'cors-array-origins-test',
384
383
  tablePrefix: 'cors_array',
385
384
  includeExpress: true,
386
385
  express: { app: arrayOriginApp }
387
- });
388
-
386
+ })
387
+
389
388
  await arrayOriginApi.use(CorsPlugin, {
390
389
  origin: ['https://app1.com', 'https://app2.com', 'https://app3.com'],
391
390
  credentials: true
392
- });
393
-
391
+ })
392
+
394
393
  // Mount routes after CORS plugin is installed
395
- arrayOriginApi.http.express.mount(arrayOriginApp);
396
-
394
+ arrayOriginApi.http.express.mount(arrayOriginApp)
395
+
397
396
  // Test allowed origin
398
397
  const response1 = await request(arrayOriginApp)
399
398
  .get('/api/countries')
400
399
  .set('Origin', 'https://app2.com')
401
- .set('Accept', 'application/vnd.api+json');
402
-
403
- assert.equal(response1.status, 200);
404
- assert.equal(response1.headers['access-control-allow-origin'], 'https://app2.com');
405
-
400
+ .set('Accept', 'application/vnd.api+json')
401
+
402
+ assert.equal(response1.status, 200)
403
+ assert.equal(response1.headers['access-control-allow-origin'], 'https://app2.com')
404
+
406
405
  // Test disallowed origin
407
406
  const response2 = await request(arrayOriginApp)
408
407
  .get('/api/countries')
409
408
  .set('Origin', 'https://app4.com')
410
- .set('Accept', 'application/vnd.api+json');
411
-
412
- assert.equal(response2.status, 200);
413
- assert.equal(response2.headers['access-control-allow-origin'], undefined);
414
- });
415
-
409
+ .set('Accept', 'application/vnd.api+json')
410
+
411
+ assert.equal(response2.status, 200)
412
+ assert.equal(response2.headers['access-control-allow-origin'], undefined)
413
+ })
414
+
416
415
  it('should support regex origin matching', async () => {
417
416
  // Create a new API instance with regex origin configuration
418
- const regexOriginApp = express();
419
- const regexOriginApi = await createBasicApi(knex, {
417
+ const regexOriginApp = express()
418
+ const regexOriginApi = await createBasicApi(knex, {
420
419
  apiName: 'cors-regex-origins-test',
421
420
  tablePrefix: 'cors_regex',
422
421
  includeExpress: true,
423
422
  express: { app: regexOriginApp }
424
- });
425
-
423
+ })
424
+
426
425
  await regexOriginApi.use(CorsPlugin, {
427
426
  origin: /^https:\/\/.*\.example\.com$/,
428
427
  credentials: true
429
- });
430
-
428
+ })
429
+
431
430
  // Mount routes after CORS plugin is installed
432
- regexOriginApi.http.express.mount(regexOriginApp);
433
-
431
+ regexOriginApi.http.express.mount(regexOriginApp)
432
+
434
433
  // Test matching subdomain
435
434
  const response1 = await request(regexOriginApp)
436
435
  .get('/api/countries')
437
436
  .set('Origin', 'https://app.example.com')
438
- .set('Accept', 'application/vnd.api+json');
439
-
440
- assert.equal(response1.status, 200);
441
- assert.equal(response1.headers['access-control-allow-origin'], 'https://app.example.com');
442
-
437
+ .set('Accept', 'application/vnd.api+json')
438
+
439
+ assert.equal(response1.status, 200)
440
+ assert.equal(response1.headers['access-control-allow-origin'], 'https://app.example.com')
441
+
443
442
  // Test another matching subdomain
444
443
  const response2 = await request(regexOriginApp)
445
444
  .get('/api/countries')
446
445
  .set('Origin', 'https://api.example.com')
447
- .set('Accept', 'application/vnd.api+json');
448
-
449
- assert.equal(response2.status, 200);
450
- assert.equal(response2.headers['access-control-allow-origin'], 'https://api.example.com');
451
-
446
+ .set('Accept', 'application/vnd.api+json')
447
+
448
+ assert.equal(response2.status, 200)
449
+ assert.equal(response2.headers['access-control-allow-origin'], 'https://api.example.com')
450
+
452
451
  // Test non-matching domain
453
452
  const response3 = await request(regexOriginApp)
454
453
  .get('/api/countries')
455
454
  .set('Origin', 'https://example.org')
456
- .set('Accept', 'application/vnd.api+json');
457
-
458
- assert.equal(response3.status, 200);
459
- assert.equal(response3.headers['access-control-allow-origin'], undefined);
460
- });
461
-
455
+ .set('Accept', 'application/vnd.api+json')
456
+
457
+ assert.equal(response3.status, 200)
458
+ assert.equal(response3.headers['access-control-allow-origin'], undefined)
459
+ })
460
+
462
461
  it('should support function-based origin validation', async () => {
463
462
  // Create a new API instance with function origin configuration
464
- const functionOriginApp = express();
465
- const functionOriginApi = await createBasicApi(knex, {
463
+ const functionOriginApp = express()
464
+ const functionOriginApi = await createBasicApi(knex, {
466
465
  apiName: 'cors-function-origins-test',
467
466
  tablePrefix: 'cors_func',
468
467
  includeExpress: true,
469
468
  express: { app: functionOriginApp }
470
- });
471
-
472
- const allowedOrigins = new Set(['https://dynamic1.com', 'https://dynamic2.com']);
473
-
469
+ })
470
+
471
+ const allowedOrigins = new Set(['https://dynamic1.com', 'https://dynamic2.com'])
472
+
474
473
  await functionOriginApi.use(CorsPlugin, {
475
474
  origin: (origin) => allowedOrigins.has(origin),
476
475
  credentials: true
477
- });
478
-
476
+ })
477
+
479
478
  // Mount routes after CORS plugin is installed
480
- functionOriginApi.http.express.mount(functionOriginApp);
481
-
479
+ functionOriginApi.http.express.mount(functionOriginApp)
480
+
482
481
  // Test allowed origin
483
482
  const response1 = await request(functionOriginApp)
484
483
  .get('/api/countries')
485
484
  .set('Origin', 'https://dynamic1.com')
486
- .set('Accept', 'application/vnd.api+json');
487
-
488
- assert.equal(response1.status, 200);
489
- assert.equal(response1.headers['access-control-allow-origin'], 'https://dynamic1.com');
490
-
485
+ .set('Accept', 'application/vnd.api+json')
486
+
487
+ assert.equal(response1.status, 200)
488
+ assert.equal(response1.headers['access-control-allow-origin'], 'https://dynamic1.com')
489
+
491
490
  // Test disallowed origin
492
491
  const response2 = await request(functionOriginApp)
493
492
  .get('/api/countries')
494
493
  .set('Origin', 'https://dynamic3.com')
495
- .set('Accept', 'application/vnd.api+json');
496
-
497
- assert.equal(response2.status, 200);
498
- assert.equal(response2.headers['access-control-allow-origin'], undefined);
499
- });
500
- });
494
+ .set('Accept', 'application/vnd.api+json')
495
+
496
+ assert.equal(response2.status, 200)
497
+ assert.equal(response2.headers['access-control-allow-origin'], undefined)
498
+ })
499
+ })
501
500
 
502
501
  describe('CORS Error Handling', () => {
503
- it('should reject preflight from disallowed origin', async () => {
502
+ it('should reject preflight from disallowed origin', async () => {
504
503
  // Create a new API instance with specific allowed origin
505
- const restrictedApp = express();
506
- const restrictedApi = await createBasicApi(knex, {
504
+ const restrictedApp = express()
505
+ const restrictedApi = await createBasicApi(knex, {
507
506
  apiName: 'cors-restricted-test',
508
507
  tablePrefix: 'cors_restrict',
509
508
  includeExpress: true,
510
509
  express: { app: restrictedApp }
511
- });
512
-
510
+ })
511
+
513
512
  await restrictedApi.use(CorsPlugin, {
514
513
  origin: 'https://allowed-only.com',
515
514
  credentials: true
516
- });
517
-
515
+ })
516
+
518
517
  // Mount routes after CORS plugin is installed
519
- restrictedApi.http.express.mount(restrictedApp);
520
-
518
+ restrictedApi.http.express.mount(restrictedApp)
519
+
521
520
  const response = await request(restrictedApp)
522
521
  .options('/api/countries')
523
522
  .set('Origin', 'https://not-allowed.com')
524
- .set('Access-Control-Request-Method', 'POST');
525
-
526
- assert.equal(response.status, 403);
527
- assert(response.body.error.includes('CORS origin not allowed'));
528
- assert.equal(response.headers['access-control-allow-origin'], undefined);
529
- });
530
-
531
- it('should handle lowercase HTTP methods', async () => {
523
+ .set('Access-Control-Request-Method', 'POST')
524
+
525
+ assert.equal(response.status, 403)
526
+ assert(response.body.error.includes('CORS origin not allowed'))
527
+ assert.equal(response.headers['access-control-allow-origin'], undefined)
528
+ })
529
+
530
+ it('should handle lowercase HTTP methods', async () => {
532
531
  // Some clients might send lowercase methods
533
532
  const response = await request(app)
534
533
  .options('/api/countries')
535
534
  .set('Origin', 'https://example.com')
536
- .set('Access-Control-Request-Method', 'post'); // lowercase
537
-
538
- assert.equal(response.status, 204);
535
+ .set('Access-Control-Request-Method', 'post') // lowercase
536
+
537
+ assert.equal(response.status, 204)
539
538
  // When credentials are true and origin is sent, specific origin is returned
540
- assert.equal(response.headers['access-control-allow-origin'], 'https://example.com');
541
- assert.equal(response.headers['vary'], 'Origin');
542
- });
543
- });
539
+ assert.equal(response.headers['access-control-allow-origin'], 'https://example.com')
540
+ assert.equal(response.headers['vary'], 'Origin')
541
+ })
542
+ })
544
543
 
545
544
  describe('CORS Integration with REST API', () => {
546
- it('should handle CORS for error responses', async () => {
545
+ it('should handle CORS for error responses', async () => {
547
546
  // Try to get non-existent resource
548
547
  const response = await request(app)
549
548
  .get('/api/countries/999999')
550
549
  .set('Origin', 'https://example.com')
551
- .set('Accept', 'application/vnd.api+json');
552
-
553
- assert.equal(response.status, 404);
550
+ .set('Accept', 'application/vnd.api+json')
551
+
552
+ assert.equal(response.status, 404)
554
553
  // CORS headers should still be set for error responses
555
- assert.equal(response.headers['access-control-allow-origin'], 'https://example.com');
556
- assert.equal(response.headers['access-control-allow-credentials'], 'true');
557
- assert.equal(response.headers['vary'], 'Origin');
558
- });
559
-
560
- it('should handle CORS for validation errors', async () => {
554
+ assert.equal(response.headers['access-control-allow-origin'], 'https://example.com')
555
+ assert.equal(response.headers['access-control-allow-credentials'], 'true')
556
+ assert.equal(response.headers['vary'], 'Origin')
557
+ })
558
+
559
+ it('should handle CORS for validation errors', async () => {
561
560
  // Send invalid data
562
561
  const invalidDoc = createJsonApiDocument('countries', {
563
562
  // Missing required 'name' field
564
563
  code: 'XX'
565
- });
566
-
564
+ })
565
+
567
566
  const response = await request(app)
568
567
  .post('/api/countries')
569
568
  .send(invalidDoc)
570
569
  .set('Origin', 'https://example.com')
571
570
  .set('Content-Type', 'application/vnd.api+json')
572
- .set('Accept', 'application/vnd.api+json');
573
-
574
- assert.equal(response.status, 422);
571
+ .set('Accept', 'application/vnd.api+json')
572
+
573
+ assert.equal(response.status, 422)
575
574
  // CORS headers should be set for validation errors
576
- assert.equal(response.headers['access-control-allow-origin'], 'https://example.com');
577
- assert.equal(response.headers['vary'], 'Origin');
578
- });
579
- });
575
+ assert.equal(response.headers['access-control-allow-origin'], 'https://example.com')
576
+ assert.equal(response.headers['vary'], 'Origin')
577
+ })
578
+ })
580
579
 
581
580
  describe('CORS Configuration Options', () => {
582
581
  it('should respect custom max-age configuration', async () => {
583
582
  // Create API with custom max-age
584
- const maxAgeApp = express();
583
+ const maxAgeApp = express()
585
584
  const maxAgeApi = await createBasicApi(knex, {
586
585
  apiName: 'cors-maxage-test',
587
586
  tablePrefix: 'cors_maxage',
588
587
  includeExpress: true,
589
588
  express: { app: maxAgeApp }
590
- });
591
-
589
+ })
590
+
592
591
  await maxAgeApi.use(CorsPlugin, {
593
592
  origin: '*',
594
593
  credentials: false,
595
594
  maxAge: 3600 // 1 hour instead of default 24 hours
596
- });
597
-
598
- maxAgeApi.http.express.mount(maxAgeApp);
599
-
595
+ })
596
+
597
+ maxAgeApi.http.express.mount(maxAgeApp)
598
+
600
599
  const response = await request(maxAgeApp)
601
600
  .options('/api/countries')
602
601
  .set('Origin', 'https://example.com')
603
- .set('Access-Control-Request-Method', 'POST');
604
-
605
- assert.equal(response.status, 204);
606
- assert.equal(response.headers['access-control-max-age'], '3600');
607
- });
608
-
602
+ .set('Access-Control-Request-Method', 'POST')
603
+
604
+ assert.equal(response.status, 204)
605
+ assert.equal(response.headers['access-control-max-age'], '3600')
606
+ })
607
+
609
608
  it('should respect custom exposed headers configuration', async () => {
610
609
  // Create API with custom exposed headers
611
- const exposedApp = express();
610
+ const exposedApp = express()
612
611
  const exposedApi = await createBasicApi(knex, {
613
612
  apiName: 'cors-exposed-test',
614
613
  tablePrefix: 'cors_exposed',
615
614
  includeExpress: true,
616
615
  express: { app: exposedApp }
617
- });
618
-
616
+ })
617
+
619
618
  await exposedApi.use(CorsPlugin, {
620
619
  origin: '*',
621
620
  credentials: false,
622
621
  exposedHeaders: ['X-Custom-Header', 'X-Rate-Limit', 'X-Request-Id']
623
- });
624
-
625
- exposedApi.http.express.mount(exposedApp);
626
-
622
+ })
623
+
624
+ exposedApi.http.express.mount(exposedApp)
625
+
627
626
  const response = await request(exposedApp)
628
627
  .get('/api/countries')
629
628
  .set('Origin', 'https://example.com')
630
- .set('Accept', 'application/vnd.api+json');
631
-
632
- assert.equal(response.status, 200);
633
- assert.equal(response.headers['access-control-expose-headers'], 'X-Custom-Header, X-Rate-Limit, X-Request-Id');
634
- });
635
-
629
+ .set('Accept', 'application/vnd.api+json')
630
+
631
+ assert.equal(response.status, 200)
632
+ assert.equal(response.headers['access-control-expose-headers'], 'X-Custom-Header, X-Rate-Limit, X-Request-Id')
633
+ })
634
+
636
635
  it('should respect custom allowed methods configuration', async () => {
637
636
  // Create API with limited methods
638
- const methodsApp = express();
637
+ const methodsApp = express()
639
638
  const methodsApi = await createBasicApi(knex, {
640
639
  apiName: 'cors-methods-test',
641
640
  tablePrefix: 'cors_methods',
642
641
  includeExpress: true,
643
642
  express: { app: methodsApp }
644
- });
645
-
643
+ })
644
+
646
645
  await methodsApi.use(CorsPlugin, {
647
646
  origin: '*',
648
647
  credentials: false,
649
648
  methods: ['GET', 'POST'] // Only allow GET and POST
650
- });
651
-
652
- methodsApi.http.express.mount(methodsApp);
653
-
649
+ })
650
+
651
+ methodsApi.http.express.mount(methodsApp)
652
+
654
653
  const response = await request(methodsApp)
655
654
  .options('/api/countries')
656
655
  .set('Origin', 'https://example.com')
657
- .set('Access-Control-Request-Method', 'DELETE');
658
-
659
- assert.equal(response.status, 204);
660
- assert.equal(response.headers['access-control-allow-methods'], 'GET, POST');
661
- });
662
-
656
+ .set('Access-Control-Request-Method', 'DELETE')
657
+
658
+ assert.equal(response.status, 204)
659
+ assert.equal(response.headers['access-control-allow-methods'], 'GET, POST')
660
+ })
661
+
663
662
  it('should handle credentials false configuration', async () => {
664
663
  // Create API with credentials disabled
665
- const noCredApp = express();
664
+ const noCredApp = express()
666
665
  const noCredApi = await createBasicApi(knex, {
667
666
  apiName: 'cors-nocred-test',
668
667
  tablePrefix: 'cors_nocred',
669
668
  includeExpress: true,
670
669
  express: { app: noCredApp }
671
- });
672
-
670
+ })
671
+
673
672
  await noCredApi.use(CorsPlugin, {
674
673
  origin: '*',
675
674
  credentials: false
676
- });
677
-
678
- noCredApi.http.express.mount(noCredApp);
679
-
675
+ })
676
+
677
+ noCredApi.http.express.mount(noCredApp)
678
+
680
679
  const response = await request(noCredApp)
681
680
  .get('/api/countries')
682
681
  .set('Origin', 'https://example.com')
683
- .set('Accept', 'application/vnd.api+json');
684
-
685
- assert.equal(response.status, 200);
686
- assert.equal(response.headers['access-control-allow-origin'], '*');
687
- assert.equal(response.headers['access-control-allow-credentials'], undefined);
682
+ .set('Accept', 'application/vnd.api+json')
683
+
684
+ assert.equal(response.status, 200)
685
+ assert.equal(response.headers['access-control-allow-origin'], '*')
686
+ assert.equal(response.headers['access-control-allow-credentials'], undefined)
688
687
  // No Vary header when using wildcard without credentials
689
- assert.equal(response.headers['vary'], undefined);
690
- });
691
-
688
+ assert.equal(response.headers['vary'], undefined)
689
+ })
690
+
692
691
  it('should handle custom allowed headers configuration', async () => {
693
692
  // Create API with custom allowed headers
694
- const headersApp = express();
693
+ const headersApp = express()
695
694
  const headersApi = await createBasicApi(knex, {
696
695
  apiName: 'cors-headers-test',
697
696
  tablePrefix: 'cors_headers',
698
697
  includeExpress: true,
699
698
  express: { app: headersApp }
700
- });
701
-
699
+ })
700
+
702
701
  await headersApi.use(CorsPlugin, {
703
702
  origin: 'https://app.example.com',
704
703
  credentials: true,
705
704
  allowedHeaders: ['Content-Type', 'X-API-Key', 'X-Session-Token']
706
- });
707
-
708
- headersApi.http.express.mount(headersApp);
709
-
705
+ })
706
+
707
+ headersApi.http.express.mount(headersApp)
708
+
710
709
  const response = await request(headersApp)
711
710
  .options('/api/countries')
712
711
  .set('Origin', 'https://app.example.com')
713
712
  .set('Access-Control-Request-Method', 'POST')
714
- .set('Access-Control-Request-Headers', 'X-API-Key, X-Session-Token');
715
-
716
- assert.equal(response.status, 204);
717
- assert.equal(response.headers['access-control-allow-headers'], 'Content-Type, X-API-Key, X-Session-Token');
718
- });
719
- });
713
+ .set('Access-Control-Request-Headers', 'X-API-Key, X-Session-Token')
714
+
715
+ assert.equal(response.status, 204)
716
+ assert.equal(response.headers['access-control-allow-headers'], 'Content-Type, X-API-Key, X-Session-Token')
717
+ })
718
+ })
720
719
 
721
720
  describe('CORS with PUT Requests', () => {
722
721
  it('should handle PUT requests with proper CORS headers', async () => {
@@ -725,15 +724,15 @@ describe('CORS Plugin Tests', { timeout: 30000 }, () => {
725
724
  const doc = createJsonApiDocument('countries', {
726
725
  name: 'Original Country',
727
726
  code: 'OC'
728
- });
729
-
727
+ })
728
+
730
729
  const createResult = await api.resources.countries.post({
731
730
  inputRecord: doc,
732
731
  simplified: false
733
- });
734
-
735
- const countryId = createResult.data.id;
736
-
732
+ })
733
+
734
+ const countryId = createResult.data.id
735
+
737
736
  // Now test PUT with CORS
738
737
  const putDoc = {
739
738
  data: {
@@ -744,92 +743,92 @@ describe('CORS Plugin Tests', { timeout: 30000 }, () => {
744
743
  code: 'RC'
745
744
  }
746
745
  }
747
- };
748
-
746
+ }
747
+
749
748
  const response = await request(app)
750
749
  .put(`/api/countries/${countryId}`)
751
750
  .send(putDoc)
752
751
  .set('Origin', 'https://example.com')
753
752
  .set('Content-Type', 'application/vnd.api+json')
754
- .set('Accept', 'application/vnd.api+json');
755
-
756
- assert.equal(response.status, 204);
757
- assert.equal(response.headers['access-control-allow-origin'], 'https://example.com');
758
- assert.equal(response.headers['access-control-allow-credentials'], 'true');
759
- assert.equal(response.headers['vary'], 'Origin');
760
-
753
+ .set('Accept', 'application/vnd.api+json')
754
+
755
+ assert.equal(response.status, 204)
756
+ assert.equal(response.headers['access-control-allow-origin'], 'https://example.com')
757
+ assert.equal(response.headers['access-control-allow-credentials'], 'true')
758
+ assert.equal(response.headers['vary'], 'Origin')
759
+
761
760
  // Note: PUT might not return the full record by default depending on returnFullRecord settings
762
761
  // The important part for CORS testing is that the headers are correct, which we've verified above
763
- });
764
-
762
+ })
763
+
765
764
  it('should handle PUT preflight requests', async () => {
766
765
  const response = await request(app)
767
766
  .options('/api/countries/123')
768
767
  .set('Origin', 'https://example.com')
769
768
  .set('Access-Control-Request-Method', 'PUT')
770
- .set('Access-Control-Request-Headers', 'Content-Type');
771
-
772
- assert.equal(response.status, 204);
773
- assert.equal(response.headers['access-control-allow-origin'], 'https://example.com');
774
- assert.equal(response.headers['access-control-allow-methods'], 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
775
- assert(response.headers['access-control-allow-methods'].includes('PUT'));
776
- });
777
- });
769
+ .set('Access-Control-Request-Headers', 'Content-Type')
770
+
771
+ assert.equal(response.status, 204)
772
+ assert.equal(response.headers['access-control-allow-origin'], 'https://example.com')
773
+ assert.equal(response.headers['access-control-allow-methods'], 'GET, POST, PUT, PATCH, DELETE, OPTIONS')
774
+ assert(response.headers['access-control-allow-methods'].includes('PUT'))
775
+ })
776
+ })
778
777
 
779
778
  describe('CORS Edge Cases', () => {
780
779
  it('should handle requests with no exposed headers configured', async () => {
781
780
  // Create API with empty exposed headers
782
- const noExposedApp = express();
781
+ const noExposedApp = express()
783
782
  const noExposedApi = await createBasicApi(knex, {
784
783
  apiName: 'cors-noexposed-test',
785
784
  tablePrefix: 'cors_noexposed',
786
785
  includeExpress: true,
787
786
  express: { app: noExposedApp }
788
- });
789
-
787
+ })
788
+
790
789
  await noExposedApi.use(CorsPlugin, {
791
790
  origin: '*',
792
791
  credentials: false,
793
792
  exposedHeaders: [] // Empty array
794
- });
795
-
796
- noExposedApi.http.express.mount(noExposedApp);
797
-
793
+ })
794
+
795
+ noExposedApi.http.express.mount(noExposedApp)
796
+
798
797
  const response = await request(noExposedApp)
799
798
  .get('/api/countries')
800
799
  .set('Origin', 'https://example.com')
801
- .set('Accept', 'application/vnd.api+json');
802
-
803
- assert.equal(response.status, 200);
800
+ .set('Accept', 'application/vnd.api+json')
801
+
802
+ assert.equal(response.status, 200)
804
803
  // Should not set exposed headers when array is empty
805
- assert.equal(response.headers['access-control-expose-headers'], undefined);
806
- });
807
-
804
+ assert.equal(response.headers['access-control-expose-headers'], undefined)
805
+ })
806
+
808
807
  it('should handle optionsSuccessStatus configuration', async () => {
809
808
  // Create API with custom options success status
810
- const statusApp = express();
809
+ const statusApp = express()
811
810
  const statusApi = await createBasicApi(knex, {
812
811
  apiName: 'cors-status-test',
813
812
  tablePrefix: 'cors_status',
814
813
  includeExpress: true,
815
814
  express: { app: statusApp }
816
- });
817
-
815
+ })
816
+
818
817
  await statusApi.use(CorsPlugin, {
819
818
  origin: '*',
820
819
  credentials: false,
821
820
  optionsSuccessStatus: 200 // Use 200 instead of 204
822
- });
823
-
824
- statusApi.http.express.mount(statusApp);
825
-
821
+ })
822
+
823
+ statusApi.http.express.mount(statusApp)
824
+
826
825
  const response = await request(statusApp)
827
826
  .options('/api/countries')
828
827
  .set('Origin', 'https://example.com')
829
- .set('Access-Control-Request-Method', 'GET');
830
-
831
- assert.equal(response.status, 200);
832
- assert.equal(response.headers['access-control-allow-origin'], '*');
833
- });
834
- });
835
- });
828
+ .set('Access-Control-Request-Method', 'GET')
829
+
830
+ assert.equal(response.status, 200)
831
+ assert.equal(response.headers['access-control-allow-origin'], '*')
832
+ })
833
+ })
834
+ })