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,15 +1,15 @@
1
- import { describe, it, before, after, beforeEach } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import knexLib from 'knex';
4
- import {
5
- validateJsonApiStructure,
6
- 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 {
5
+ validateJsonApiStructure,
6
+ cleanTables,
7
7
  createJsonApiDocument,
8
8
  assertResourceAttributes,
9
9
  createRelationship,
10
10
  resourceIdentifier
11
- } from './helpers/test-utils.js';
12
- import { createMultiHomeApi } from './fixtures/api-configs.js';
11
+ } from './helpers/test-utils.js'
12
+ import { createMultiHomeApi } from './fixtures/api-configs.js'
13
13
 
14
14
  // Create Knex instance for tests - always use SQLite in-memory
15
15
  const knex = knexLib({
@@ -18,24 +18,24 @@ const knex = knexLib({
18
18
  filename: ':memory:'
19
19
  },
20
20
  useNullAsDefault: true
21
- });
21
+ })
22
22
 
23
23
  // API instance that persists across ALL tests
24
- let api;
24
+ let api
25
25
 
26
26
  describe('MultiHome Plugin Tests', () => {
27
27
  // IMPORTANT: before() runs ONCE for the entire test suite
28
28
  before(async () => {
29
29
  // Create API instance ONCE - this is reused for all tests
30
- api = await createMultiHomeApi(knex);
31
- });
32
-
30
+ api = await createMultiHomeApi(knex)
31
+ })
32
+
33
33
  // IMPORTANT: after() cleans up resources
34
34
  after(async () => {
35
35
  // Always destroy knex connection to allow tests to exit
36
- await knex.destroy();
37
- });
38
-
36
+ await knex.destroy()
37
+ })
38
+
39
39
  // IMPORTANT: beforeEach() cleans data but does NOT recreate API
40
40
  beforeEach(async () => {
41
41
  // Clean all tables - list ALL tables your tests use
@@ -44,35 +44,35 @@ describe('MultiHome Plugin Tests', () => {
44
44
  'multihome_tasks',
45
45
  'multihome_users',
46
46
  'multihome_system_settings'
47
- ]);
48
- });
49
-
47
+ ])
48
+ })
49
+
50
50
  describe('Tenant Isolation', () => {
51
51
  it('should automatically set tenant_id on new records', async () => {
52
52
  // Create a project for tenant A
53
53
  const doc = createJsonApiDocument('projects', {
54
54
  name: 'Tenant A Project',
55
55
  description: 'A project for tenant A'
56
- });
57
-
56
+ })
57
+
58
58
  // Create with tenant context
59
59
  const createResult = await api.resources.projects.post({
60
60
  inputRecord: doc,
61
61
  simplified: false
62
62
  }, {
63
63
  auth: { multihome_id: 'tenant-a' }
64
- });
65
-
64
+ })
65
+
66
66
  // Verify response
