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.
- package/connectors_ideas/fastify.js +66 -68
- package/connectors_ideas/koa-plugin.js +50 -51
- package/eslint.config.js +11 -2
- package/examples/url-override-example.js +63 -63
- package/index.js +13 -13
- package/lib/enhanced-logger.js +51 -51
- package/lib/error-formatter.js +61 -61
- package/lib/rest-api-errors.js +28 -28
- package/package.json +1 -1
- package/plugins/core/bulk-operations-plugin.js +142 -145
- package/plugins/core/connectors/express-plugin.js +210 -217
- package/plugins/core/connectors/lib/busboy-detector.js +84 -84
- package/plugins/core/connectors/lib/formidable-detector.js +50 -50
- package/plugins/core/connectors/lib/request-helpers.js +74 -74
- package/plugins/core/file-handling-plugin.js +125 -125
- package/plugins/core/lib/anyapi/anyapi-registry.js +254 -254
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +287 -287
- package/plugins/core/lib/anyapi/schema-utils.js +101 -101
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +41 -41
- package/plugins/core/lib/dbTablesOperations.js +134 -135
- package/plugins/core/lib/fractional-positioning.js +74 -76
- package/plugins/core/lib/querying/knex-cross-table-search.js +320 -320
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +144 -144
- package/plugins/core/lib/querying/knex-pagination-helpers.js +164 -164
- package/plugins/core/lib/querying/knex-process-includes.js +50 -50
- package/plugins/core/lib/querying/knex-query-helpers-base.js +31 -31
- package/plugins/core/lib/querying/knex-query-helpers.js +451 -452
- package/plugins/core/lib/querying/knex-relationship-includes.js +1160 -1170
- package/plugins/core/lib/querying/knex-window-queries.js +89 -89
- package/plugins/core/lib/querying/url-helpers.js +35 -35
- package/plugins/core/lib/querying-writing/compile-schemas.js +97 -98
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +31 -31
- package/plugins/core/lib/querying-writing/database-capabilities.js +94 -94
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +76 -76
- package/plugins/core/lib/querying-writing/default-data-helpers.js +27 -27
- package/plugins/core/lib/querying-writing/field-utils.js +44 -42
- package/plugins/core/lib/querying-writing/knex-constants.js +15 -16
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +117 -115
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +41 -41
- package/plugins/core/lib/querying-writing/payload-validators.js +275 -275
- package/plugins/core/lib/querying-writing/schema-helpers.js +129 -129
- package/plugins/core/lib/querying-writing/scope-validations.js +143 -146
- package/plugins/core/lib/querying-writing/simplified-helpers.js +133 -129
- package/plugins/core/lib/storage/storage-adapter.js +68 -68
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +31 -31
- package/plugins/core/lib/writing/many-to-many-manipulations.js +81 -81
- package/plugins/core/lib/writing/relationship-processor.js +58 -58
- package/plugins/core/rest-api-access.js +8 -8
- package/plugins/core/rest-api-anyapi-knex-plugin.js +920 -920
- package/plugins/core/rest-api-cors-plugin.js +170 -171
- package/plugins/core/rest-api-knex-plugin.js +482 -493
- package/plugins/core/rest-api-multihome-plugin.js +247 -251
- package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +5 -5
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +20 -21
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +44 -44
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +23 -23
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +15 -15
- package/plugins/core/rest-api-plugin-methods/add-route.js +9 -9
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +5 -6
- package/plugins/core/rest-api-plugin-methods/common.js +295 -297
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +35 -37
- package/plugins/core/rest-api-plugin-methods/delete.js +57 -59
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +172 -171
- package/plugins/core/rest-api-plugin-methods/get-related.js +96 -96
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +16 -18
- package/plugins/core/rest-api-plugin-methods/get.js +86 -86
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +16 -17
- package/plugins/core/rest-api-plugin-methods/patch.js +63 -64
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +54 -55
- package/plugins/core/rest-api-plugin-methods/post.js +61 -62
- package/plugins/core/rest-api-plugin-methods/put.js +86 -87
- package/plugins/core/rest-api-plugin-methods/query.js +88 -88
- package/plugins/core/rest-api-plugin-methods/release.js +5 -6
- package/plugins/core/rest-api-plugin.js +76 -78
- package/plugins/core/rest-api-positioning-plugin.js +251 -252
- package/plugins/core/socketio-plugin.js +240 -236
- package/plugins/storage/local-storage.js +103 -103
- package/plugins/storage/s3-storage.js +41 -42
- package/quickTest.js +37 -37
- package/scripts/debug-anyapi-review-filter.mjs +18 -18
- package/scripts/inspect-anyapi-descriptor-country.mjs +10 -10
- package/scripts/inspect-anyapi-descriptor.mjs +10 -10
- package/scripts/run-docs.js +112 -113
- package/test-debug.js +28 -28
- package/tests/TEST_TEMPLATE.test.js +51 -51
- package/tests/anyapi-basic.test.js +42 -42
- package/tests/anyapi-cursor-pagination.test.js +48 -48
- package/tests/bulk-operations.test.js +208 -208
- package/tests/computed-fields.test.js +204 -204
- package/tests/cors.test.js +412 -413
- package/tests/curl.test.js +275 -276
- package/tests/custom-idproperty-relationships.test.js +266 -266
- package/tests/field-getters.test.js +132 -132
- package/tests/field-setters.test.js +110 -110
- package/tests/fixtures/api-configs.js +699 -699
- package/tests/helpers/storage-mode.js +32 -32
- package/tests/helpers/test-utils.js +76 -76
- package/tests/include-depth-validation.test.js +76 -76
- package/tests/includes.test.js +145 -145
- package/tests/multihome.test.js +175 -175
- package/tests/nested-includes.test.js +255 -255
- package/tests/pagination-cursor-multifield.test.js +164 -164
- package/tests/pagination-enhanced.test.js +225 -225
- package/tests/polymorphic-relationships.test.js +96 -96
- package/tests/polymorphic-simple.test.js +44 -44
- package/tests/positioning-advanced.test.js +226 -226
- package/tests/positioning-extended.test.js +399 -399
- package/tests/positioning-multifilter.test.js +204 -204
- package/tests/positioning.test.js +266 -266
- package/tests/queries.test.js +184 -184
- package/tests/query-limits.test.js +154 -155
- package/tests/relationship-endpoints.test.js +127 -127
- package/tests/relationships.test.js +178 -179
- package/tests/rest-api-access.test.js +86 -86
- package/tests/rest-api.test.js +103 -103
- package/tests/searchschema-merge.test.js +93 -93
- package/tests/socketio.test.js +326 -326
- package/tests/virtual-fields.test.js +164 -164
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { describe, it, before, beforeEach, after } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import knexLib from 'knex'
|
|
4
|
-
import { createCustomIdPropertyApi } from './fixtures/api-configs.js'
|
|
1
|
+
import { describe, it, before, beforeEach, after } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import knexLib from 'knex'
|
|
4
|
+
import { createCustomIdPropertyApi } from './fixtures/api-configs.js'
|
|
5
5
|
import {
|
|
6
6
|
validateJsonApiStructure,
|
|
7
7
|
resourceIdentifier,
|
|
@@ -12,8 +12,8 @@ import {
|
|
|
12
12
|
createToManyRelationship,
|
|
13
13
|
assertResourceAttributes,
|
|
14
14
|
assertResourceRelationship
|
|
15
|
-
} from './helpers/test-utils.js'
|
|
16
|
-
import { storageMode } from './helpers/storage-mode.js'
|
|
15
|
+
} from './helpers/test-utils.js'
|
|
16
|
+
import { storageMode } from './helpers/storage-mode.js'
|
|
17
17
|
|
|
18
18
|
// Create Knex instance for tests
|
|
19
19
|
const knex = knexLib({
|
|
@@ -22,59 +22,59 @@ const knex = knexLib({
|
|
|
22
22
|
filename: ':memory:'
|
|
23
23
|
},
|
|
24
24
|
useNullAsDefault: true
|
|
25
|
-
})
|
|
25
|
+
})
|
|
26
26
|
|
|
27
27
|
// API instance that persists across tests
|
|
28
|
-
let api
|
|
28
|
+
let api
|
|
29
29
|
|
|
30
30
|
describe('Custom idProperty Relationship Operations', () => {
|
|
31
31
|
before(async () => {
|
|
32
32
|
// Initialize API with custom idProperty for all resources
|
|
33
|
-
api = await createCustomIdPropertyApi(knex)
|
|
34
|
-
})
|
|
33
|
+
api = await createCustomIdPropertyApi(knex)
|
|
34
|
+
})
|
|
35
35
|
|
|
36
36
|
after(async () => {
|
|
37
|
-
await knex.destroy()
|
|
38
|
-
})
|
|
37
|
+
await knex.destroy()
|
|
38
|
+
})
|
|
39
39
|
|
|
40
40
|
describe('Basic CRUD with custom idProperty', () => {
|
|
41
41
|
beforeEach(async () => {
|
|
42
42
|
await cleanTables(knex, [
|
|
43
|
-
'custom_id_countries', 'custom_id_publishers', 'custom_id_authors',
|
|
43
|
+
'custom_id_countries', 'custom_id_publishers', 'custom_id_authors',
|
|
44
44
|
'custom_id_books', 'custom_id_book_authors', 'custom_id_reviews'
|
|
45
|
-
])
|
|
46
|
-
})
|
|
45
|
+
])
|
|
46
|
+
})
|
|
47
47
|
|
|
48
48
|
it('should create and retrieve resources with custom ID fields', async () => {
|
|
49
49
|
// Create country
|
|
50
50
|
const countryDoc = createJsonApiDocument('countries', {
|
|
51
51
|
name: 'United States',
|
|
52
52
|
code: 'US'
|
|
53
|
-
})
|
|
53
|
+
})
|
|
54
54
|
|
|
55
55
|
const countryResult = await api.resources.countries.post({
|
|
56
56
|
inputRecord: countryDoc
|
|
57
|
-
})
|
|
57
|
+
})
|
|
58
58
|
|
|
59
59
|
// Verify response has 'id' not 'country_id'
|
|
60
|
-
assert(countryResult.data.id, 'Should have id field')
|
|
61
|
-
assert(!countryResult.data.attributes.country_id, 'Should not expose country_id in attributes')
|
|
60
|
+
assert(countryResult.data.id, 'Should have id field')
|
|
61
|
+
assert(!countryResult.data.attributes.country_id, 'Should not expose country_id in attributes')
|
|
62
62
|
|
|
63
63
|
// Verify database has correct column when using legacy storage
|
|
64
64
|
if (!storageMode.isAnyApi()) {
|
|
65
|
-
const dbCountry = await knex('custom_id_countries').first()
|
|
66
|
-
assert(dbCountry.country_id, 'Database should have country_id column')
|
|
67
|
-
assert.equal(String(dbCountry.country_id), countryResult.data.id)
|
|
65
|
+
const dbCountry = await knex('custom_id_countries').first()
|
|
66
|
+
assert(dbCountry.country_id, 'Database should have country_id column')
|
|
67
|
+
assert.equal(String(dbCountry.country_id), countryResult.data.id)
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
// Test GET
|
|
71
71
|
const getResult = await api.resources.countries.get({
|
|
72
72
|
id: countryResult.data.id
|
|
73
|
-
})
|
|
73
|
+
})
|
|
74
74
|
|
|
75
|
-
assert.equal(getResult.data.id, countryResult.data.id)
|
|
76
|
-
assert.equal(getResult.data.attributes.name, 'United States')
|
|
77
|
-
})
|
|
75
|
+
assert.equal(getResult.data.id, countryResult.data.id)
|
|
76
|
+
assert.equal(getResult.data.attributes.name, 'United States')
|
|
77
|
+
})
|
|
78
78
|
|
|
79
79
|
it('should handle PATCH updates returning full record', async () => {
|
|
80
80
|
const country = await api.resources.countries.post({
|
|
@@ -82,7 +82,7 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
82
82
|
name: 'United States',
|
|
83
83
|
code: 'US'
|
|
84
84
|
})
|
|
85
|
-
})
|
|
85
|
+
})
|
|
86
86
|
|
|
87
87
|
const patchResult = await api.resources.countries.patch({
|
|
88
88
|
inputRecord: {
|
|
@@ -94,33 +94,33 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
-
})
|
|
97
|
+
})
|
|
98
98
|
|
|
99
99
|
// With returnRecordApi.patch = 'full', we should get the complete record
|
|
100
|
-
validateJsonApiStructure(patchResult, false)
|
|
101
|
-
assert.equal(patchResult.data.attributes.name, 'USA')
|
|
102
|
-
assert.equal(patchResult.data.attributes.code, 'US')
|
|
103
|
-
})
|
|
104
|
-
})
|
|
100
|
+
validateJsonApiStructure(patchResult, false)
|
|
101
|
+
assert.equal(patchResult.data.attributes.name, 'USA')
|
|
102
|
+
assert.equal(patchResult.data.attributes.code, 'US') // Original value preserved
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
105
|
|
|
106
106
|
describe('One-to-One (belongsTo) Relationships with custom idProperty', () => {
|
|
107
107
|
beforeEach(async () => {
|
|
108
108
|
await cleanTables(knex, [
|
|
109
|
-
'custom_id_countries', 'custom_id_publishers', 'custom_id_authors',
|
|
109
|
+
'custom_id_countries', 'custom_id_publishers', 'custom_id_authors',
|
|
110
110
|
'custom_id_books', 'custom_id_book_authors', 'custom_id_reviews'
|
|
111
|
-
])
|
|
112
|
-
})
|
|
111
|
+
])
|
|
112
|
+
})
|
|
113
113
|
|
|
114
114
|
it('should create resource with belongsTo relationship using custom IDs', async () => {
|
|
115
115
|
// Create a country first
|
|
116
116
|
const countryDoc = createJsonApiDocument('countries', {
|
|
117
117
|
name: 'United States',
|
|
118
118
|
code: 'US'
|
|
119
|
-
})
|
|
119
|
+
})
|
|
120
120
|
|
|
121
121
|
const countryResult = await api.resources.countries.post({
|
|
122
122
|
inputRecord: countryDoc
|
|
123
|
-
})
|
|
123
|
+
})
|
|
124
124
|
|
|
125
125
|
// Create publisher with country relationship
|
|
126
126
|
const publisherDoc = createJsonApiDocument('publishers',
|
|
@@ -130,22 +130,22 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
130
130
|
{
|
|
131
131
|
country: createRelationship(resourceIdentifier('countries', countryResult.data.id))
|
|
132
132
|
}
|
|
133
|
-
)
|
|
133
|
+
)
|
|
134
134
|
|
|
135
135
|
const publisherResult = await api.resources.publishers.post({
|
|
136
136
|
inputRecord: publisherDoc
|
|
137
|
-
})
|
|
137
|
+
})
|
|
138
138
|
|
|
139
139
|
// Verify response
|
|
140
|
-
validateJsonApiStructure(publisherResult, false)
|
|
141
|
-
assert.equal(publisherResult.data.type, 'publishers')
|
|
140
|
+
validateJsonApiStructure(publisherResult, false)
|
|
141
|
+
assert.equal(publisherResult.data.type, 'publishers')
|
|
142
142
|
assertResourceRelationship(publisherResult.data, 'country',
|
|
143
|
-
resourceIdentifier('countries', countryResult.data.id))
|
|
143
|
+
resourceIdentifier('countries', countryResult.data.id))
|
|
144
144
|
|
|
145
145
|
// Verify database foreign key uses country_id in legacy mode
|
|
146
146
|
if (!storageMode.isAnyApi()) {
|
|
147
|
-
const dbPublisher = await knex('custom_id_publishers').first()
|
|
148
|
-
assert.equal(String(dbPublisher.country_id), countryResult.data.id)
|
|
147
|
+
const dbPublisher = await knex('custom_id_publishers').first()
|
|
148
|
+
assert.equal(String(dbPublisher.country_id), countryResult.data.id)
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
// Verify through GET with include
|
|
@@ -154,24 +154,24 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
154
154
|
queryParams: {
|
|
155
155
|
include: ['country']
|
|
156
156
|
}
|
|
157
|
-
})
|
|
157
|
+
})
|
|
158
158
|
|
|
159
|
-
validateJsonApiStructure(getResult, false)
|
|
160
|
-
assert(getResult.included, 'Should have included data')
|
|
161
|
-
assert.equal(getResult.included.length, 1)
|
|
162
|
-
assert.equal(getResult.included[0].type, 'countries')
|
|
163
|
-
assert.equal(getResult.included[0].attributes.name, 'United States')
|
|
164
|
-
})
|
|
159
|
+
validateJsonApiStructure(getResult, false)
|
|
160
|
+
assert(getResult.included, 'Should have included data')
|
|
161
|
+
assert.equal(getResult.included.length, 1)
|
|
162
|
+
assert.equal(getResult.included[0].type, 'countries')
|
|
163
|
+
assert.equal(getResult.included[0].attributes.name, 'United States')
|
|
164
|
+
})
|
|
165
165
|
|
|
166
166
|
it('should update belongsTo relationship via PATCH', async () => {
|
|
167
167
|
// Create two countries
|
|
168
168
|
const country1 = await api.resources.countries.post({
|
|
169
169
|
inputRecord: createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
170
|
-
})
|
|
170
|
+
})
|
|
171
171
|
|
|
172
172
|
const country2 = await api.resources.countries.post({
|
|
173
173
|
inputRecord: createJsonApiDocument('countries', { name: 'UK', code: 'GB' })
|
|
174
|
-
})
|
|
174
|
+
})
|
|
175
175
|
|
|
176
176
|
// Create publisher with first country
|
|
177
177
|
const publisher = await api.resources.publishers.post({
|
|
@@ -179,7 +179,7 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
179
179
|
{ name: 'Test Publisher' },
|
|
180
180
|
{ country: createRelationship(resourceIdentifier('countries', country1.data.id)) }
|
|
181
181
|
)
|
|
182
|
-
})
|
|
182
|
+
})
|
|
183
183
|
|
|
184
184
|
// Update to second country
|
|
185
185
|
const patchDoc = {
|
|
@@ -190,65 +190,65 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
190
190
|
country: createRelationship(resourceIdentifier('countries', country2.data.id))
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
|
-
}
|
|
193
|
+
}
|
|
194
194
|
|
|
195
195
|
const patchResult = await api.resources.publishers.patch({
|
|
196
196
|
inputRecord: patchDoc
|
|
197
|
-
})
|
|
197
|
+
})
|
|
198
198
|
|
|
199
199
|
// Verify we get full record back
|
|
200
|
-
assert.equal(patchResult.data.attributes.name, 'Test Publisher')
|
|
200
|
+
assert.equal(patchResult.data.attributes.name, 'Test Publisher')
|
|
201
201
|
assertResourceRelationship(patchResult.data, 'country',
|
|
202
|
-
resourceIdentifier('countries', country2.data.id))
|
|
202
|
+
resourceIdentifier('countries', country2.data.id))
|
|
203
203
|
|
|
204
204
|
// Verify update via GET with include
|
|
205
205
|
const updated = await api.resources.publishers.get({
|
|
206
206
|
id: publisher.data.id,
|
|
207
207
|
queryParams: { include: ['country'] }
|
|
208
|
-
})
|
|
208
|
+
})
|
|
209
209
|
|
|
210
|
-
assert.equal(updated.included[0].id, country2.data.id)
|
|
211
|
-
assert.equal(updated.included[0].attributes.name, 'UK')
|
|
212
|
-
})
|
|
213
|
-
})
|
|
210
|
+
assert.equal(updated.included[0].id, country2.data.id)
|
|
211
|
+
assert.equal(updated.included[0].attributes.name, 'UK')
|
|
212
|
+
})
|
|
213
|
+
})
|
|
214
214
|
|
|
215
215
|
describe('Many-to-Many Relationships with custom idProperty', () => {
|
|
216
|
-
let countryId
|
|
217
|
-
let publisherId
|
|
218
|
-
let author1Id
|
|
219
|
-
let author2Id
|
|
220
|
-
let bookId
|
|
216
|
+
let countryId
|
|
217
|
+
let publisherId
|
|
218
|
+
let author1Id
|
|
219
|
+
let author2Id
|
|
220
|
+
let bookId
|
|
221
221
|
|
|
222
222
|
beforeEach(async () => {
|
|
223
223
|
await cleanTables(knex, [
|
|
224
|
-
'custom_id_countries', 'custom_id_publishers', 'custom_id_authors',
|
|
224
|
+
'custom_id_countries', 'custom_id_publishers', 'custom_id_authors',
|
|
225
225
|
'custom_id_books', 'custom_id_book_authors', 'custom_id_reviews'
|
|
226
|
-
])
|
|
226
|
+
])
|
|
227
227
|
|
|
228
228
|
// Setup test data
|
|
229
229
|
const country = await api.resources.countries.post({
|
|
230
230
|
inputRecord: createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
231
|
-
})
|
|
232
|
-
countryId = country.data.id
|
|
231
|
+
})
|
|
232
|
+
countryId = country.data.id
|
|
233
233
|
|
|
234
234
|
const publisher = await api.resources.publishers.post({
|
|
235
235
|
inputRecord: createJsonApiDocument('publishers',
|
|
236
236
|
{ name: 'Test Publisher' },
|
|
237
237
|
{ country: createRelationship(resourceIdentifier('countries', countryId)) }
|
|
238
238
|
)
|
|
239
|
-
})
|
|
240
|
-
publisherId = publisher.data.id
|
|
239
|
+
})
|
|
240
|
+
publisherId = publisher.data.id
|
|
241
241
|
|
|
242
242
|
const author1 = await api.resources.authors.post({
|
|
243
243
|
inputRecord: createJsonApiDocument('authors', { name: 'Author One' })
|
|
244
|
-
})
|
|
245
|
-
author1Id = author1.data.id
|
|
244
|
+
})
|
|
245
|
+
author1Id = author1.data.id
|
|
246
246
|
|
|
247
247
|
const author2 = await api.resources.authors.post({
|
|
248
248
|
inputRecord: createJsonApiDocument('authors', { name: 'Author Two' })
|
|
249
|
-
})
|
|
250
|
-
author2Id = author2.data.id
|
|
251
|
-
})
|
|
249
|
+
})
|
|
250
|
+
author2Id = author2.data.id
|
|
251
|
+
})
|
|
252
252
|
|
|
253
253
|
it('should create many-to-many relationships with custom IDs', async () => {
|
|
254
254
|
// Create book with multiple authors
|
|
@@ -262,40 +262,40 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
262
262
|
resourceIdentifier('authors', author2Id)
|
|
263
263
|
])
|
|
264
264
|
}
|
|
265
|
-
)
|
|
265
|
+
)
|
|
266
266
|
|
|
267
267
|
const bookResult = await api.resources.books.post({
|
|
268
268
|
inputRecord: bookDoc
|
|
269
|
-
})
|
|
269
|
+
})
|
|
270
270
|
|
|
271
|
-
bookId = bookResult.data.id
|
|
271
|
+
bookId = bookResult.data.id
|
|
272
272
|
|
|
273
273
|
// Verify pivot table has correct foreign keys
|
|
274
274
|
if (!storageMode.isAnyApi()) {
|
|
275
|
-
const pivotRecords = await knex('custom_id_book_authors').select('*')
|
|
276
|
-
assert.equal(pivotRecords.length, 2)
|
|
275
|
+
const pivotRecords = await knex('custom_id_book_authors').select('*')
|
|
276
|
+
assert.equal(pivotRecords.length, 2)
|
|
277
277
|
|
|
278
|
-
const bookIds = pivotRecords.map((r) => r.book_id)
|
|
279
|
-
const authorIds = pivotRecords.map((r) => r.author_id).sort()
|
|
278
|
+
const bookIds = pivotRecords.map((r) => r.book_id)
|
|
279
|
+
const authorIds = pivotRecords.map((r) => r.author_id).sort()
|
|
280
280
|
|
|
281
|
-
assert(bookIds.every((id) => String(id) === bookId))
|
|
282
|
-
assert.deepEqual(authorIds.map(String).sort(), [author1Id, author2Id].sort())
|
|
281
|
+
assert(bookIds.every((id) => String(id) === bookId))
|
|
282
|
+
assert.deepEqual(authorIds.map(String).sort(), [author1Id, author2Id].sort())
|
|
283
283
|
} else {
|
|
284
|
-
const pivotCount = await countRecords(knex, 'custom_id_book_authors')
|
|
285
|
-
assert.equal(pivotCount, 2)
|
|
284
|
+
const pivotCount = await countRecords(knex, 'custom_id_book_authors')
|
|
285
|
+
assert.equal(pivotCount, 2)
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
// Verify through GET with include
|
|
289
289
|
const getResult = await api.resources.books.get({
|
|
290
290
|
id: bookId,
|
|
291
291
|
queryParams: { include: ['authors'] }
|
|
292
|
-
})
|
|
292
|
+
})
|
|
293
293
|
|
|
294
|
-
assert(getResult.included)
|
|
295
|
-
assert.equal(getResult.included.length, 2)
|
|
296
|
-
const includedNames = getResult.included.map(a => a.attributes.name).sort()
|
|
297
|
-
assert.deepEqual(includedNames, ['Author One', 'Author Two'])
|
|
298
|
-
})
|
|
294
|
+
assert(getResult.included)
|
|
295
|
+
assert.equal(getResult.included.length, 2)
|
|
296
|
+
const includedNames = getResult.included.map(a => a.attributes.name).sort()
|
|
297
|
+
assert.deepEqual(includedNames, ['Author One', 'Author Two'])
|
|
298
|
+
})
|
|
299
299
|
|
|
300
300
|
it('should handle PATCH updates to many-to-many relationships', async () => {
|
|
301
301
|
// Create book with one author
|
|
@@ -307,7 +307,7 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
307
307
|
authors: createToManyRelationship([resourceIdentifier('authors', author1Id)])
|
|
308
308
|
}
|
|
309
309
|
)
|
|
310
|
-
})
|
|
310
|
+
})
|
|
311
311
|
|
|
312
312
|
// Update to have both authors
|
|
313
313
|
const patchDoc = {
|
|
@@ -321,65 +321,65 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
321
321
|
])
|
|
322
322
|
}
|
|
323
323
|
}
|
|
324
|
-
}
|
|
324
|
+
}
|
|
325
325
|
|
|
326
326
|
const patchResult = await api.resources.books.patch({
|
|
327
327
|
inputRecord: patchDoc,
|
|
328
328
|
queryParams: {
|
|
329
329
|
include: ['authors']
|
|
330
330
|
}
|
|
331
|
-
})
|
|
331
|
+
})
|
|
332
332
|
|
|
333
333
|
// Verify full record returned
|
|
334
|
-
assert.equal(patchResult.data.attributes.title, 'Test Book')
|
|
334
|
+
assert.equal(patchResult.data.attributes.title, 'Test Book')
|
|
335
335
|
// With include, we should get the authors relationship data
|
|
336
336
|
assertResourceRelationship(patchResult.data, 'authors',
|
|
337
|
-
[resourceIdentifier('authors', author1Id), resourceIdentifier('authors', author2Id)])
|
|
337
|
+
[resourceIdentifier('authors', author1Id), resourceIdentifier('authors', author2Id)])
|
|
338
338
|
|
|
339
339
|
// Verify update
|
|
340
340
|
const updated = await api.resources.books.get({
|
|
341
341
|
id: book.data.id,
|
|
342
342
|
queryParams: { include: ['authors'] }
|
|
343
|
-
})
|
|
343
|
+
})
|
|
344
344
|
|
|
345
|
-
assert.equal(updated.included.length, 2)
|
|
346
|
-
})
|
|
347
|
-
})
|
|
345
|
+
assert.equal(updated.included.length, 2)
|
|
346
|
+
})
|
|
347
|
+
})
|
|
348
348
|
|
|
349
349
|
describe('Polymorphic Relationships with custom idProperty', () => {
|
|
350
|
-
let bookId
|
|
351
|
-
let authorId
|
|
352
|
-
let publisherId
|
|
350
|
+
let bookId
|
|
351
|
+
let authorId
|
|
352
|
+
let publisherId
|
|
353
353
|
|
|
354
354
|
beforeEach(async () => {
|
|
355
355
|
await cleanTables(knex, [
|
|
356
|
-
'custom_id_countries', 'custom_id_publishers', 'custom_id_authors',
|
|
356
|
+
'custom_id_countries', 'custom_id_publishers', 'custom_id_authors',
|
|
357
357
|
'custom_id_books', 'custom_id_book_authors', 'custom_id_reviews'
|
|
358
|
-
])
|
|
358
|
+
])
|
|
359
359
|
|
|
360
360
|
// Create test resources
|
|
361
361
|
const country = await api.resources.countries.post({
|
|
362
362
|
inputRecord: createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
363
|
-
})
|
|
363
|
+
})
|
|
364
364
|
|
|
365
365
|
const book = await api.resources.books.post({
|
|
366
366
|
inputRecord: createJsonApiDocument('books',
|
|
367
367
|
{ title: 'Test Book' },
|
|
368
368
|
{ country: createRelationship(resourceIdentifier('countries', country.data.id)) }
|
|
369
369
|
)
|
|
370
|
-
})
|
|
371
|
-
bookId = book.data.id
|
|
370
|
+
})
|
|
371
|
+
bookId = book.data.id
|
|
372
372
|
|
|
373
373
|
const author = await api.resources.authors.post({
|
|
374
374
|
inputRecord: createJsonApiDocument('authors', { name: 'Test Author' })
|
|
375
|
-
})
|
|
376
|
-
authorId = author.data.id
|
|
375
|
+
})
|
|
376
|
+
authorId = author.data.id
|
|
377
377
|
|
|
378
378
|
const publisher = await api.resources.publishers.post({
|
|
379
379
|
inputRecord: createJsonApiDocument('publishers', { name: 'Test Publisher' })
|
|
380
|
-
})
|
|
381
|
-
publisherId = publisher.data.id
|
|
382
|
-
})
|
|
380
|
+
})
|
|
381
|
+
publisherId = publisher.data.id
|
|
382
|
+
})
|
|
383
383
|
|
|
384
384
|
it('should create polymorphic review for book with custom IDs', async () => {
|
|
385
385
|
const review = await api.resources.reviews.post({
|
|
@@ -391,19 +391,19 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
391
391
|
reviewable_type: 'books',
|
|
392
392
|
reviewable_id: bookId
|
|
393
393
|
})
|
|
394
|
-
})
|
|
394
|
+
})
|
|
395
395
|
|
|
396
|
-
validateJsonApiStructure(review)
|
|
397
|
-
assert.equal(review.data.attributes.reviewable_type, 'books')
|
|
398
|
-
assert.equal(String(review.data.attributes.reviewable_id), bookId)
|
|
396
|
+
validateJsonApiStructure(review)
|
|
397
|
+
assert.equal(review.data.attributes.reviewable_type, 'books')
|
|
398
|
+
assert.equal(String(review.data.attributes.reviewable_id), bookId)
|
|
399
399
|
|
|
400
400
|
if (!storageMode.isAnyApi()) {
|
|
401
|
-
const dbReview = await knex('custom_id_reviews').first()
|
|
402
|
-
assert.equal(dbReview.reviewable_type, 'books')
|
|
403
|
-
assert.equal(String(dbReview.reviewable_id), bookId)
|
|
404
|
-
assert(dbReview.review_id, 'Should have custom review_id column')
|
|
401
|
+
const dbReview = await knex('custom_id_reviews').first()
|
|
402
|
+
assert.equal(dbReview.reviewable_type, 'books')
|
|
403
|
+
assert.equal(String(dbReview.reviewable_id), bookId)
|
|
404
|
+
assert(dbReview.review_id, 'Should have custom review_id column')
|
|
405
405
|
}
|
|
406
|
-
})
|
|
406
|
+
})
|
|
407
407
|
|
|
408
408
|
it('should create polymorphic review for author with custom IDs', async () => {
|
|
409
409
|
const review = await api.resources.reviews.post({
|
|
@@ -415,11 +415,11 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
415
415
|
reviewable_type: 'authors',
|
|
416
416
|
reviewable_id: authorId
|
|
417
417
|
})
|
|
418
|
-
})
|
|
418
|
+
})
|
|
419
419
|
|
|
420
|
-
assert.equal(review.data.attributes.reviewable_type, 'authors')
|
|
421
|
-
assert.equal(String(review.data.attributes.reviewable_id), authorId)
|
|
422
|
-
})
|
|
420
|
+
assert.equal(review.data.attributes.reviewable_type, 'authors')
|
|
421
|
+
assert.equal(String(review.data.attributes.reviewable_id), authorId)
|
|
422
|
+
})
|
|
423
423
|
|
|
424
424
|
it('should query polymorphic reviews by type', async () => {
|
|
425
425
|
// Create reviews for different types
|
|
@@ -432,7 +432,7 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
432
432
|
reviewable_type: 'books',
|
|
433
433
|
reviewable_id: bookId
|
|
434
434
|
})
|
|
435
|
-
})
|
|
435
|
+
})
|
|
436
436
|
|
|
437
437
|
await api.resources.reviews.post({
|
|
438
438
|
inputRecord: createJsonApiDocument('reviews', {
|
|
@@ -443,43 +443,43 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
443
443
|
reviewable_type: 'authors',
|
|
444
444
|
reviewable_id: authorId
|
|
445
445
|
})
|
|
446
|
-
})
|
|
446
|
+
})
|
|
447
447
|
|
|
448
448
|
// Query only book reviews
|
|
449
449
|
const bookReviews = await api.resources.reviews.query({
|
|
450
450
|
queryParams: {
|
|
451
451
|
filters: { reviewable_type: 'books' }
|
|
452
452
|
}
|
|
453
|
-
})
|
|
454
|
-
assert.equal(bookReviews.data.length, 1, `Expected 1 book review, got ${bookReviews.data.length}`)
|
|
455
|
-
assert.equal(bookReviews.data[0].attributes.reviewable_type, 'books')
|
|
456
|
-
})
|
|
457
|
-
})
|
|
453
|
+
})
|
|
454
|
+
assert.equal(bookReviews.data.length, 1, `Expected 1 book review, got ${bookReviews.data.length}`)
|
|
455
|
+
assert.equal(bookReviews.data[0].attributes.reviewable_type, 'books')
|
|
456
|
+
})
|
|
457
|
+
})
|
|
458
458
|
|
|
459
459
|
describe('Nested Includes with custom idProperty', () => {
|
|
460
460
|
beforeEach(async () => {
|
|
461
461
|
await cleanTables(knex, [
|
|
462
|
-
'custom_id_countries', 'custom_id_publishers', 'custom_id_authors',
|
|
462
|
+
'custom_id_countries', 'custom_id_publishers', 'custom_id_authors',
|
|
463
463
|
'custom_id_books', 'custom_id_book_authors', 'custom_id_reviews'
|
|
464
|
-
])
|
|
465
|
-
})
|
|
464
|
+
])
|
|
465
|
+
})
|
|
466
466
|
|
|
467
467
|
it('should handle nested includes through custom ID relationships', async () => {
|
|
468
468
|
// Create hierarchy: Country -> Publisher -> Book -> Authors
|
|
469
469
|
const country = await api.resources.countries.post({
|
|
470
470
|
inputRecord: createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
471
|
-
})
|
|
471
|
+
})
|
|
472
472
|
|
|
473
473
|
const publisher = await api.resources.publishers.post({
|
|
474
474
|
inputRecord: createJsonApiDocument('publishers',
|
|
475
475
|
{ name: 'Test Publisher' },
|
|
476
476
|
{ country: createRelationship(resourceIdentifier('countries', country.data.id)) }
|
|
477
477
|
)
|
|
478
|
-
})
|
|
478
|
+
})
|
|
479
479
|
|
|
480
480
|
const author = await api.resources.authors.post({
|
|
481
481
|
inputRecord: createJsonApiDocument('authors', { name: 'Test Author' })
|
|
482
|
-
})
|
|
482
|
+
})
|
|
483
483
|
|
|
484
484
|
const book = await api.resources.books.post({
|
|
485
485
|
inputRecord: createJsonApiDocument('books',
|
|
@@ -490,50 +490,50 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
490
490
|
authors: createToManyRelationship([resourceIdentifier('authors', author.data.id)])
|
|
491
491
|
}
|
|
492
492
|
)
|
|
493
|
-
})
|
|
493
|
+
})
|
|
494
494
|
|
|
495
495
|
// Query with nested includes
|
|
496
496
|
const result = await api.resources.books.query({
|
|
497
497
|
queryParams: {
|
|
498
498
|
include: ['publisher.country', 'authors']
|
|
499
499
|
}
|
|
500
|
-
})
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
validateJsonApiStructure(result, true)
|
|
503
|
+
assert(result.included)
|
|
501
504
|
|
|
502
|
-
validateJsonApiStructure(result, true);
|
|
503
|
-
assert(result.included);
|
|
504
|
-
|
|
505
505
|
// Find each type in included
|
|
506
|
-
const includedPublisher = result.included.find(r => r.type === 'publishers')
|
|
507
|
-
const includedCountry = result.included.find(r => r.type === 'countries')
|
|
508
|
-
const includedAuthor = result.included.find(r => r.type === 'authors')
|
|
509
|
-
|
|
510
|
-
assert(includedPublisher, 'Should include publisher')
|
|
511
|
-
assert(includedCountry, 'Should include country')
|
|
512
|
-
assert(includedAuthor, 'Should include author')
|
|
513
|
-
|
|
506
|
+
const includedPublisher = result.included.find(r => r.type === 'publishers')
|
|
507
|
+
const includedCountry = result.included.find(r => r.type === 'countries')
|
|
508
|
+
const includedAuthor = result.included.find(r => r.type === 'authors')
|
|
509
|
+
|
|
510
|
+
assert(includedPublisher, 'Should include publisher')
|
|
511
|
+
assert(includedCountry, 'Should include country')
|
|
512
|
+
assert(includedAuthor, 'Should include author')
|
|
513
|
+
|
|
514
514
|
// Verify nested relationship
|
|
515
|
-
assert.equal(includedPublisher.relationships.country.data.id, country.data.id)
|
|
516
|
-
})
|
|
515
|
+
assert.equal(includedPublisher.relationships.country.data.id, country.data.id)
|
|
516
|
+
})
|
|
517
517
|
|
|
518
518
|
it('should handle 3+ levels of nested includes', async () => {
|
|
519
519
|
// Create deeper hierarchy
|
|
520
520
|
const country = await api.resources.countries.post({
|
|
521
521
|
inputRecord: createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
522
|
-
})
|
|
522
|
+
})
|
|
523
523
|
|
|
524
524
|
const author = await api.resources.authors.post({
|
|
525
|
-
inputRecord: createJsonApiDocument('authors',
|
|
525
|
+
inputRecord: createJsonApiDocument('authors',
|
|
526
526
|
{ name: 'Test Author' },
|
|
527
527
|
{ country: createRelationship(resourceIdentifier('countries', country.data.id)) }
|
|
528
528
|
)
|
|
529
|
-
})
|
|
529
|
+
})
|
|
530
530
|
|
|
531
531
|
const publisher = await api.resources.publishers.post({
|
|
532
532
|
inputRecord: createJsonApiDocument('publishers',
|
|
533
533
|
{ name: 'Test Publisher' },
|
|
534
534
|
{ country: createRelationship(resourceIdentifier('countries', country.data.id)) }
|
|
535
535
|
)
|
|
536
|
-
})
|
|
536
|
+
})
|
|
537
537
|
|
|
538
538
|
const book = await api.resources.books.post({
|
|
539
539
|
inputRecord: createJsonApiDocument('books',
|
|
@@ -544,52 +544,52 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
544
544
|
authors: createToManyRelationship([resourceIdentifier('authors', author.data.id)])
|
|
545
545
|
}
|
|
546
546
|
)
|
|
547
|
-
})
|
|
547
|
+
})
|
|
548
548
|
|
|
549
549
|
// Query with 3 levels: books -> authors -> country
|
|
550
550
|
const result = await api.resources.books.query({
|
|
551
551
|
queryParams: {
|
|
552
552
|
include: ['authors.country', 'publisher.country']
|
|
553
553
|
}
|
|
554
|
-
})
|
|
555
|
-
|
|
556
|
-
validateJsonApiStructure(result, true)
|
|
557
|
-
assert(result.included)
|
|
558
|
-
|
|
559
|
-
const types = result.included.map(r => r.type)
|
|
560
|
-
assert(types.includes('authors'), 'Should include authors')
|
|
561
|
-
assert(types.includes('publishers'), 'Should include publishers')
|
|
562
|
-
assert(types.includes('countries'), 'Should include countries')
|
|
563
|
-
})
|
|
564
|
-
})
|
|
554
|
+
})
|
|
555
|
+
|
|
556
|
+
validateJsonApiStructure(result, true)
|
|
557
|
+
assert(result.included)
|
|
558
|
+
|
|
559
|
+
const types = result.included.map(r => r.type)
|
|
560
|
+
assert(types.includes('authors'), 'Should include authors')
|
|
561
|
+
assert(types.includes('publishers'), 'Should include publishers')
|
|
562
|
+
assert(types.includes('countries'), 'Should include countries')
|
|
563
|
+
})
|
|
564
|
+
})
|
|
565
565
|
|
|
566
566
|
describe('Include with Sparse Fieldsets and custom idProperty', () => {
|
|
567
|
-
|
|
567
|
+
const testData = {}
|
|
568
568
|
|
|
569
569
|
beforeEach(async () => {
|
|
570
570
|
await cleanTables(knex, [
|
|
571
|
-
'custom_id_countries', 'custom_id_publishers', 'custom_id_authors',
|
|
571
|
+
'custom_id_countries', 'custom_id_publishers', 'custom_id_authors',
|
|
572
572
|
'custom_id_books', 'custom_id_book_authors', 'custom_id_reviews'
|
|
573
|
-
])
|
|
573
|
+
])
|
|
574
574
|
|
|
575
575
|
// Create test data
|
|
576
576
|
const country = await api.resources.countries.post({
|
|
577
577
|
inputRecord: createJsonApiDocument('countries', { name: 'Test Country', code: 'TC' })
|
|
578
|
-
})
|
|
579
|
-
testData.country = country.data
|
|
578
|
+
})
|
|
579
|
+
testData.country = country.data
|
|
580
580
|
|
|
581
581
|
const publisher = await api.resources.publishers.post({
|
|
582
582
|
inputRecord: createJsonApiDocument('publishers',
|
|
583
583
|
{ name: 'Test Publisher' },
|
|
584
584
|
{ country: createRelationship(resourceIdentifier('countries', testData.country.id)) }
|
|
585
585
|
)
|
|
586
|
-
})
|
|
587
|
-
testData.publisher = publisher.data
|
|
586
|
+
})
|
|
587
|
+
testData.publisher = publisher.data
|
|
588
588
|
|
|
589
589
|
const author = await api.resources.authors.post({
|
|
590
590
|
inputRecord: createJsonApiDocument('authors', { name: 'Test Author', biography: 'A great author' })
|
|
591
|
-
})
|
|
592
|
-
testData.author = author.data
|
|
591
|
+
})
|
|
592
|
+
testData.author = author.data
|
|
593
593
|
|
|
594
594
|
const book = await api.resources.books.post({
|
|
595
595
|
inputRecord: createJsonApiDocument('books',
|
|
@@ -600,9 +600,9 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
600
600
|
authors: createToManyRelationship([resourceIdentifier('authors', testData.author.id)])
|
|
601
601
|
}
|
|
602
602
|
)
|
|
603
|
-
})
|
|
604
|
-
testData.book = book.data
|
|
605
|
-
})
|
|
603
|
+
})
|
|
604
|
+
testData.book = book.data
|
|
605
|
+
})
|
|
606
606
|
|
|
607
607
|
it('should combine sparse fieldsets with includes using custom IDs', async () => {
|
|
608
608
|
const result = await api.resources.books.get({
|
|
@@ -615,52 +615,52 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
615
615
|
authors: 'name' // Exclude biography
|
|
616
616
|
}
|
|
617
617
|
}
|
|
618
|
-
})
|
|
618
|
+
})
|
|
619
619
|
|
|
620
620
|
// Check main resource has only requested fields
|
|
621
|
-
assert(result.data.attributes.title)
|
|
622
|
-
assert(!result.data.attributes.isbn)
|
|
623
|
-
assert(!result.data.attributes.pages)
|
|
621
|
+
assert(result.data.attributes.title)
|
|
622
|
+
assert(!result.data.attributes.isbn)
|
|
623
|
+
assert(!result.data.attributes.pages)
|
|
624
624
|
|
|
625
625
|
// Check included resources have only requested fields
|
|
626
|
-
const publisher = result.included.find(r => r.type === 'publishers')
|
|
627
|
-
assert(publisher.attributes.name)
|
|
628
|
-
assert(publisher.id)
|
|
626
|
+
const publisher = result.included.find(r => r.type === 'publishers')
|
|
627
|
+
assert(publisher.attributes.name)
|
|
628
|
+
assert(publisher.id) // ID is always included
|
|
629
629
|
|
|
630
|
-
const author = result.included.find(r => r.type === 'authors')
|
|
631
|
-
assert(author.attributes.name)
|
|
632
|
-
assert(!author.attributes.biography)
|
|
633
|
-
assert(author.id)
|
|
634
|
-
})
|
|
635
|
-
})
|
|
630
|
+
const author = result.included.find(r => r.type === 'authors')
|
|
631
|
+
assert(author.attributes.name)
|
|
632
|
+
assert(!author.attributes.biography)
|
|
633
|
+
assert(author.id)
|
|
634
|
+
})
|
|
635
|
+
})
|
|
636
636
|
|
|
637
637
|
describe('Include Depth Validation with custom idProperty', () => {
|
|
638
638
|
beforeEach(async () => {
|
|
639
639
|
await cleanTables(knex, [
|
|
640
|
-
'custom_id_countries', 'custom_id_publishers', 'custom_id_authors',
|
|
640
|
+
'custom_id_countries', 'custom_id_publishers', 'custom_id_authors',
|
|
641
641
|
'custom_id_books', 'custom_id_book_authors', 'custom_id_reviews'
|
|
642
|
-
])
|
|
643
|
-
})
|
|
642
|
+
])
|
|
643
|
+
})
|
|
644
644
|
|
|
645
645
|
it('should enforce include depth limits', async () => {
|
|
646
646
|
// Create deep hierarchy
|
|
647
647
|
const country = await api.resources.countries.post({
|
|
648
648
|
inputRecord: createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
649
|
-
})
|
|
649
|
+
})
|
|
650
650
|
|
|
651
651
|
const publisher = await api.resources.publishers.post({
|
|
652
652
|
inputRecord: createJsonApiDocument('publishers',
|
|
653
653
|
{ name: 'Test Publisher' },
|
|
654
654
|
{ country: createRelationship(resourceIdentifier('countries', country.data.id)) }
|
|
655
655
|
)
|
|
656
|
-
})
|
|
656
|
+
})
|
|
657
657
|
|
|
658
658
|
const author = await api.resources.authors.post({
|
|
659
659
|
inputRecord: createJsonApiDocument('authors',
|
|
660
660
|
{ name: 'Test Author' },
|
|
661
661
|
{ country: createRelationship(resourceIdentifier('countries', country.data.id)) }
|
|
662
662
|
)
|
|
663
|
-
})
|
|
663
|
+
})
|
|
664
664
|
|
|
665
665
|
const book = await api.resources.books.post({
|
|
666
666
|
inputRecord: createJsonApiDocument('books',
|
|
@@ -671,7 +671,7 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
671
671
|
authors: createToManyRelationship([resourceIdentifier('authors', author.data.id)])
|
|
672
672
|
}
|
|
673
673
|
)
|
|
674
|
-
})
|
|
674
|
+
})
|
|
675
675
|
|
|
676
676
|
// Test depth limit (default is 3)
|
|
677
677
|
// This should work: books -> authors -> country (depth 2)
|
|
@@ -679,8 +679,8 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
679
679
|
queryParams: {
|
|
680
680
|
include: ['authors.country']
|
|
681
681
|
}
|
|
682
|
-
})
|
|
683
|
-
assert(validDepth.data)
|
|
682
|
+
})
|
|
683
|
+
assert(validDepth.data)
|
|
684
684
|
|
|
685
685
|
// This should fail: trying to go too deep (if we had deeper relationships)
|
|
686
686
|
// Since our test data doesn't have 4+ levels, we test that 3 levels work
|
|
@@ -688,28 +688,28 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
688
688
|
queryParams: {
|
|
689
689
|
include: ['publisher.country', 'authors.country']
|
|
690
690
|
}
|
|
691
|
-
})
|
|
692
|
-
assert(maxDepth.data)
|
|
693
|
-
})
|
|
694
|
-
})
|
|
691
|
+
})
|
|
692
|
+
assert(maxDepth.data)
|
|
693
|
+
})
|
|
694
|
+
})
|
|
695
695
|
|
|
696
696
|
describe('Complex Scenarios with custom idProperty', () => {
|
|
697
697
|
beforeEach(async () => {
|
|
698
698
|
await cleanTables(knex, [
|
|
699
|
-
'custom_id_countries', 'custom_id_publishers', 'custom_id_authors',
|
|
699
|
+
'custom_id_countries', 'custom_id_publishers', 'custom_id_authors',
|
|
700
700
|
'custom_id_books', 'custom_id_book_authors', 'custom_id_reviews'
|
|
701
|
-
])
|
|
702
|
-
})
|
|
701
|
+
])
|
|
702
|
+
})
|
|
703
703
|
|
|
704
704
|
it('should handle complex queries with filters, includes, and custom IDs', async () => {
|
|
705
705
|
// Create multiple countries
|
|
706
706
|
const usa = await api.resources.countries.post({
|
|
707
707
|
inputRecord: createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
708
|
-
})
|
|
708
|
+
})
|
|
709
709
|
|
|
710
710
|
const uk = await api.resources.countries.post({
|
|
711
711
|
inputRecord: createJsonApiDocument('countries', { name: 'UK', code: 'GB' })
|
|
712
|
-
})
|
|
712
|
+
})
|
|
713
713
|
|
|
714
714
|
// Create publishers in different countries
|
|
715
715
|
const usPublisher = await api.resources.publishers.post({
|
|
@@ -717,23 +717,23 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
717
717
|
{ name: 'US Publisher' },
|
|
718
718
|
{ country: createRelationship(resourceIdentifier('countries', usa.data.id)) }
|
|
719
719
|
)
|
|
720
|
-
})
|
|
720
|
+
})
|
|
721
721
|
|
|
722
722
|
const ukPublisher = await api.resources.publishers.post({
|
|
723
723
|
inputRecord: createJsonApiDocument('publishers',
|
|
724
724
|
{ name: 'UK Publisher' },
|
|
725
725
|
{ country: createRelationship(resourceIdentifier('countries', uk.data.id)) }
|
|
726
726
|
)
|
|
727
|
-
})
|
|
727
|
+
})
|
|
728
728
|
|
|
729
729
|
// Create authors
|
|
730
730
|
const author1 = await api.resources.authors.post({
|
|
731
731
|
inputRecord: createJsonApiDocument('authors', { name: 'Author One' })
|
|
732
|
-
})
|
|
732
|
+
})
|
|
733
733
|
|
|
734
734
|
const author2 = await api.resources.authors.post({
|
|
735
735
|
inputRecord: createJsonApiDocument('authors', { name: 'Author Two' })
|
|
736
|
-
})
|
|
736
|
+
})
|
|
737
737
|
|
|
738
738
|
// Create books
|
|
739
739
|
await api.resources.books.post({
|
|
@@ -745,7 +745,7 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
745
745
|
authors: createToManyRelationship([resourceIdentifier('authors', author1.data.id)])
|
|
746
746
|
}
|
|
747
747
|
)
|
|
748
|
-
})
|
|
748
|
+
})
|
|
749
749
|
|
|
750
750
|
await api.resources.books.post({
|
|
751
751
|
inputRecord: createJsonApiDocument('books',
|
|
@@ -759,7 +759,7 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
759
759
|
])
|
|
760
760
|
}
|
|
761
761
|
)
|
|
762
|
-
})
|
|
762
|
+
})
|
|
763
763
|
|
|
764
764
|
// Complex query: US books with their publishers and authors
|
|
765
765
|
const result = await api.resources.books.query({
|
|
@@ -768,30 +768,30 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
768
768
|
include: ['publisher.country', 'authors'],
|
|
769
769
|
sort: ['title']
|
|
770
770
|
}
|
|
771
|
-
})
|
|
771
|
+
})
|
|
772
|
+
|
|
773
|
+
assert.equal(result.data.length, 1)
|
|
774
|
+
assert.equal(result.data[0].attributes.title, 'US Book 1')
|
|
772
775
|
|
|
773
|
-
assert.equal(result.data.length, 1);
|
|
774
|
-
assert.equal(result.data[0].attributes.title, 'US Book 1');
|
|
775
|
-
|
|
776
776
|
// Verify includes
|
|
777
|
-
const includedTypes = result.included.map(r => r.type)
|
|
778
|
-
assert(includedTypes.includes('publishers'))
|
|
779
|
-
assert(includedTypes.includes('countries'))
|
|
780
|
-
assert(includedTypes.includes('authors'))
|
|
781
|
-
})
|
|
777
|
+
const includedTypes = result.included.map(r => r.type)
|
|
778
|
+
assert(includedTypes.includes('publishers'))
|
|
779
|
+
assert(includedTypes.includes('countries'))
|
|
780
|
+
assert(includedTypes.includes('authors'))
|
|
781
|
+
})
|
|
782
782
|
|
|
783
783
|
it('should handle bulk operations with custom IDs', async () => {
|
|
784
784
|
const country = await api.resources.countries.post({
|
|
785
785
|
inputRecord: createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
786
|
-
})
|
|
786
|
+
})
|
|
787
787
|
|
|
788
788
|
// Create multiple authors
|
|
789
|
-
const authors = []
|
|
789
|
+
const authors = []
|
|
790
790
|
for (let i = 1; i <= 3; i++) {
|
|
791
791
|
const author = await api.resources.authors.post({
|
|
792
792
|
inputRecord: createJsonApiDocument('authors', { name: `Author ${i}` })
|
|
793
|
-
})
|
|
794
|
-
authors.push(author.data)
|
|
793
|
+
})
|
|
794
|
+
authors.push(author.data)
|
|
795
795
|
}
|
|
796
796
|
|
|
797
797
|
// Create book with all authors
|
|
@@ -805,29 +805,29 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
805
805
|
)
|
|
806
806
|
}
|
|
807
807
|
)
|
|
808
|
-
})
|
|
808
|
+
})
|
|
809
809
|
|
|
810
810
|
// Verify all relationships were created
|
|
811
811
|
const bookWithAuthors = await api.resources.books.get({
|
|
812
812
|
id: book.data.id,
|
|
813
813
|
queryParams: { include: ['authors'] }
|
|
814
|
-
})
|
|
814
|
+
})
|
|
815
|
+
|
|
816
|
+
assert.equal(bookWithAuthors.included.length, 3)
|
|
815
817
|
|
|
816
|
-
assert.equal(bookWithAuthors.included.length, 3);
|
|
817
|
-
|
|
818
818
|
// Verify pivot table
|
|
819
|
-
const pivotCount = await countRecords(knex, 'custom_id_book_authors')
|
|
820
|
-
assert.equal(pivotCount, 3)
|
|
821
|
-
})
|
|
819
|
+
const pivotCount = await countRecords(knex, 'custom_id_book_authors')
|
|
820
|
+
assert.equal(pivotCount, 3)
|
|
821
|
+
})
|
|
822
822
|
|
|
823
823
|
it('should handle DELETE operations with custom IDs', async () => {
|
|
824
824
|
const country = await api.resources.countries.post({
|
|
825
825
|
inputRecord: createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
826
|
-
})
|
|
826
|
+
})
|
|
827
827
|
|
|
828
828
|
const author = await api.resources.authors.post({
|
|
829
829
|
inputRecord: createJsonApiDocument('authors', { name: 'Test Author' })
|
|
830
|
-
})
|
|
830
|
+
})
|
|
831
831
|
|
|
832
832
|
const book = await api.resources.books.post({
|
|
833
833
|
inputRecord: createJsonApiDocument('books',
|
|
@@ -837,41 +837,41 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
837
837
|
authors: createToManyRelationship([resourceIdentifier('authors', author.data.id)])
|
|
838
838
|
}
|
|
839
839
|
)
|
|
840
|
-
})
|
|
840
|
+
})
|
|
841
841
|
|
|
842
842
|
// Delete the book
|
|
843
|
-
await api.resources.books.delete({ id: book.data.id })
|
|
843
|
+
await api.resources.books.delete({ id: book.data.id })
|
|
844
844
|
|
|
845
845
|
// Verify book is deleted
|
|
846
846
|
try {
|
|
847
|
-
await api.resources.books.get({ id: book.data.id })
|
|
848
|
-
assert.fail('Should have thrown error')
|
|
847
|
+
await api.resources.books.get({ id: book.data.id })
|
|
848
|
+
assert.fail('Should have thrown error')
|
|
849
849
|
} catch (error) {
|
|
850
|
-
assert(error.message.includes('not found') || error.message.includes('404'))
|
|
850
|
+
assert(error.message.includes('not found') || error.message.includes('404'))
|
|
851
851
|
}
|
|
852
852
|
|
|
853
853
|
// The REST API correctly does NOT cascade delete pivot records
|
|
854
854
|
// This is intentional - automatic cascade deletion would be dangerous
|
|
855
855
|
// Pivot records remain and should be cleaned up by application logic if needed
|
|
856
|
-
const pivotCount = await countRecords(knex, 'custom_id_book_authors')
|
|
857
|
-
assert.equal(pivotCount, 1, 'Pivot record should still exist')
|
|
856
|
+
const pivotCount = await countRecords(knex, 'custom_id_book_authors')
|
|
857
|
+
assert.equal(pivotCount, 1, 'Pivot record should still exist')
|
|
858
858
|
|
|
859
859
|
// Verify author still exists
|
|
860
|
-
const authorStillExists = await api.resources.authors.get({ id: author.data.id })
|
|
861
|
-
assert(authorStillExists.data)
|
|
862
|
-
})
|
|
860
|
+
const authorStillExists = await api.resources.authors.get({ id: author.data.id })
|
|
861
|
+
assert(authorStillExists.data)
|
|
862
|
+
})
|
|
863
863
|
|
|
864
864
|
it('should handle PUT operations with custom IDs', async () => {
|
|
865
865
|
const country = await api.resources.countries.post({
|
|
866
866
|
inputRecord: createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
867
|
-
})
|
|
867
|
+
})
|
|
868
868
|
|
|
869
869
|
const publisher = await api.resources.publishers.post({
|
|
870
870
|
inputRecord: createJsonApiDocument('publishers',
|
|
871
871
|
{ name: 'Original Publisher' },
|
|
872
872
|
{ country: createRelationship(resourceIdentifier('countries', country.data.id)) }
|
|
873
873
|
)
|
|
874
|
-
})
|
|
874
|
+
})
|
|
875
875
|
|
|
876
876
|
// PUT to completely replace the publisher
|
|
877
877
|
const putResult = await api.resources.publishers.put({
|
|
@@ -887,13 +887,13 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
887
887
|
}
|
|
888
888
|
}
|
|
889
889
|
}
|
|
890
|
-
})
|
|
890
|
+
})
|
|
891
891
|
|
|
892
892
|
// Verify full record returned
|
|
893
|
-
validateJsonApiStructure(putResult, false)
|
|
894
|
-
assert.equal(putResult.data.attributes.name, 'Replaced Publisher')
|
|
893
|
+
validateJsonApiStructure(putResult, false)
|
|
894
|
+
assert.equal(putResult.data.attributes.name, 'Replaced Publisher')
|
|
895
895
|
assertResourceRelationship(putResult.data, 'country',
|
|
896
|
-
resourceIdentifier('countries', country.data.id))
|
|
897
|
-
})
|
|
898
|
-
})
|
|
899
|
-
})
|
|
896
|
+
resourceIdentifier('countries', country.data.id))
|
|
897
|
+
})
|
|
898
|
+
})
|
|
899
|
+
})
|