json-rest-api 1.0.13 → 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 +13 -0
- 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 +9 -5
- 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 { createBasicApi, createExtendedApi } 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 { createBasicApi, createExtendedApi } from './fixtures/api-configs.js'
|
|
5
5
|
import {
|
|
6
6
|
validateJsonApiStructure,
|
|
7
7
|
resourceIdentifier,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
createToManyRelationship,
|
|
13
13
|
assertResourceAttributes,
|
|
14
14
|
assertResourceRelationship
|
|
15
|
-
} from './helpers/test-utils.js'
|
|
15
|
+
} from './helpers/test-utils.js'
|
|
16
16
|
|
|
17
17
|
// Create Knex instance for tests
|
|
18
18
|
const knex = knexLib({
|
|
@@ -21,43 +21,43 @@ const knex = knexLib({
|
|
|
21
21
|
filename: ':memory:'
|
|
22
22
|
},
|
|
23
23
|
useNullAsDefault: true
|
|
24
|
-
})
|
|
24
|
+
})
|
|
25
25
|
|
|
26
26
|
// API instances that persist across tests
|
|
27
|
-
let basicApi
|
|
28
|
-
let extendedApi
|
|
27
|
+
let basicApi
|
|
28
|
+
let extendedApi
|
|
29
29
|
|
|
30
30
|
describe('Relationship Operations', () => {
|
|
31
31
|
before(async () => {
|
|
32
32
|
// Initialize APIs once
|
|
33
|
-
basicApi = await createBasicApi(knex)
|
|
34
|
-
extendedApi = await createExtendedApi(knex)
|
|
35
|
-
})
|
|
33
|
+
basicApi = await createBasicApi(knex)
|
|
34
|
+
extendedApi = await createExtendedApi(knex)
|
|
35
|
+
})
|
|
36
36
|
|
|
37
37
|
after(async () => {
|
|
38
38
|
// Close database connection to allow tests to exit
|
|
39
|
-
await knex.destroy()
|
|
40
|
-
})
|
|
39
|
+
await knex.destroy()
|
|
40
|
+
})
|
|
41
41
|
|
|
42
42
|
describe('One-to-One (belongsTo) Relationships', () => {
|
|
43
43
|
beforeEach(async () => {
|
|
44
44
|
// Clean all tables before each test
|
|
45
45
|
await cleanTables(knex, [
|
|
46
46
|
'basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors'
|
|
47
|
-
])
|
|
48
|
-
})
|
|
47
|
+
])
|
|
48
|
+
})
|
|
49
49
|
|
|
50
50
|
it('should create resource with belongsTo relationship', async () => {
|
|
51
51
|
// Create a country first
|
|
52
52
|
const countryDoc = createJsonApiDocument('countries', {
|
|
53
53
|
name: 'United States',
|
|
54
54
|
code: 'US'
|
|
55
|
-
})
|
|
55
|
+
})
|
|
56
56
|
|
|
57
57
|
const countryResult = await basicApi.resources.countries.post({
|
|
58
58
|
inputRecord: countryDoc,
|
|
59
59
|
simplified: false
|
|
60
|
-
})
|
|
60
|
+
})
|
|
61
61
|
|
|
62
62
|
// Create publisher with country relationship
|
|
63
63
|
const publisherDoc = createJsonApiDocument('publishers',
|
|
@@ -67,18 +67,18 @@ describe('Relationship Operations', () => {
|
|
|
67
67
|
{
|
|
68
68
|
country: createRelationship(resourceIdentifier('countries', countryResult.data.id))
|
|
69
69
|
}
|
|
70
|
-
)
|
|
70
|
+
)
|
|
71
71
|
|
|
72
72
|
const publisherResult = await basicApi.resources.publishers.post({
|
|
73
73
|
inputRecord: publisherDoc,
|
|
74
74
|
simplified: false
|
|
75
|
-
})
|
|
75
|
+
})
|
|
76
76
|
|
|
77
77
|
// Verify response
|
|
78
|
-
validateJsonApiStructure(publisherResult, false)
|
|
79
|
-
assert.equal(publisherResult.data.type, 'publishers')
|
|
78
|
+
validateJsonApiStructure(publisherResult, false)
|
|
79
|
+
assert.equal(publisherResult.data.type, 'publishers')
|
|
80
80
|
assertResourceRelationship(publisherResult.data, 'country',
|
|
81
|
-
resourceIdentifier('countries', countryResult.data.id))
|
|
81
|
+
resourceIdentifier('countries', countryResult.data.id))
|
|
82
82
|
|
|
83
83
|
// Verify through GET with include
|
|
84
84
|
const getResult = await basicApi.resources.publishers.get({
|
|
@@ -87,40 +87,40 @@ describe('Relationship Operations', () => {
|
|
|
87
87
|
include: ['country']
|
|
88
88
|
},
|
|
89
89
|
simplified: false
|
|
90
|
-
})
|
|
90
|
+
})
|
|
91
91
|
|
|
92
|
-
validateJsonApiStructure(getResult, false)
|
|
93
|
-
assert(getResult.included, 'Should have included data')
|
|
94
|
-
assert.equal(getResult.included.length, 1)
|
|
95
|
-
assert.equal(getResult.included[0].type, 'countries')
|
|
96
|
-
assert.equal(getResult.included[0].attributes.name, 'United States')
|
|
97
|
-
})
|
|
92
|
+
validateJsonApiStructure(getResult, false)
|
|
93
|
+
assert(getResult.included, 'Should have included data')
|
|
94
|
+
assert.equal(getResult.included.length, 1)
|
|
95
|
+
assert.equal(getResult.included[0].type, 'countries')
|
|
96
|
+
assert.equal(getResult.included[0].attributes.name, 'United States')
|
|
97
|
+
})
|
|
98
98
|
|
|
99
99
|
it('should update belongsTo relationship via PATCH', async () => {
|
|
100
100
|
// Create two countries
|
|
101
|
-
const usDoc = createJsonApiDocument('countries', { name: 'United States', code: 'US' })
|
|
102
|
-
const ukDoc = createJsonApiDocument('countries', { name: 'United Kingdom', code: 'UK' })
|
|
101
|
+
const usDoc = createJsonApiDocument('countries', { name: 'United States', code: 'US' })
|
|
102
|
+
const ukDoc = createJsonApiDocument('countries', { name: 'United Kingdom', code: 'UK' })
|
|
103
103
|
|
|
104
104
|
const usResult = await basicApi.resources.countries.post({
|
|
105
105
|
inputRecord: usDoc,
|
|
106
106
|
simplified: false
|
|
107
|
-
})
|
|
107
|
+
})
|
|
108
108
|
|
|
109
109
|
const ukResult = await basicApi.resources.countries.post({
|
|
110
110
|
inputRecord: ukDoc,
|
|
111
111
|
simplified: false
|
|
112
|
-
})
|
|
112
|
+
})
|
|
113
113
|
|
|
114
114
|
// Create publisher with US country
|
|
115
115
|
const publisherDoc = createJsonApiDocument('publishers',
|
|
116
116
|
{ name: 'Test Publisher' },
|
|
117
117
|
{ country: createRelationship(resourceIdentifier('countries', usResult.data.id)) }
|
|
118
|
-
)
|
|
118
|
+
)
|
|
119
119
|
|
|
120
120
|
const publisherResult = await basicApi.resources.publishers.post({
|
|
121
121
|
inputRecord: publisherDoc,
|
|
122
122
|
simplified: false
|
|
123
|
-
})
|
|
123
|
+
})
|
|
124
124
|
|
|
125
125
|
// Update publisher to UK country
|
|
126
126
|
const patchDoc = {
|
|
@@ -131,43 +131,43 @@ describe('Relationship Operations', () => {
|
|
|
131
131
|
country: createRelationship(resourceIdentifier('countries', ukResult.data.id))
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
|
-
}
|
|
134
|
+
}
|
|
135
135
|
|
|
136
136
|
await basicApi.resources.publishers.patch({
|
|
137
137
|
id: publisherResult.data.id,
|
|
138
138
|
inputRecord: patchDoc,
|
|
139
139
|
simplified: false
|
|
140
|
-
})
|
|
140
|
+
})
|
|
141
141
|
|
|
142
142
|
// Verify update
|
|
143
143
|
const getResult = await basicApi.resources.publishers.get({
|
|
144
144
|
id: publisherResult.data.id,
|
|
145
145
|
queryParams: { include: ['country'] },
|
|
146
146
|
simplified: false
|
|
147
|
-
})
|
|
147
|
+
})
|
|
148
148
|
|
|
149
149
|
assertResourceRelationship(getResult.data, 'country',
|
|
150
|
-
resourceIdentifier('countries', ukResult.data.id))
|
|
151
|
-
assert.equal(getResult.included[0].attributes.code, 'UK')
|
|
152
|
-
})
|
|
150
|
+
resourceIdentifier('countries', ukResult.data.id))
|
|
151
|
+
assert.equal(getResult.included[0].attributes.code, 'UK')
|
|
152
|
+
})
|
|
153
153
|
|
|
154
154
|
it('should clear belongsTo relationship with null', async () => {
|
|
155
155
|
// Create country and publisher
|
|
156
|
-
const countryDoc = createJsonApiDocument('countries', { name: 'France', code: 'FR' })
|
|
156
|
+
const countryDoc = createJsonApiDocument('countries', { name: 'France', code: 'FR' })
|
|
157
157
|
const countryResult = await basicApi.resources.countries.post({
|
|
158
158
|
inputRecord: countryDoc,
|
|
159
159
|
simplified: false
|
|
160
|
-
})
|
|
160
|
+
})
|
|
161
161
|
|
|
162
162
|
const publisherDoc = createJsonApiDocument('publishers',
|
|
163
163
|
{ name: 'French Publisher' },
|
|
164
164
|
{ country: createRelationship(resourceIdentifier('countries', countryResult.data.id)) }
|
|
165
|
-
)
|
|
165
|
+
)
|
|
166
166
|
|
|
167
167
|
const publisherResult = await basicApi.resources.publishers.post({
|
|
168
168
|
inputRecord: publisherDoc,
|
|
169
169
|
simplified: false
|
|
170
|
-
})
|
|
170
|
+
})
|
|
171
171
|
|
|
172
172
|
// Clear the relationship
|
|
173
173
|
const patchDoc = {
|
|
@@ -178,53 +178,53 @@ describe('Relationship Operations', () => {
|
|
|
178
178
|
country: { data: null }
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
|
-
}
|
|
181
|
+
}
|
|
182
182
|
|
|
183
183
|
await basicApi.resources.publishers.patch({
|
|
184
184
|
id: publisherResult.data.id,
|
|
185
185
|
inputRecord: patchDoc,
|
|
186
186
|
simplified: false
|
|
187
|
-
})
|
|
187
|
+
})
|
|
188
188
|
|
|
189
189
|
// Verify relationship cleared
|
|
190
190
|
const getResult = await basicApi.resources.publishers.get({
|
|
191
191
|
id: publisherResult.data.id,
|
|
192
192
|
simplified: false
|
|
193
|
-
})
|
|
193
|
+
})
|
|
194
194
|
|
|
195
|
-
assert(getResult.data.relationships)
|
|
196
|
-
assert.equal(getResult.data.relationships.country.data, null)
|
|
197
|
-
})
|
|
198
|
-
})
|
|
195
|
+
assert(getResult.data.relationships)
|
|
196
|
+
assert.equal(getResult.data.relationships.country.data, null)
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
199
|
|
|
200
200
|
describe('Many-to-Many Relationships', () => {
|
|
201
201
|
beforeEach(async () => {
|
|
202
202
|
await cleanTables(knex, [
|
|
203
203
|
'basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors'
|
|
204
|
-
])
|
|
205
|
-
})
|
|
204
|
+
])
|
|
205
|
+
})
|
|
206
206
|
|
|
207
207
|
it('should create book with multiple authors (many-to-many)', async () => {
|
|
208
208
|
// Create country for book
|
|
209
|
-
const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
209
|
+
const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
210
210
|
const countryResult = await basicApi.resources.countries.post({
|
|
211
211
|
inputRecord: countryDoc,
|
|
212
212
|
simplified: false
|
|
213
|
-
})
|
|
213
|
+
})
|
|
214
214
|
|
|
215
215
|
// Create authors
|
|
216
|
-
const author1Doc = createJsonApiDocument('authors', { name: 'Stephen King' })
|
|
217
|
-
const author2Doc = createJsonApiDocument('authors', { name: 'Peter Straub' })
|
|
216
|
+
const author1Doc = createJsonApiDocument('authors', { name: 'Stephen King' })
|
|
217
|
+
const author2Doc = createJsonApiDocument('authors', { name: 'Peter Straub' })
|
|
218
218
|
|
|
219
219
|
const author1Result = await basicApi.resources.authors.post({
|
|
220
220
|
inputRecord: author1Doc,
|
|
221
221
|
simplified: false
|
|
222
|
-
})
|
|
222
|
+
})
|
|
223
223
|
|
|
224
224
|
const author2Result = await basicApi.resources.authors.post({
|
|
225
225
|
inputRecord: author2Doc,
|
|
226
226
|
simplified: false
|
|
227
|
-
})
|
|
227
|
+
})
|
|
228
228
|
|
|
229
229
|
// Create book with authors
|
|
230
230
|
const bookDoc = createJsonApiDocument('books',
|
|
@@ -238,56 +238,56 @@ describe('Relationship Operations', () => {
|
|
|
238
238
|
resourceIdentifier('authors', author2Result.data.id)
|
|
239
239
|
])
|
|
240
240
|
}
|
|
241
|
-
)
|
|
241
|
+
)
|
|
242
242
|
|
|
243
243
|
const bookResult = await basicApi.resources.books.post({
|
|
244
244
|
inputRecord: bookDoc,
|
|
245
245
|
simplified: false
|
|
246
|
-
})
|
|
246
|
+
})
|
|
247
247
|
|
|
248
248
|
// Verify response
|
|
249
|
-
validateJsonApiStructure(bookResult, false)
|
|
250
|
-
assert.equal(bookResult.data.type, 'books')
|
|
249
|
+
validateJsonApiStructure(bookResult, false)
|
|
250
|
+
assert.equal(bookResult.data.type, 'books')
|
|
251
251
|
|
|
252
252
|
// Verify pivot table records created
|
|
253
|
-
const pivotCount = await countRecords(knex, 'basic_book_authors')
|
|
254
|
-
assert.equal(pivotCount, 2, 'Should have created 2 pivot records')
|
|
253
|
+
const pivotCount = await countRecords(knex, 'basic_book_authors')
|
|
254
|
+
assert.equal(pivotCount, 2, 'Should have created 2 pivot records')
|
|
255
255
|
|
|
256
256
|
// GET book with authors included
|
|
257
257
|
const getResult = await basicApi.resources.books.get({
|
|
258
258
|
id: bookResult.data.id,
|
|
259
259
|
queryParams: { include: ['authors'] },
|
|
260
260
|
simplified: false
|
|
261
|
-
})
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
validateJsonApiStructure(getResult, false)
|
|
264
|
+
assert(getResult.data.relationships.authors)
|
|
265
|
+
assert.equal(getResult.data.relationships.authors.data.length, 2)
|
|
262
266
|
|
|
263
|
-
validateJsonApiStructure(getResult, false);
|
|
264
|
-
assert(getResult.data.relationships.authors);
|
|
265
|
-
assert.equal(getResult.data.relationships.authors.data.length, 2);
|
|
266
|
-
|
|
267
267
|
// Verify included authors
|
|
268
|
-
assert(getResult.included)
|
|
269
|
-
assert.equal(getResult.included.length, 2)
|
|
270
|
-
const authorNames = getResult.included.map(a => a.attributes.name).sort()
|
|
271
|
-
assert.deepEqual(authorNames, ['Peter Straub', 'Stephen King'])
|
|
272
|
-
})
|
|
268
|
+
assert(getResult.included)
|
|
269
|
+
assert.equal(getResult.included.length, 2)
|
|
270
|
+
const authorNames = getResult.included.map(a => a.attributes.name).sort()
|
|
271
|
+
assert.deepEqual(authorNames, ['Peter Straub', 'Stephen King'])
|
|
272
|
+
})
|
|
273
273
|
|
|
274
274
|
it('should update many-to-many relationships via PUT (complete replacement)', async () => {
|
|
275
275
|
// Create country
|
|
276
|
-
const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
276
|
+
const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
277
277
|
const countryResult = await basicApi.resources.countries.post({
|
|
278
278
|
inputRecord: countryDoc,
|
|
279
279
|
simplified: false
|
|
280
|
-
})
|
|
280
|
+
})
|
|
281
281
|
|
|
282
282
|
// Create 4 authors
|
|
283
|
-
const authors = []
|
|
283
|
+
const authors = []
|
|
284
284
|
for (const name of ['Author 1', 'Author 2', 'Author 3', 'Author 4']) {
|
|
285
|
-
const doc = createJsonApiDocument('authors', { name })
|
|
285
|
+
const doc = createJsonApiDocument('authors', { name })
|
|
286
286
|
const result = await basicApi.resources.authors.post({
|
|
287
287
|
inputRecord: doc,
|
|
288
288
|
simplified: false
|
|
289
|
-
})
|
|
290
|
-
authors.push(result.data)
|
|
289
|
+
})
|
|
290
|
+
authors.push(result.data)
|
|
291
291
|
}
|
|
292
292
|
|
|
293
293
|
// Create book with first 2 authors
|
|
@@ -302,16 +302,16 @@ describe('Relationship Operations', () => {
|
|
|
302
302
|
resourceIdentifier('authors', authors[1].id)
|
|
303
303
|
])
|
|
304
304
|
}
|
|
305
|
-
)
|
|
305
|
+
)
|
|
306
306
|
|
|
307
307
|
const bookResult = await basicApi.resources.books.post({
|
|
308
308
|
inputRecord: bookDoc,
|
|
309
309
|
simplified: false
|
|
310
|
-
})
|
|
310
|
+
})
|
|
311
311
|
|
|
312
312
|
// Verify initial state
|
|
313
|
-
let pivotCount = await countRecords(knex, 'basic_book_authors')
|
|
314
|
-
assert.equal(pivotCount, 2)
|
|
313
|
+
let pivotCount = await countRecords(knex, 'basic_book_authors')
|
|
314
|
+
assert.equal(pivotCount, 2)
|
|
315
315
|
|
|
316
316
|
// PUT to replace with different 2 authors
|
|
317
317
|
const putDoc = {
|
|
@@ -329,48 +329,47 @@ describe('Relationship Operations', () => {
|
|
|
329
329
|
])
|
|
330
330
|
}
|
|
331
331
|
}
|
|
332
|
-
}
|
|
332
|
+
}
|
|
333
333
|
console.log('THE NEXT LINE WILL BLOCK THE TEST')
|
|
334
334
|
await basicApi.resources.books.put({
|
|
335
335
|
id: bookResult.data.id,
|
|
336
336
|
inputRecord: putDoc,
|
|
337
337
|
simplified: false,
|
|
338
338
|
returnFullRecord: false
|
|
339
|
-
})
|
|
340
|
-
|
|
339
|
+
})
|
|
341
340
|
|
|
342
341
|
// Verify old pivot records removed, new ones created
|
|
343
|
-
pivotCount = await countRecords(knex, 'basic_book_authors')
|
|
344
|
-
assert.equal(pivotCount, 2)
|
|
342
|
+
pivotCount = await countRecords(knex, 'basic_book_authors')
|
|
343
|
+
assert.equal(pivotCount, 2)
|
|
345
344
|
|
|
346
345
|
// GET to verify new relationships
|
|
347
346
|
const getResult = await basicApi.resources.books.get({
|
|
348
347
|
id: bookResult.data.id,
|
|
349
348
|
queryParams: { include: ['authors'] },
|
|
350
349
|
simplified: false
|
|
351
|
-
})
|
|
350
|
+
})
|
|
352
351
|
|
|
353
|
-
const authorIds = getResult.data.relationships.authors.data.map(a => a.id).sort()
|
|
354
|
-
assert.deepEqual(authorIds, [authors[2].id, authors[3].id].sort())
|
|
355
|
-
})
|
|
352
|
+
const authorIds = getResult.data.relationships.authors.data.map(a => a.id).sort()
|
|
353
|
+
assert.deepEqual(authorIds, [authors[2].id, authors[3].id].sort())
|
|
354
|
+
})
|
|
356
355
|
|
|
357
356
|
it('should update many-to-many relationships via PATCH (partial update)', async () => {
|
|
358
357
|
// Create country
|
|
359
|
-
const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
358
|
+
const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
360
359
|
const countryResult = await basicApi.resources.countries.post({
|
|
361
360
|
inputRecord: countryDoc,
|
|
362
361
|
simplified: false
|
|
363
|
-
})
|
|
362
|
+
})
|
|
364
363
|
|
|
365
364
|
// Create 4 authors
|
|
366
|
-
const authors = []
|
|
365
|
+
const authors = []
|
|
367
366
|
for (const name of ['Author A', 'Author B', 'Author C', 'Author D']) {
|
|
368
|
-
const doc = createJsonApiDocument('authors', { name })
|
|
367
|
+
const doc = createJsonApiDocument('authors', { name })
|
|
369
368
|
const result = await basicApi.resources.authors.post({
|
|
370
369
|
inputRecord: doc,
|
|
371
370
|
simplified: false
|
|
372
|
-
})
|
|
373
|
-
authors.push(result.data)
|
|
371
|
+
})
|
|
372
|
+
authors.push(result.data)
|
|
374
373
|
}
|
|
375
374
|
|
|
376
375
|
// Create book with first 3 authors
|
|
@@ -386,12 +385,12 @@ describe('Relationship Operations', () => {
|
|
|
386
385
|
resourceIdentifier('authors', authors[2].id)
|
|
387
386
|
])
|
|
388
387
|
}
|
|
389
|
-
)
|
|
388
|
+
)
|
|
390
389
|
|
|
391
390
|
const bookResult = await basicApi.resources.books.post({
|
|
392
391
|
inputRecord: bookDoc,
|
|
393
392
|
simplified: false
|
|
394
|
-
})
|
|
393
|
+
})
|
|
395
394
|
|
|
396
395
|
// PATCH to change to [B, C, D] - should remove A, keep B&C, add D
|
|
397
396
|
const patchDoc = {
|
|
@@ -406,51 +405,51 @@ describe('Relationship Operations', () => {
|
|
|
406
405
|
])
|
|
407
406
|
}
|
|
408
407
|
}
|
|
409
|
-
}
|
|
408
|
+
}
|
|
410
409
|
|
|
411
410
|
await basicApi.resources.books.patch({
|
|
412
411
|
id: bookResult.data.id,
|
|
413
412
|
inputRecord: patchDoc,
|
|
414
413
|
simplified: false
|
|
415
|
-
})
|
|
414
|
+
})
|
|
416
415
|
|
|
417
416
|
// Verify the update
|
|
418
417
|
const getResult = await basicApi.resources.books.get({
|
|
419
418
|
id: bookResult.data.id,
|
|
420
419
|
queryParams: { include: ['authors'] },
|
|
421
420
|
simplified: false
|
|
422
|
-
})
|
|
421
|
+
})
|
|
423
422
|
|
|
424
|
-
const authorIds = getResult.data.relationships.authors.data.map(a => a.id).sort()
|
|
425
|
-
const expectedIds = [authors[1].id, authors[2].id, authors[3].id].sort()
|
|
426
|
-
assert.deepEqual(authorIds, expectedIds)
|
|
423
|
+
const authorIds = getResult.data.relationships.authors.data.map(a => a.id).sort()
|
|
424
|
+
const expectedIds = [authors[1].id, authors[2].id, authors[3].id].sort()
|
|
425
|
+
assert.deepEqual(authorIds, expectedIds)
|
|
427
426
|
|
|
428
427
|
// Verify correct number of pivot records
|
|
429
|
-
const pivotCount = await countRecords(knex, 'basic_book_authors')
|
|
430
|
-
assert.equal(pivotCount, 3)
|
|
431
|
-
})
|
|
428
|
+
const pivotCount = await countRecords(knex, 'basic_book_authors')
|
|
429
|
+
assert.equal(pivotCount, 3)
|
|
430
|
+
})
|
|
432
431
|
|
|
433
432
|
it('should clear all many-to-many relationships with empty array', async () => {
|
|
434
433
|
// Create country
|
|
435
|
-
const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
434
|
+
const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
436
435
|
const countryResult = await basicApi.resources.countries.post({
|
|
437
436
|
inputRecord: countryDoc,
|
|
438
437
|
simplified: false
|
|
439
|
-
})
|
|
438
|
+
})
|
|
440
439
|
|
|
441
440
|
// Create authors
|
|
442
|
-
const author1Doc = createJsonApiDocument('authors', { name: 'Author One' })
|
|
443
|
-
const author2Doc = createJsonApiDocument('authors', { name: 'Author Two' })
|
|
441
|
+
const author1Doc = createJsonApiDocument('authors', { name: 'Author One' })
|
|
442
|
+
const author2Doc = createJsonApiDocument('authors', { name: 'Author Two' })
|
|
444
443
|
|
|
445
444
|
const author1Result = await basicApi.resources.authors.post({
|
|
446
445
|
inputRecord: author1Doc,
|
|
447
446
|
simplified: false
|
|
448
|
-
})
|
|
447
|
+
})
|
|
449
448
|
|
|
450
449
|
const author2Result = await basicApi.resources.authors.post({
|
|
451
450
|
inputRecord: author2Doc,
|
|
452
451
|
simplified: false
|
|
453
|
-
})
|
|
452
|
+
})
|
|
454
453
|
|
|
455
454
|
// Create book with authors
|
|
456
455
|
const bookDoc = createJsonApiDocument('books',
|
|
@@ -464,16 +463,16 @@ describe('Relationship Operations', () => {
|
|
|
464
463
|
resourceIdentifier('authors', author2Result.data.id)
|
|
465
464
|
])
|
|
466
465
|
}
|
|
467
|
-
)
|
|
466
|
+
)
|
|
468
467
|
|
|
469
468
|
const bookResult = await basicApi.resources.books.post({
|
|
470
469
|
inputRecord: bookDoc,
|
|
471
470
|
simplified: false
|
|
472
|
-
})
|
|
471
|
+
})
|
|
473
472
|
|
|
474
473
|
// Verify initial state
|
|
475
|
-
let pivotCount = await countRecords(knex, 'basic_book_authors')
|
|
476
|
-
assert.equal(pivotCount, 2)
|
|
474
|
+
let pivotCount = await countRecords(knex, 'basic_book_authors')
|
|
475
|
+
assert.equal(pivotCount, 2)
|
|
477
476
|
|
|
478
477
|
// Clear all relationships
|
|
479
478
|
const patchDoc = {
|
|
@@ -484,64 +483,64 @@ describe('Relationship Operations', () => {
|
|
|
484
483
|
authors: { data: [] }
|
|
485
484
|
}
|
|
486
485
|
}
|
|
487
|
-
}
|
|
486
|
+
}
|
|
488
487
|
|
|
489
488
|
await basicApi.resources.books.patch({
|
|
490
489
|
id: bookResult.data.id,
|
|
491
490
|
inputRecord: patchDoc,
|
|
492
491
|
simplified: false
|
|
493
|
-
})
|
|
492
|
+
})
|
|
494
493
|
|
|
495
494
|
// Verify all pivot records removed
|
|
496
|
-
pivotCount = await countRecords(knex, 'basic_book_authors')
|
|
497
|
-
assert.equal(pivotCount, 0)
|
|
495
|
+
pivotCount = await countRecords(knex, 'basic_book_authors')
|
|
496
|
+
assert.equal(pivotCount, 0)
|
|
498
497
|
|
|
499
498
|
// Verify through GET
|
|
500
499
|
const getResult = await basicApi.resources.books.get({
|
|
501
500
|
id: bookResult.data.id,
|
|
502
501
|
queryParams: { include: ['authors'] },
|
|
503
502
|
simplified: false
|
|
504
|
-
})
|
|
503
|
+
})
|
|
505
504
|
|
|
506
|
-
assert(getResult.data.relationships.authors)
|
|
507
|
-
assert.equal(getResult.data.relationships.authors.data.length, 0)
|
|
508
|
-
})
|
|
509
|
-
})
|
|
505
|
+
assert(getResult.data.relationships.authors)
|
|
506
|
+
assert.equal(getResult.data.relationships.authors.data.length, 0)
|
|
507
|
+
})
|
|
508
|
+
})
|
|
510
509
|
|
|
511
510
|
describe('Complex Relationship Scenarios', () => {
|
|
512
511
|
beforeEach(async () => {
|
|
513
512
|
await cleanTables(knex, [
|
|
514
513
|
'basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors'
|
|
515
|
-
])
|
|
516
|
-
})
|
|
514
|
+
])
|
|
515
|
+
})
|
|
517
516
|
|
|
518
517
|
it('should create resource with multiple relationship types', async () => {
|
|
519
518
|
// Create supporting resources
|
|
520
|
-
const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
519
|
+
const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
521
520
|
const countryResult = await basicApi.resources.countries.post({
|
|
522
521
|
inputRecord: countryDoc,
|
|
523
522
|
simplified: false
|
|
524
|
-
})
|
|
523
|
+
})
|
|
525
524
|
|
|
526
525
|
const publisherDoc = createJsonApiDocument('publishers',
|
|
527
526
|
{ name: 'Big Publisher' },
|
|
528
527
|
{ country: createRelationship(resourceIdentifier('countries', countryResult.data.id)) }
|
|
529
|
-
)
|
|
528
|
+
)
|
|
530
529
|
const publisherResult = await basicApi.resources.publishers.post({
|
|
531
530
|
inputRecord: publisherDoc,
|
|
532
531
|
simplified: false
|
|
533
|
-
})
|
|
532
|
+
})
|
|
534
533
|
|
|
535
|
-
const author1Doc = createJsonApiDocument('authors', { name: 'Main Author' })
|
|
536
|
-
const author2Doc = createJsonApiDocument('authors', { name: 'Co-Author' })
|
|
534
|
+
const author1Doc = createJsonApiDocument('authors', { name: 'Main Author' })
|
|
535
|
+
const author2Doc = createJsonApiDocument('authors', { name: 'Co-Author' })
|
|
537
536
|
const author1Result = await basicApi.resources.authors.post({
|
|
538
537
|
inputRecord: author1Doc,
|
|
539
538
|
simplified: false
|
|
540
|
-
})
|
|
539
|
+
})
|
|
541
540
|
const author2Result = await basicApi.resources.authors.post({
|
|
542
541
|
inputRecord: author2Doc,
|
|
543
542
|
simplified: false
|
|
544
|
-
})
|
|
543
|
+
})
|
|
545
544
|
|
|
546
545
|
// Create book with all relationships
|
|
547
546
|
const bookDoc = createJsonApiDocument('books',
|
|
@@ -556,12 +555,12 @@ describe('Relationship Operations', () => {
|
|
|
556
555
|
resourceIdentifier('authors', author2Result.data.id)
|
|
557
556
|
])
|
|
558
557
|
}
|
|
559
|
-
)
|
|
558
|
+
)
|
|
560
559
|
|
|
561
560
|
const bookResult = await basicApi.resources.books.post({
|
|
562
561
|
inputRecord: bookDoc,
|
|
563
562
|
simplified: false
|
|
564
|
-
})
|
|
563
|
+
})
|
|
565
564
|
|
|
566
565
|
// Verify with full includes
|
|
567
566
|
const getResult = await basicApi.resources.books.get({
|
|
@@ -570,49 +569,49 @@ describe('Relationship Operations', () => {
|
|
|
570
569
|
include: ['publisher', 'publisher.country', 'authors', 'country']
|
|
571
570
|
},
|
|
572
571
|
simplified: false
|
|
573
|
-
})
|
|
572
|
+
})
|
|
573
|
+
|
|
574
|
+
validateJsonApiStructure(getResult, false)
|
|
574
575
|
|
|
575
|
-
validateJsonApiStructure(getResult, false);
|
|
576
|
-
|
|
577
576
|
// Verify all relationships
|
|
578
|
-
assert(getResult.data.relationships.publisher)
|
|
579
|
-
assert(getResult.data.relationships.authors)
|
|
580
|
-
assert(getResult.data.relationships.country)
|
|
581
|
-
|
|
577
|
+
assert(getResult.data.relationships.publisher)
|
|
578
|
+
assert(getResult.data.relationships.authors)
|
|
579
|
+
assert(getResult.data.relationships.country)
|
|
580
|
+
|
|
582
581
|
// Verify included resources
|
|
583
|
-
assert(getResult.included)
|
|
584
|
-
const includedTypes = getResult.included.map(r => r.type)
|
|
585
|
-
assert(includedTypes.includes('publishers'))
|
|
586
|
-
assert(includedTypes.includes('authors'))
|
|
587
|
-
assert(includedTypes.includes('countries'))
|
|
588
|
-
|
|
582
|
+
assert(getResult.included)
|
|
583
|
+
const includedTypes = getResult.included.map(r => r.type)
|
|
584
|
+
assert(includedTypes.includes('publishers'))
|
|
585
|
+
assert(includedTypes.includes('authors'))
|
|
586
|
+
assert(includedTypes.includes('countries'))
|
|
587
|
+
|
|
589
588
|
// Verify nested relationship (publisher.country)
|
|
590
|
-
const publisher = getResult.included.find(r => r.type === 'publishers')
|
|
591
|
-
assert(publisher.relationships.country)
|
|
592
|
-
})
|
|
589
|
+
const publisher = getResult.included.find(r => r.type === 'publishers')
|
|
590
|
+
assert(publisher.relationships.country)
|
|
591
|
+
})
|
|
593
592
|
|
|
594
593
|
it('should handle PUT with partial relationships object', async () => {
|
|
595
594
|
// Create resources
|
|
596
|
-
const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
595
|
+
const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
597
596
|
const countryResult = await basicApi.resources.countries.post({
|
|
598
597
|
inputRecord: countryDoc,
|
|
599
598
|
simplified: false
|
|
600
|
-
})
|
|
599
|
+
})
|
|
601
600
|
|
|
602
601
|
const publisherDoc = createJsonApiDocument('publishers',
|
|
603
602
|
{ name: 'Original Publisher' },
|
|
604
603
|
{ country: createRelationship(resourceIdentifier('countries', countryResult.data.id)) }
|
|
605
|
-
)
|
|
604
|
+
)
|
|
606
605
|
const publisherResult = await basicApi.resources.publishers.post({
|
|
607
606
|
inputRecord: publisherDoc,
|
|
608
607
|
simplified: false
|
|
609
|
-
})
|
|
608
|
+
})
|
|
610
609
|
|
|
611
|
-
const authorDoc = createJsonApiDocument('authors', { name: 'Original Author' })
|
|
610
|
+
const authorDoc = createJsonApiDocument('authors', { name: 'Original Author' })
|
|
612
611
|
const authorResult = await basicApi.resources.authors.post({
|
|
613
612
|
inputRecord: authorDoc,
|
|
614
613
|
simplified: false
|
|
615
|
-
})
|
|
614
|
+
})
|
|
616
615
|
|
|
617
616
|
// Create book with all relationships
|
|
618
617
|
const bookDoc = createJsonApiDocument('books',
|
|
@@ -624,22 +623,22 @@ describe('Relationship Operations', () => {
|
|
|
624
623
|
publisher: createRelationship(resourceIdentifier('publishers', publisherResult.data.id)),
|
|
625
624
|
authors: createToManyRelationship([resourceIdentifier('authors', authorResult.data.id)])
|
|
626
625
|
}
|
|
627
|
-
)
|
|
626
|
+
)
|
|
628
627
|
|
|
629
628
|
const bookResult = await basicApi.resources.books.post({
|
|
630
629
|
inputRecord: bookDoc,
|
|
631
630
|
simplified: false
|
|
632
|
-
})
|
|
631
|
+
})
|
|
633
632
|
|
|
634
633
|
// Create new publisher for update
|
|
635
634
|
const newPublisherDoc = createJsonApiDocument('publishers',
|
|
636
635
|
{ name: 'New Publisher' },
|
|
637
636
|
{ country: createRelationship(resourceIdentifier('countries', countryResult.data.id)) }
|
|
638
|
-
)
|
|
637
|
+
)
|
|
639
638
|
const newPublisherResult = await basicApi.resources.publishers.post({
|
|
640
639
|
inputRecord: newPublisherDoc,
|
|
641
640
|
simplified: false
|
|
642
|
-
})
|
|
641
|
+
})
|
|
643
642
|
|
|
644
643
|
// PUT with only publisher relationship (should preserve authors since relationships object is partial)
|
|
645
644
|
const putDoc = {
|
|
@@ -655,28 +654,28 @@ describe('Relationship Operations', () => {
|
|
|
655
654
|
// Note: authors not mentioned, but relationships object is present
|
|
656
655
|
}
|
|
657
656
|
}
|
|
658
|
-
}
|
|
657
|
+
}
|
|
659
658
|
|
|
660
659
|
await basicApi.resources.books.put({
|
|
661
660
|
id: bookResult.data.id,
|
|
662
661
|
inputRecord: putDoc,
|
|
663
662
|
simplified: false,
|
|
664
663
|
returnFullRecord: false
|
|
665
|
-
})
|
|
664
|
+
})
|
|
666
665
|
|
|
667
666
|
// Verify update
|
|
668
667
|
const getResult = await basicApi.resources.books.get({
|
|
669
668
|
id: bookResult.data.id,
|
|
670
669
|
queryParams: { include: ['publisher', 'authors'] },
|
|
671
670
|
simplified: false
|
|
672
|
-
})
|
|
671
|
+
})
|
|
673
672
|
|
|
674
673
|
// Publisher should be updated
|
|
675
|
-
assert.equal(getResult.data.relationships.publisher.data.id, newPublisherResult.data.id)
|
|
676
|
-
|
|
674
|
+
assert.equal(getResult.data.relationships.publisher.data.id, newPublisherResult.data.id)
|
|
675
|
+
|
|
677
676
|
// Authors should be cleared (PUT with relationships object clears unmentioned relationships)
|
|
678
|
-
assert(getResult.data.relationships.authors)
|
|
679
|
-
assert.equal(getResult.data.relationships.authors.data.length, 0)
|
|
680
|
-
})
|
|
681
|
-
})
|
|
682
|
-
})
|
|
677
|
+
assert(getResult.data.relationships.authors)
|
|
678
|
+
assert.equal(getResult.data.relationships.authors.data.length, 0)
|
|
679
|
+
})
|
|
680
|
+
})
|
|
681
|
+
})
|