67
- validateJsonApiStructure(createResult);
68
- assert.equal(createResult.data.type, 'projects');
67
+ validateJsonApiStructure(createResult)
68
+ assert.equal(createResult.data.type, 'projects')
69
69
  assertResourceAttributes(createResult.data, {
70
70
  name: 'Tenant A Project',
71
71
  description: 'A project for tenant A',
72
72
  tenant_id: 'tenant-a' // Should be automatically set
73
- });
74
- });
75
-
73
+ })
74
+ })
75
+
76
76
  it('should filter queries by tenant_id', async () => {
77
77
  // Create projects for different tenants
78
78
  const projectA1 = await api.resources.projects.post({
@@ -82,8 +82,8 @@ describe('MultiHome Plugin Tests', () => {
82
82
  simplified: false
83
83
  }, {
84
84
  auth: { multihome_id: 'tenant-a' }
85
- });
86
-
85
+ })
86
+
87
87
  const projectA2 = await api.resources.projects.post({
88
88
  inputRecord: createJsonApiDocument('projects', {
89
89
  name: 'Project A2'
@@ -91,8 +91,8 @@ describe('MultiHome Plugin Tests', () => {
91
91
  simplified: false
92
92
  }, {
93
93
  auth: { multihome_id: 'tenant-a' }
94
- });
95
-
94
+ })
95
+
96
96
  const projectB1 = await api.resources.projects.post({
97
97
  inputRecord: createJsonApiDocument('projects', {
98
98
  name: 'Project B1'
@@ -100,31 +100,31 @@ describe('MultiHome Plugin Tests', () => {
100
100
  simplified: false
101
101
  }, {
102
102
  auth: { multihome_id: 'tenant-b' }
103
- });
104
-
103
+ })
104
+
105
105
  // Query as tenant A - should only see tenant A's projects
106
106
  const resultA = await api.resources.projects.query({
107
107
  simplified: false
108
108
  }, {
109
109
  auth: { multihome_id: 'tenant-a' }
110
- });
111
-
112
- validateJsonApiStructure(resultA, true); // true for collection
113
- assert.equal(resultA.data.length, 2, 'Tenant A should see 2 projects');
114
- assert(resultA.data.every(p => p.attributes.name.startsWith('Project A')));
115
-
110
+ })
111
+
112
+ validateJsonApiStructure(resultA, true) // true for collection
113
+ assert.equal(resultA.data.length, 2, 'Tenant A should see 2 projects')
114
+ assert(resultA.data.every(p => p.attributes.name.startsWith('Project A')))
115
+
116
116
  // Query as tenant B - should only see tenant B's projects
117
117
  const resultB = await api.resources.projects.query({
118
118
  simplified: false
119
119
  }, {
120
120
  auth: { multihome_id: 'tenant-b' }
121
- });
122
-
123
- validateJsonApiStructure(resultB, true); // true for collection
124
- assert.equal(resultB.data.length, 1, 'Tenant B should see 1 project');
125
- assert.equal(resultB.data[0].attributes.name, 'Project B1');
126
- });
127
-
121
+ })
122
+
123
+ validateJsonApiStructure(resultB, true) // true for collection
124
+ assert.equal(resultB.data.length, 1, 'Tenant B should see 1 project')
125
+ assert.equal(resultB.data[0].attributes.name, 'Project B1')
126
+ })
127
+
128
128
  it('should prevent access to other tenant records', async () => {
129
129
  // Create a project for tenant A
130
130
  const projectA = await api.resources.projects.post({
@@ -134,8 +134,8 @@ describe('MultiHome Plugin Tests', () => {
134
134
  simplified: false
135
135
  }, {
136
136
  auth: { multihome_id: 'tenant-a' }
137
- });
138
-
137
+ })
138
+
139
139
  // Try to access as tenant B - should fail
140
140
  await assert.rejects(
141
141
  async () => {
@@ -144,15 +144,15 @@ describe('MultiHome Plugin Tests', () => {
144
144
  simplified: false
145
145
  }, {
146
146
  auth: { multihome_id: 'tenant-b' }
147
- });
147
+ })
148
148
  },
149
149
  (err) => {
150
- return err.code === 'REST_API_RESOURCE';
150
+ return err.code === 'REST_API_RESOURCE'
151
151
  },
152
152
  'Should not be able to access other tenant data'
153
- );
154
- });
155
-
153
+ )
154
+ })
155
+
156
156
  it('should prevent updating tenant_id to different value', async () => {
157
157
  // Create a project for tenant A
158
158
  const project = await api.resources.projects.post({
@@ -162,8 +162,8 @@ describe('MultiHome Plugin Tests', () => {
162
162
  simplified: false
163
163
  }, {
164
164
  auth: { multihome_id: 'tenant-a' }
165
- });
166
-
165
+ })
166
+
167
167
  // Try to update with different tenant_id - should fail
168
168
  await assert.rejects(
169
169
  async () => {
@@ -182,18 +182,18 @@ describe('MultiHome Plugin Tests', () => {
182
182
  simplified: false
183
183
  }, {
184
184
  auth: { multihome_id: 'tenant-a' }
185
- });
185
+ })
186
186
  },
187
187
  (err) => {
188
- console.log('PATCH error:', err.message);
189
- console.log('PATCH error stack:', err.stack);
190
- return err.message.includes('must match current context');
188
+ console.log('PATCH error:', err.message)
189
+ console.log('PATCH error stack:', err.stack)
190
+ return err.message.includes('must match current context')
191
191
  },
192
192
  'Should not be able to change tenant_id'
193
- );
194
- });
195
- });
196
-
193
+ )
194
+ })
195
+ })
196
+
197
197
  describe('Relationships Across Tenants', () => {
198
198
  it('should maintain tenant isolation for related records', async () => {
199
199
  // Create projects for different tenants
@@ -204,8 +204,8 @@ describe('MultiHome Plugin Tests', () => {
204
204
  simplified: false
205
205
  }, {
206
206
  auth: { multihome_id: 'tenant-a' }
207
- });
208
-
207
+ })
208
+
209
209
  const projectB = await api.resources.projects.post({
210
210
  inputRecord: createJsonApiDocument('projects', {
211
211
  name: 'Tenant B Project'
@@ -213,11 +213,11 @@ describe('MultiHome Plugin Tests', () => {
213
213
  simplified: false
214
214
  }, {
215
215
  auth: { multihome_id: 'tenant-b' }
216
- });
217
-
216
+ })
217
+
218
218
  // Create task for tenant A project
219
219
  const taskA = await api.resources.tasks.post({
220
- inputRecord: createJsonApiDocument('tasks',
220
+ inputRecord: createJsonApiDocument('tasks',
221
221
  {
222
222
  title: 'Task for Tenant A'
223
223
  },
@@ -228,13 +228,13 @@ describe('MultiHome Plugin Tests', () => {
228
228
  simplified: false
229
229
  }, {
230
230
  auth: { multihome_id: 'tenant-a' }
231
- });
232
-
231
+ })
232
+
233
233
  // Try to create task for tenant B project as tenant A - should fail
234
234
  await assert.rejects(
235
235
  async () => {
236
236
  await api.resources.tasks.post({
237
- inputRecord: createJsonApiDocument('tasks',
237
+ inputRecord: createJsonApiDocument('tasks',
238
238
  {
239
239
  title: 'Invalid Task'
240
240
  },
@@ -245,15 +245,15 @@ describe('MultiHome Plugin Tests', () => {
245
245
  simplified: false
246
246
  }, {
247
247
  auth: { multihome_id: 'tenant-a' }
248
- });
248
+ })
249
249
  },
250
250
  (err) => {
251
251
  // Should fail because project belongs to different tenant
252
- return err.code === 'REST_API_VALIDATION' || err.code === 'REST_API_RESOURCE';
252
+ return err.code === 'REST_API_VALIDATION' || err.code === 'REST_API_RESOURCE'
253
253
  },
254
254
  'Should not be able to link to other tenant resources'
255
- );
256
-
255
+ )
256
+
257
257
  // Query with includes - should only see own tenant data
258
258
  const projectWithTasks = await api.resources.projects.get({
259
259
  id: projectA.data.id,
@@ -263,15 +263,15 @@ describe('MultiHome Plugin Tests', () => {
263
263
  simplified: false
264
264
  }, {
265
265
  auth: { multihome_id: 'tenant-a' }
266
- });
267
-
268
- validateJsonApiStructure(projectWithTasks);
269
- assert(projectWithTasks.included, 'Should have included tasks');
270
- assert.equal(projectWithTasks.included.length, 1, 'Should include one task');
271
- assert.equal(projectWithTasks.included[0].attributes.tenant_id, 'tenant-a');
272
- });
273
- });
274
-
266
+ })
267
+
268
+ validateJsonApiStructure(projectWithTasks)
269
+ assert(projectWithTasks.included, 'Should have included tasks')
270
+ assert.equal(projectWithTasks.included.length, 1, 'Should include one task')
271
+ assert.equal(projectWithTasks.included[0].attributes.tenant_id, 'tenant-a')
272
+ })
273
+ })
274
+
275
275
  describe('Excluded Resources', () => {
276
276
  it('should allow access to excluded resources without tenant context', async () => {
277
277
  // Create system setting without tenant context
@@ -281,31 +281,31 @@ describe('MultiHome Plugin Tests', () => {
281
281
  value: '1.0.0'
282
282
  }),
283
283
  simplified: false
284
- });
285
-
286
- validateJsonApiStructure(setting);
287
- assert.equal(setting.data.type, 'system_settings');
288
- assert(!setting.data.attributes.tenant_id, 'Should not have tenant_id');
289
-
284
+ })
285
+
286
+ validateJsonApiStructure(setting)
287
+ assert.equal(setting.data.type, 'system_settings')
288
+ assert(!setting.data.attributes.tenant_id, 'Should not have tenant_id')
289
+
290
290
  // Query without tenant context should work
291
291
  const allSettings = await api.resources.system_settings.query({
292
292
  simplified: false
293
- });
294
-
295
- assert.equal(allSettings.data.length, 1);
296
-
293
+ })
294
+
295
+ assert.equal(allSettings.data.length, 1)
296
+
297
297
  // Should be accessible by any tenant
298
298
  const settingByTenantA = await api.resources.system_settings.get({
299
299
  id: setting.data.id,
300
300
  simplified: false
301
301
  }, {
302
302
  auth: { multihome_id: 'tenant-a' }
303
- });
304
-
305
- assert.equal(settingByTenantA.data.id, setting.data.id);
306
- });
307
- });
308
-
303
+ })
304
+
305
+ assert.equal(settingByTenantA.data.id, setting.data.id)
306
+ })
307
+ })
308
+
309
309
  describe('Configuration Options', () => {
310
310
  it('should require tenant context when requireAuth is true', async () => {
311
311
  // Try to create without tenant context - should fail
@@ -316,26 +316,26 @@ describe('MultiHome Plugin Tests', () => {
316
316
  name: 'No Tenant Project'
317
317
  }),
318
318
  simplified: false
319
- });
319
+ })
320
320
  },
321
321
  (err) => {
322
- return err.message.includes('multihome context');
322
+ return err.message.includes('multihome context')
323
323
  },
324
324
  'Should require tenant context when requireAuth is true'
325
- );
326
- });
327
-
325
+ )
326
+ })
327
+
328
328
  it('should validate resources have tenant field when allowMissing is false', async () => {
329
329
  // This test validates that the plugin threw an error during setup
330
330
  // if a resource was missing the tenant field. Since our test resources
331
331
  // all have tenant_id (except excluded ones), we can verify the
332
332
  // configuration is working by checking vars
333
- assert.equal(api.vars.multihome.field, 'tenant_id');
334
- assert.equal(api.vars.multihome.allowMissing, false);
335
- assert(api.vars.multihome.excludeResources.includes('system_settings'));
336
- });
337
- });
338
-
333
+ assert.equal(api.vars.multihome.field, 'tenant_id')
334
+ assert.equal(api.vars.multihome.allowMissing, false)
335
+ assert(api.vars.multihome.excludeResources.includes('system_settings'))
336
+ })
337
+ })
338
+
339
339
  describe('Complex Queries', () => {
340
340
  it('should apply tenant filter to filtered queries', async () => {
341
341
  // Create users for different tenants
@@ -348,8 +348,8 @@ describe('MultiHome Plugin Tests', () => {
348
348
  simplified: false
349
349
  }, {
350
350
  auth: { multihome_id: 'tenant-a' }
351
- });
352
-
351
+ })
352
+
353
353
  await api.resources.users.post({
354
354
  inputRecord: createJsonApiDocument('users', {
355
355
  email: 'user@tenant-a.com',
@@ -359,8 +359,8 @@ describe('MultiHome Plugin Tests', () => {
359
359
  simplified: false
360
360
  }, {
361
361
  auth: { multihome_id: 'tenant-a' }
362
- });
363
-
362
+ })
363
+
364
364
  await api.resources.users.post({
365
365
  inputRecord: createJsonApiDocument('users', {
366
366
  email: 'admin@tenant-b.com',
@@ -370,8 +370,8 @@ describe('MultiHome Plugin Tests', () => {
370
370
  simplified: false
371
371
  }, {
372
372
  auth: { multihome_id: 'tenant-b' }
373
- });
374
-
373
+ })
374
+
375
375
  // Query for admins as tenant A - should only see tenant A admin
376
376
  const adminsA = await api.resources.users.query({
377
377
  queryParams: {
@@ -380,22 +380,22 @@ describe('MultiHome Plugin Tests', () => {
380
380
  simplified: false
381
381
  }, {
382
382
  auth: { multihome_id: 'tenant-a' }
383
- });
384
-
385
- assert.equal(adminsA.data.length, 1, 'Should only see tenant A admin');
386
- assert.equal(adminsA.data[0].attributes.email, 'admin@tenant-a.com');
387
-
383
+ })
384
+
385
+ assert.equal(adminsA.data.length, 1, 'Should only see tenant A admin')
386
+ assert.equal(adminsA.data[0].attributes.email, 'admin@tenant-a.com')
387
+
388
388
  // Query for all users as tenant B - should only see tenant B users
389
389
  const usersB = await api.resources.users.query({
390
390
  simplified: false
391
391
  }, {
392
392
  auth: { multihome_id: 'tenant-b' }
393
- });
394
-
395
- assert.equal(usersB.data.length, 1, 'Should only see tenant B users');
396
- assert.equal(usersB.data[0].attributes.email, 'admin@tenant-b.com');
397
- });
398
-
393
+ })
394
+
395
+ assert.equal(usersB.data.length, 1, 'Should only see tenant B users')
396
+ assert.equal(usersB.data[0].attributes.email, 'admin@tenant-b.com')
397
+ })
398
+
399
399
  it('should apply tenant filter with sorting and pagination', async () => {
400
400
  // Create multiple projects for tenant A
401
401
  for (let i = 1; i <= 5; i++) {
@@ -407,9 +407,9 @@ describe('MultiHome Plugin Tests', () => {
407
407
  simplified: false
408
408
  }, {
409
409
  auth: { multihome_id: 'tenant-a' }
410
- });
410
+ })
411
411
  }
412
-
412
+
413
413
  // Create projects for tenant B
414
414
  for (let i = 1; i <= 3; i++) {
415
415
  await api.resources.projects.post({
@@ -419,9 +419,9 @@ describe('MultiHome Plugin Tests', () => {
419
419
  simplified: false
420
420
  }, {
421
421
  auth: { multihome_id: 'tenant-b' }
422
- });
422
+ })
423
423
  }
424
-
424
+
425
425
  // Query with pagination as tenant A
426
426
  const page1 = await api.resources.projects.query({
427
427
  queryParams: {
@@ -431,12 +431,12 @@ describe('MultiHome Plugin Tests', () => {
431
431
  simplified: false
432
432
  }, {
433
433
  auth: { multihome_id: 'tenant-a' }
434
- });
435
-
436
- assert.equal(page1.data.length, 2, 'Should return page size');
437
- assert.equal(page1.data[0].attributes.name, 'Project A5', 'Should be sorted descending');
438
- assert.equal(page1.data[1].attributes.name, 'Project A4');
439
-
434
+ })
435
+
436
+ assert.equal(page1.data.length, 2, 'Should return page size')
437
+ assert.equal(page1.data[0].attributes.name, 'Project A5', 'Should be sorted descending')
438
+ assert.equal(page1.data[1].attributes.name, 'Project A4')
439
+
440
440
  // Get page 2
441
441
  const page2 = await api.resources.projects.query({
442
442
  queryParams: {
@@ -446,28 +446,28 @@ describe('MultiHome Plugin Tests', () => {
446
446
  simplified: false
447
447
  }, {
448
448
  auth: { multihome_id: 'tenant-a' }
449
- });
450
-
451
- assert.equal(page2.data.length, 2, 'Should return page size');
452
- assert(page2.data.every(p => p.attributes.tenant_id === 'tenant-a'));
453
- });
454
- });
455
-
449
+ })
450
+
451
+ assert.equal(page2.data.length, 2, 'Should return page size')
452
+ assert(page2.data.every(p => p.attributes.tenant_id === 'tenant-a'))
453
+ })
454
+ })
455
+
456
456
  describe('Error Handling', () => {
457
457
  it('should provide clear error when no tenant context available', async () => {
458
458
  await assert.rejects(
459
459
  async () => {
460
460
  await api.resources.projects.query({
461
461
  simplified: false
462
- });
462
+ })
463
463
  },
464
464
  (err) => {
465
- return err.message.includes('No multihome context available');
465
+ return err.message.includes('No multihome context available')
466
466
  },
467
467
  'Should provide clear error message'
468
- );
469
- });
470
-
468
+ )
469
+ })
470
+
471
471
  it('should handle missing tenant field gracefully for excluded resources', async () => {
472
472
  // system_settings is excluded and doesn't have tenant_id field
473
473
  const result = await api.resources.system_settings.post({
@@ -478,13 +478,13 @@ describe('MultiHome Plugin Tests', () => {
478
478
  simplified: false
479
479
  }, {
480
480
  auth: { multihome_id: 'tenant-a' } // Should be ignored for excluded resources
481
- });
482
-
483
- validateJsonApiStructure(result);
484
- assert(!result.data.attributes.tenant_id, 'Excluded resource should not have tenant_id');
485
- });
486
- });
487
-
481
+ })
482
+
483
+ validateJsonApiStructure(result)
484
+ assert(!result.data.attributes.tenant_id, 'Excluded resource should not have tenant_id')
485
+ })
486
+ })
487
+
488
488
  describe('Security', () => {
489
489
  it('should prevent bypassing tenant filter with malicious IDs', async () => {
490
490
  // Create a project for tenant A
@@ -495,15 +495,15 @@ describe('MultiHome Plugin Tests', () => {
495
495
  simplified: false
496
496
  }, {
497
497
  auth: { multihome_id: 'tenant-a' }
498
- });
499
-
498
+ })
499
+
500
500
  // Try various bypass attempts as tenant B
501
501
  const maliciousIds = [
502
502
  project.data.id,
503
503
  `${project.data.id} OR 1=1`,
504
504
  `${project.data.id}'; DROP TABLE projects; --`
505
- ];
506
-
505
+ ]
506
+
507
507
  for (const id of maliciousIds) {
508
508
  await assert.rejects(
509
509
  async () => {
@@ -512,19 +512,19 @@ describe('MultiHome Plugin Tests', () => {
512
512
  simplified: false
513
513
  }, {
514
514
  auth: { multihome_id: 'tenant-b' }
515
- });
515
+ })
516
516
  },
517
517
  undefined, // Any error is fine
518
518
  `Should not bypass security with ID: ${id}`
519
- );
519
+ )
520
520
  }
521
- });
522
-
521
+ })
522
+
523
523
  it('should log security violations', async () => {
524
524
  // This test verifies that the plugin logs security violations
525
525
  // In a real implementation, you might capture logs and verify
526
526
  // For now, we just ensure the security check happens
527
-
527
+
528
528
  // Create data for tenant A
529
529
  const project = await api.resources.projects.post({
530
530
  inputRecord: createJsonApiDocument('projects', {
@@ -533,8 +533,8 @@ describe('MultiHome Plugin Tests', () => {
533
533
  simplified: false
534
534
  }, {
535
535
  auth: { multihome_id: 'tenant-a' }
536
- });
537
-
536
+ })
537
+
538
538
  // Attempt access as tenant B
539
539
  try {
540
540
  await api.resources.projects.get({
@@ -542,12 +542,12 @@ describe('MultiHome Plugin Tests', () => {
542
542
  simplified: false
543
543
  }, {
544
544
  auth: { multihome_id: 'tenant-b' }
545
- });
546
- assert.fail('Should have thrown error');
545
+ })
546
+ assert.fail('Should have thrown error')
547
547
  } catch (err) {
548
548
  // Error is expected - security violation should be logged
549
- assert(err.code === 'REST_API_RESOURCE' || err.message.includes('Access denied'));
549
+ assert(err.code === 'REST_API_RESOURCE' || err.message.includes('Access denied'))
550
550
  }
551
- });
552
- });
553
- });
551
+ })
552
+ })
553
+ })