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,15 +1,15 @@
|
|
|
1
|
-
import { describe, it, before, beforeEach, after } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import knexLib from 'knex'
|
|
4
|
-
import { Api } from 'hooked-api'
|
|
5
|
-
import { RestApiPlugin } from '../plugins/core/rest-api-plugin.js'
|
|
6
|
-
import { RestApiKnexPlugin } from '../plugins/core/rest-api-knex-plugin.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 { Api } from 'hooked-api'
|
|
5
|
+
import { RestApiPlugin } from '../plugins/core/rest-api-plugin.js'
|
|
6
|
+
import { RestApiKnexPlugin } from '../plugins/core/rest-api-knex-plugin.js'
|
|
7
7
|
import {
|
|
8
8
|
cleanTables,
|
|
9
9
|
createJsonApiDocument,
|
|
10
10
|
createRelationship,
|
|
11
11
|
validateJsonApiStructure
|
|
12
|
-
} from './helpers/test-utils.js'
|
|
12
|
+
} from './helpers/test-utils.js'
|
|
13
13
|
|
|
14
14
|
// Create Knex instance for tests
|
|
15
15
|
const knex = knexLib({
|
|
@@ -18,26 +18,26 @@ const knex = knexLib({
|
|
|
18
18
|
filename: ':memory:'
|
|
19
19
|
},
|
|
20
20
|
useNullAsDefault: true
|
|
21
|
-
})
|
|
21
|
+
})
|
|
22
22
|
|
|
23
23
|
describe('Nested Include Operations', () => {
|
|
24
|
-
let api
|
|
24
|
+
let api
|
|
25
25
|
|
|
26
26
|
before(async () => {
|
|
27
27
|
// Create API instance
|
|
28
28
|
api = new Api({
|
|
29
29
|
name: 'test-nested-includes'
|
|
30
|
-
})
|
|
30
|
+
})
|
|
31
31
|
|
|
32
32
|
await api.use(RestApiPlugin, {
|
|
33
33
|
queryDefaultLimit: 10,
|
|
34
34
|
queryMaxLimit: 50,
|
|
35
35
|
simplified: false
|
|
36
|
-
})
|
|
36
|
+
})
|
|
37
37
|
|
|
38
38
|
await api.use(RestApiKnexPlugin, {
|
|
39
|
-
knex
|
|
40
|
-
})
|
|
39
|
+
knex
|
|
40
|
+
})
|
|
41
41
|
|
|
42
42
|
// Countries
|
|
43
43
|
await api.addResource('countries', {
|
|
@@ -46,8 +46,8 @@ describe('Nested Include Operations', () => {
|
|
|
46
46
|
name: { type: 'string', required: true },
|
|
47
47
|
code: { type: 'string', max: 2 }
|
|
48
48
|
}
|
|
49
|
-
})
|
|
50
|
-
await api.resources.countries.createKnexTable()
|
|
49
|
+
})
|
|
50
|
+
await api.resources.countries.createKnexTable()
|
|
51
51
|
|
|
52
52
|
// Publishers
|
|
53
53
|
await api.addResource('publishers', {
|
|
@@ -57,15 +57,15 @@ describe('Nested Include Operations', () => {
|
|
|
57
57
|
country_id: { type: 'number', belongsTo: 'countries', as: 'country' }
|
|
58
58
|
},
|
|
59
59
|
relationships: {
|
|
60
|
-
books: {
|
|
60
|
+
books: {
|
|
61
61
|
type: 'hasMany',
|
|
62
|
-
target: 'books',
|
|
62
|
+
target: 'books',
|
|
63
63
|
foreignKey: 'publisher_id'
|
|
64
64
|
// Relationships are always includable via ?include=
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
-
})
|
|
68
|
-
await api.resources.publishers.createKnexTable()
|
|
67
|
+
})
|
|
68
|
+
await api.resources.publishers.createKnexTable()
|
|
69
69
|
|
|
70
70
|
// Authors
|
|
71
71
|
await api.addResource('authors', {
|
|
@@ -75,10 +75,10 @@ describe('Nested Include Operations', () => {
|
|
|
75
75
|
country_id: { type: 'number', belongsTo: 'countries', as: 'country' }
|
|
76
76
|
},
|
|
77
77
|
relationships: {
|
|
78
|
-
books: {
|
|
78
|
+
books: {
|
|
79
79
|
type: 'manyToMany',
|
|
80
|
-
through: 'book_authors',
|
|
81
|
-
foreignKey: 'author_id',
|
|
80
|
+
through: 'book_authors',
|
|
81
|
+
foreignKey: 'author_id',
|
|
82
82
|
otherKey: 'book_id',
|
|
83
83
|
// Relationships are always includable via ?include=
|
|
84
84
|
include: {
|
|
@@ -87,8 +87,8 @@ describe('Nested Include Operations', () => {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
-
})
|
|
91
|
-
await api.resources.authors.createKnexTable()
|
|
90
|
+
})
|
|
91
|
+
await api.resources.authors.createKnexTable()
|
|
92
92
|
|
|
93
93
|
// Books
|
|
94
94
|
await api.addResource('books', {
|
|
@@ -98,22 +98,22 @@ describe('Nested Include Operations', () => {
|
|
|
98
98
|
publisher_id: { type: 'number', belongsTo: 'publishers', as: 'publisher' }
|
|
99
99
|
},
|
|
100
100
|
relationships: {
|
|
101
|
-
authors: {
|
|
101
|
+
authors: {
|
|
102
102
|
type: 'manyToMany',
|
|
103
|
-
through: 'book_authors',
|
|
104
|
-
foreignKey: 'book_id',
|
|
103
|
+
through: 'book_authors',
|
|
104
|
+
foreignKey: 'book_id',
|
|
105
105
|
otherKey: 'author_id'
|
|
106
106
|
// Relationships are always includable via ?include=
|
|
107
107
|
},
|
|
108
|
-
reviews: {
|
|
108
|
+
reviews: {
|
|
109
109
|
type: 'hasMany',
|
|
110
|
-
target: 'reviews',
|
|
110
|
+
target: 'reviews',
|
|
111
111
|
foreignKey: 'book_id'
|
|
112
112
|
// Relationships are always includable via ?include=
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
|
-
})
|
|
116
|
-
await api.resources.books.createKnexTable()
|
|
115
|
+
})
|
|
116
|
+
await api.resources.books.createKnexTable()
|
|
117
117
|
|
|
118
118
|
// Book-Authors pivot table
|
|
119
119
|
await api.addResource('book_authors', {
|
|
@@ -122,8 +122,8 @@ describe('Nested Include Operations', () => {
|
|
|
122
122
|
book_id: { type: 'number', belongsTo: 'books', as: 'book' },
|
|
123
123
|
author_id: { type: 'number', belongsTo: 'authors', as: 'author' }
|
|
124
124
|
}
|
|
125
|
-
})
|
|
126
|
-
await api.resources.book_authors.createKnexTable()
|
|
125
|
+
})
|
|
126
|
+
await api.resources.book_authors.createKnexTable()
|
|
127
127
|
|
|
128
128
|
// Reviews
|
|
129
129
|
await api.addResource('reviews', {
|
|
@@ -133,19 +133,19 @@ describe('Nested Include Operations', () => {
|
|
|
133
133
|
rating: { type: 'number', required: true },
|
|
134
134
|
book_id: { type: 'number', belongsTo: 'books', as: 'book' }
|
|
135
135
|
}
|
|
136
|
-
})
|
|
137
|
-
await api.resources.reviews.createKnexTable()
|
|
138
|
-
})
|
|
136
|
+
})
|
|
137
|
+
await api.resources.reviews.createKnexTable()
|
|
138
|
+
})
|
|
139
139
|
|
|
140
140
|
after(async () => {
|
|
141
|
-
await knex.destroy()
|
|
142
|
-
})
|
|
141
|
+
await knex.destroy()
|
|
142
|
+
})
|
|
143
143
|
|
|
144
144
|
beforeEach(async () => {
|
|
145
145
|
await cleanTables(knex, [
|
|
146
146
|
'countries', 'publishers', 'authors', 'books', 'book_authors', 'reviews'
|
|
147
|
-
])
|
|
148
|
-
})
|
|
147
|
+
])
|
|
148
|
+
})
|
|
149
149
|
|
|
150
150
|
describe('Bidirectional Many-to-Many Relationships', () => {
|
|
151
151
|
beforeEach(async () => {
|
|
@@ -155,89 +155,89 @@ describe('Nested Include Operations', () => {
|
|
|
155
155
|
name: 'USA',
|
|
156
156
|
code: 'US'
|
|
157
157
|
})
|
|
158
|
-
})
|
|
158
|
+
})
|
|
159
159
|
|
|
160
160
|
const publisher = await api.resources.publishers.post({
|
|
161
161
|
inputRecord: createJsonApiDocument('publishers', {
|
|
162
162
|
name: 'Test Publisher'
|
|
163
163
|
}, {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
})
|
|
164
|
+
country: createRelationship({ type: 'countries', id: String(country.id) })
|
|
165
|
+
})
|
|
166
|
+
})
|
|
167
167
|
|
|
168
168
|
// Create authors
|
|
169
169
|
const author1 = await api.resources.authors.post({
|
|
170
170
|
inputRecord: createJsonApiDocument('authors', {
|
|
171
171
|
name: 'Author 1'
|
|
172
172
|
}, {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
})
|
|
173
|
+
country: createRelationship({ type: 'countries', id: String(country.id) })
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
176
|
|
|
177
177
|
const author2 = await api.resources.authors.post({
|
|
178
178
|
inputRecord: createJsonApiDocument('authors', {
|
|
179
179
|
name: 'Author 2'
|
|
180
180
|
}, {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
})
|
|
181
|
+
country: createRelationship({ type: 'countries', id: String(country.id) })
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
184
|
|
|
185
185
|
// Create books
|
|
186
186
|
const book1 = await api.resources.books.post({
|
|
187
187
|
inputRecord: createJsonApiDocument('books', {
|
|
188
188
|
title: 'Book 1'
|
|
189
189
|
}, {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
})
|
|
190
|
+
publisher: createRelationship({ type: 'publishers', id: String(publisher.id) })
|
|
191
|
+
})
|
|
192
|
+
})
|
|
193
193
|
|
|
194
194
|
const book2 = await api.resources.books.post({
|
|
195
195
|
inputRecord: createJsonApiDocument('books', {
|
|
196
196
|
title: 'Book 2'
|
|
197
197
|
}, {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
})
|
|
198
|
+
publisher: createRelationship({ type: 'publishers', id: String(publisher.id) })
|
|
199
|
+
})
|
|
200
|
+
})
|
|
201
201
|
|
|
202
202
|
const book3 = await api.resources.books.post({
|
|
203
203
|
inputRecord: createJsonApiDocument('books', {
|
|
204
204
|
title: 'Book 3'
|
|
205
205
|
}, {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
})
|
|
206
|
+
publisher: createRelationship({ type: 'publishers', id: String(publisher.id) })
|
|
207
|
+
})
|
|
208
|
+
})
|
|
209
209
|
|
|
210
210
|
// Create relationships
|
|
211
211
|
// Book 1 - Author 1 only
|
|
212
212
|
await api.resources.book_authors.post({
|
|
213
213
|
inputRecord: createJsonApiDocument('book_authors', {}, {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
})
|
|
214
|
+
book: createRelationship({ type: 'books', id: String(book1.id) }),
|
|
215
|
+
author: createRelationship({ type: 'authors', id: String(author1.id) })
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
218
|
|
|
219
219
|
// Book 2 - Both authors
|
|
220
220
|
await api.resources.book_authors.post({
|
|
221
221
|
inputRecord: createJsonApiDocument('book_authors', {}, {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
})
|
|
222
|
+
book: createRelationship({ type: 'books', id: String(book2.id) }),
|
|
223
|
+
author: createRelationship({ type: 'authors', id: String(author1.id) })
|
|
224
|
+
})
|
|
225
|
+
})
|
|
226
226
|
await api.resources.book_authors.post({
|
|
227
227
|
inputRecord: createJsonApiDocument('book_authors', {}, {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
})
|
|
228
|
+
book: createRelationship({ type: 'books', id: String(book2.id) }),
|
|
229
|
+
author: createRelationship({ type: 'authors', id: String(author2.id) })
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
232
|
|
|
233
233
|
// Book 3 - Author 2 only
|
|
234
234
|
await api.resources.book_authors.post({
|
|
235
235
|
inputRecord: createJsonApiDocument('book_authors', {}, {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
})
|
|
240
|
-
})
|
|
236
|
+
book: createRelationship({ type: 'books', id: String(book3.id) }),
|
|
237
|
+
author: createRelationship({ type: 'authors', id: String(author2.id) })
|
|
238
|
+
})
|
|
239
|
+
})
|
|
240
|
+
})
|
|
241
241
|
|
|
242
242
|
it('should populate reverse relationships when using nested includes', async () => {
|
|
243
243
|
const result = await api.resources.authors.query({
|
|
@@ -245,62 +245,62 @@ describe('Nested Include Operations', () => {
|
|
|
245
245
|
include: ['books', 'books.authors']
|
|
246
246
|
},
|
|
247
247
|
simplified: false
|
|
248
|
-
})
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
validateJsonApiStructure(result, { isCollection: true })
|
|
251
|
+
assert.equal(result.data.length, 2, 'Should have 2 authors')
|
|
249
252
|
|
|
250
|
-
validateJsonApiStructure(result, { isCollection: true });
|
|
251
|
-
assert.equal(result.data.length, 2, 'Should have 2 authors');
|
|
252
|
-
|
|
253
253
|
// Check that books are included
|
|
254
|
-
const books = result.included.filter(r => r.type === 'books')
|
|
255
|
-
assert.equal(books.length, 3, 'Should include 3 books')
|
|
256
|
-
|
|
254
|
+
const books = result.included.filter(r => r.type === 'books')
|
|
255
|
+
assert.equal(books.length, 3, 'Should include 3 books')
|
|
256
|
+
|
|
257
257
|
// Check that authors are included (for the books.authors include)
|
|
258
|
-
const includedAuthors = result.included.filter(r => r.type === 'authors')
|
|
259
|
-
assert.equal(includedAuthors.length, 2, 'Should include 2 authors')
|
|
260
|
-
|
|
258
|
+
const includedAuthors = result.included.filter(r => r.type === 'authors')
|
|
259
|
+
assert.equal(includedAuthors.length, 2, 'Should include 2 authors')
|
|
260
|
+
|
|
261
261
|
// Verify each book has its authors relationship populated
|
|
262
262
|
books.forEach(book => {
|
|
263
|
-
assert(book.relationships?.authors?.data, `Book ${book.id} should have authors relationship`)
|
|
264
|
-
|
|
263
|
+
assert(book.relationships?.authors?.data, `Book ${book.id} should have authors relationship`)
|
|
264
|
+
|
|
265
265
|
if (book.attributes.title === 'Book 1') {
|
|
266
|
-
assert.equal(book.relationships.authors.data.length, 1, 'Book 1 should have 1 author')
|
|
267
|
-
assert.equal(book.relationships.authors.data[0].id, '1', 'Book 1 should have Author 1')
|
|
266
|
+
assert.equal(book.relationships.authors.data.length, 1, 'Book 1 should have 1 author')
|
|
267
|
+
assert.equal(book.relationships.authors.data[0].id, '1', 'Book 1 should have Author 1')
|
|
268
268
|
} else if (book.attributes.title === 'Book 2') {
|
|
269
|
-
assert.equal(book.relationships.authors.data.length, 2, 'Book 2 should have 2 authors')
|
|
270
|
-
const authorIds = book.relationships.authors.data.map(a => a.id).sort()
|
|
271
|
-
assert.deepEqual(authorIds, ['1', '2'], 'Book 2 should have both authors')
|
|
269
|
+
assert.equal(book.relationships.authors.data.length, 2, 'Book 2 should have 2 authors')
|
|
270
|
+
const authorIds = book.relationships.authors.data.map(a => a.id).sort()
|
|
271
|
+
assert.deepEqual(authorIds, ['1', '2'], 'Book 2 should have both authors')
|
|
272
272
|
} else if (book.attributes.title === 'Book 3') {
|
|
273
|
-
assert.equal(book.relationships.authors.data.length, 1, 'Book 3 should have 1 author')
|
|
274
|
-
assert.equal(book.relationships.authors.data[0].id, '2', 'Book 3 should have Author 2')
|
|
273
|
+
assert.equal(book.relationships.authors.data.length, 1, 'Book 3 should have 1 author')
|
|
274
|
+
assert.equal(book.relationships.authors.data[0].id, '2', 'Book 3 should have Author 2')
|
|
275
275
|
}
|
|
276
|
-
})
|
|
277
|
-
})
|
|
276
|
+
})
|
|
277
|
+
})
|
|
278
278
|
|
|
279
279
|
it('should work with window strategy limits', async () => {
|
|
280
280
|
// Create more books to test window limits
|
|
281
|
-
const publisher = await api.resources.publishers.query({ simplified: false })
|
|
282
|
-
const publisherId = publisher.data[0].id
|
|
283
|
-
|
|
281
|
+
const publisher = await api.resources.publishers.query({ simplified: false })
|
|
282
|
+
const publisherId = publisher.data[0].id
|
|
283
|
+
|
|
284
284
|
// Get the first author
|
|
285
|
-
const authorsResult = await api.resources.authors.query({ simplified: false })
|
|
286
|
-
const authorId = authorsResult.data[0].id
|
|
287
|
-
|
|
285
|
+
const authorsResult = await api.resources.authors.query({ simplified: false })
|
|
286
|
+
const authorId = authorsResult.data[0].id
|
|
287
|
+
|
|
288
288
|
for (let i = 4; i <= 10; i++) {
|
|
289
289
|
const book = await api.resources.books.post({
|
|
290
290
|
inputRecord: createJsonApiDocument('books', {
|
|
291
291
|
title: `Book ${i}`
|
|
292
292
|
}, {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
})
|
|
296
|
-
|
|
293
|
+
publisher: createRelationship({ type: 'publishers', id: publisherId })
|
|
294
|
+
})
|
|
295
|
+
})
|
|
296
|
+
|
|
297
297
|
// Associate all new books with Author 1
|
|
298
298
|
await api.resources.book_authors.post({
|
|
299
299
|
inputRecord: createJsonApiDocument('book_authors', {}, {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
})
|
|
300
|
+
book: createRelationship({ type: 'books', id: String(book.id) }),
|
|
301
|
+
author: createRelationship({ type: 'authors', id: String(authorId) })
|
|
302
|
+
})
|
|
303
|
+
})
|
|
304
304
|
}
|
|
305
305
|
|
|
306
306
|
const result = await api.resources.authors.query({
|
|
@@ -309,98 +309,98 @@ describe('Nested Include Operations', () => {
|
|
|
309
309
|
filters: { name: 'Author 1' }
|
|
310
310
|
},
|
|
311
311
|
simplified: false
|
|
312
|
-
})
|
|
312
|
+
})
|
|
313
313
|
|
|
314
|
-
const author1 = result.data[0]
|
|
314
|
+
const author1 = result.data[0]
|
|
315
315
|
// Author 1 has books 1,2 from setup + books 4-10 from test = 9 total books
|
|
316
316
|
// But window limit is 5, so should only get 5
|
|
317
317
|
// Verify we got the right author and they have books
|
|
318
|
-
assert(author1, 'Should have found Author 1')
|
|
319
|
-
assert(author1.relationships?.books?.data, 'Author 1 should have books relationship')
|
|
320
|
-
|
|
318
|
+
assert(author1, 'Should have found Author 1')
|
|
319
|
+
assert(author1.relationships?.books?.data, 'Author 1 should have books relationship')
|
|
320
|
+
|
|
321
321
|
// The window limit should apply
|
|
322
|
-
const bookCount = author1.relationships.books.data.length
|
|
323
|
-
assert(bookCount >= 2, `Author 1 should have at least 2 books, got ${bookCount}`)
|
|
324
|
-
assert(bookCount <= 5, `Author 1 should have at most 5 books (window limit), got ${bookCount}`)
|
|
325
|
-
|
|
322
|
+
const bookCount = author1.relationships.books.data.length
|
|
323
|
+
assert(bookCount >= 2, `Author 1 should have at least 2 books, got ${bookCount}`)
|
|
324
|
+
assert(bookCount <= 5, `Author 1 should have at most 5 books (window limit), got ${bookCount}`)
|
|
325
|
+
|
|
326
326
|
// All included books should have their authors relationship populated
|
|
327
|
-
const books = result.included.filter(r => r.type === 'books')
|
|
328
|
-
assert(books.length >= 2, 'Should include at least 2 books')
|
|
329
|
-
|
|
327
|
+
const books = result.included.filter(r => r.type === 'books')
|
|
328
|
+
assert(books.length >= 2, 'Should include at least 2 books')
|
|
329
|
+
|
|
330
330
|
// At least some books should have their authors relationship populated
|
|
331
|
-
const booksWithAuthors = books.filter(book =>
|
|
331
|
+
const booksWithAuthors = books.filter(book =>
|
|
332
332
|
book.relationships?.authors?.data && book.relationships.authors.data.length > 0
|
|
333
|
-
)
|
|
334
|
-
assert(booksWithAuthors.length > 0, 'At least some books should have authors relationship populated')
|
|
335
|
-
})
|
|
336
|
-
})
|
|
333
|
+
)
|
|
334
|
+
assert(booksWithAuthors.length > 0, 'At least some books should have authors relationship populated')
|
|
335
|
+
})
|
|
336
|
+
})
|
|
337
337
|
|
|
338
338
|
describe('Deep Nested Includes', () => {
|
|
339
339
|
beforeEach(async () => {
|
|
340
340
|
// Create hierarchical test data
|
|
341
341
|
const usa = await api.resources.countries.post({
|
|
342
342
|
inputRecord: createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
343
|
-
})
|
|
343
|
+
})
|
|
344
344
|
|
|
345
345
|
const uk = await api.resources.countries.post({
|
|
346
346
|
inputRecord: createJsonApiDocument('countries', { name: 'UK', code: 'GB' })
|
|
347
|
-
})
|
|
347
|
+
})
|
|
348
348
|
|
|
349
349
|
const publisher1 = await api.resources.publishers.post({
|
|
350
350
|
inputRecord: createJsonApiDocument('publishers', {
|
|
351
351
|
name: 'US Publisher'
|
|
352
352
|
}, {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
})
|
|
353
|
+
country: createRelationship({ type: 'countries', id: String(usa.id) })
|
|
354
|
+
})
|
|
355
|
+
})
|
|
356
356
|
|
|
357
357
|
const publisher2 = await api.resources.publishers.post({
|
|
358
358
|
inputRecord: createJsonApiDocument('publishers', {
|
|
359
359
|
name: 'UK Publisher'
|
|
360
360
|
}, {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
})
|
|
361
|
+
country: createRelationship({ type: 'countries', id: String(uk.id) })
|
|
362
|
+
})
|
|
363
|
+
})
|
|
364
364
|
|
|
365
365
|
const author = await api.resources.authors.post({
|
|
366
366
|
inputRecord: createJsonApiDocument('authors', {
|
|
367
367
|
name: 'International Author'
|
|
368
368
|
}, {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
})
|
|
369
|
+
country: createRelationship({ type: 'countries', id: String(usa.id) })
|
|
370
|
+
})
|
|
371
|
+
})
|
|
372
372
|
|
|
373
373
|
const book1 = await api.resources.books.post({
|
|
374
374
|
inputRecord: createJsonApiDocument('books', {
|
|
375
375
|
title: 'US Book'
|
|
376
376
|
}, {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
})
|
|
377
|
+
publisher: createRelationship({ type: 'publishers', id: String(publisher1.id) })
|
|
378
|
+
})
|
|
379
|
+
})
|
|
380
380
|
|
|
381
381
|
const book2 = await api.resources.books.post({
|
|
382
382
|
inputRecord: createJsonApiDocument('books', {
|
|
383
383
|
title: 'UK Book'
|
|
384
384
|
}, {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
})
|
|
385
|
+
publisher: createRelationship({ type: 'publishers', id: String(publisher2.id) })
|
|
386
|
+
})
|
|
387
|
+
})
|
|
388
388
|
|
|
389
389
|
// Associate both books with the author
|
|
390
390
|
await api.resources.book_authors.post({
|
|
391
391
|
inputRecord: createJsonApiDocument('book_authors', {}, {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
})
|
|
392
|
+
book: createRelationship({ type: 'books', id: String(book1.id) }),
|
|
393
|
+
author: createRelationship({ type: 'authors', id: String(author.id) })
|
|
394
|
+
})
|
|
395
|
+
})
|
|
396
396
|
|
|
397
397
|
await api.resources.book_authors.post({
|
|
398
398
|
inputRecord: createJsonApiDocument('book_authors', {}, {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
})
|
|
403
|
-
})
|
|
399
|
+
book: createRelationship({ type: 'books', id: String(book2.id) }),
|
|
400
|
+
author: createRelationship({ type: 'authors', id: String(author.id) })
|
|
401
|
+
})
|
|
402
|
+
})
|
|
403
|
+
})
|
|
404
404
|
|
|
405
405
|
it('should handle 3+ levels of nested includes', async () => {
|
|
406
406
|
const result = await api.resources.authors.query({
|
|
@@ -408,28 +408,28 @@ describe('Nested Include Operations', () => {
|
|
|
408
408
|
include: ['books', 'books.publisher', 'books.publisher.country']
|
|
409
409
|
},
|
|
410
410
|
simplified: false
|
|
411
|
-
})
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
validateJsonApiStructure(result, { isCollection: true })
|
|
412
414
|
|
|
413
|
-
validateJsonApiStructure(result, { isCollection: true });
|
|
414
|
-
|
|
415
415
|
// Check all resource types are included
|
|
416
|
-
const books = result.included.filter(r => r.type === 'books')
|
|
417
|
-
const publishers = result.included.filter(r => r.type === 'publishers')
|
|
418
|
-
const countries = result.included.filter(r => r.type === 'countries')
|
|
419
|
-
|
|
420
|
-
assert.equal(books.length, 2, 'Should include 2 books')
|
|
421
|
-
assert.equal(publishers.length, 2, 'Should include 2 publishers')
|
|
416
|
+
const books = result.included.filter(r => r.type === 'books')
|
|
417
|
+
const publishers = result.included.filter(r => r.type === 'publishers')
|
|
418
|
+
const countries = result.included.filter(r => r.type === 'countries')
|
|
419
|
+
|
|
420
|
+
assert.equal(books.length, 2, 'Should include 2 books')
|
|
421
|
+
assert.equal(publishers.length, 2, 'Should include 2 publishers')
|
|
422
422
|
// All defined relationships will be included when requested via ?include=
|
|
423
|
-
|
|
423
|
+
|
|
424
424
|
// Verify nested relationships are populated
|
|
425
425
|
books.forEach(book => {
|
|
426
|
-
assert(book.relationships?.publisher?.data, 'Book should have publisher relationship')
|
|
427
|
-
})
|
|
428
|
-
|
|
426
|
+
assert(book.relationships?.publisher?.data, 'Book should have publisher relationship')
|
|
427
|
+
})
|
|
428
|
+
|
|
429
429
|
publishers.forEach(publisher => {
|
|
430
|
-
assert(publisher.relationships?.country?.data, 'Publisher should have country relationship')
|
|
431
|
-
})
|
|
432
|
-
})
|
|
430
|
+
assert(publisher.relationships?.country?.data, 'Publisher should have country relationship')
|
|
431
|
+
})
|
|
432
|
+
})
|
|
433
433
|
|
|
434
434
|
it('should handle multiple paths to the same resource', async () => {
|
|
435
435
|
const result = await api.resources.publishers.query({
|
|
@@ -437,61 +437,61 @@ describe('Nested Include Operations', () => {
|
|
|
437
437
|
include: ['books', 'books.authors', 'books.authors.country', 'country']
|
|
438
438
|
},
|
|
439
439
|
simplified: false
|
|
440
|
-
})
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
validateJsonApiStructure(result, { isCollection: true })
|
|
441
443
|
|
|
442
|
-
validateJsonApiStructure(result, { isCollection: true });
|
|
443
|
-
|
|
444
444
|
// Countries should be deduplicated
|
|
445
|
-
const countries = result.included.filter(r => r.type === 'countries')
|
|
446
|
-
const uniqueCountryIds = [...new Set(countries.map(c => c.id))]
|
|
447
|
-
assert.equal(countries.length, uniqueCountryIds.length, 'Countries should be deduplicated')
|
|
448
|
-
|
|
445
|
+
const countries = result.included.filter(r => r.type === 'countries')
|
|
446
|
+
const uniqueCountryIds = [...new Set(countries.map(c => c.id))]
|
|
447
|
+
assert.equal(countries.length, uniqueCountryIds.length, 'Countries should be deduplicated')
|
|
448
|
+
|
|
449
449
|
// Authors should have their relationships populated
|
|
450
|
-
const authors = result.included.filter(r => r.type === 'authors')
|
|
450
|
+
const authors = result.included.filter(r => r.type === 'authors')
|
|
451
451
|
authors.forEach(author => {
|
|
452
|
-
assert(author.relationships?.country?.data, 'Author should have country relationship')
|
|
453
|
-
assert(author.relationships?.books?.data, 'Author should have books relationship')
|
|
454
|
-
})
|
|
455
|
-
})
|
|
456
|
-
})
|
|
452
|
+
assert(author.relationships?.country?.data, 'Author should have country relationship')
|
|
453
|
+
assert(author.relationships?.books?.data, 'Author should have books relationship')
|
|
454
|
+
})
|
|
455
|
+
})
|
|
456
|
+
})
|
|
457
457
|
|
|
458
458
|
describe('Nested Includes with Sparse Fieldsets', () => {
|
|
459
459
|
beforeEach(async () => {
|
|
460
460
|
// Create test data with reviews
|
|
461
461
|
const country = await api.resources.countries.post({
|
|
462
462
|
inputRecord: createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
463
|
-
})
|
|
463
|
+
})
|
|
464
464
|
|
|
465
465
|
const publisher = await api.resources.publishers.post({
|
|
466
466
|
inputRecord: createJsonApiDocument('publishers', {
|
|
467
467
|
name: 'Publisher'
|
|
468
468
|
}, {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
})
|
|
469
|
+
country: createRelationship({ type: 'countries', id: String(country.id) })
|
|
470
|
+
})
|
|
471
|
+
})
|
|
472
472
|
|
|
473
473
|
const author = await api.resources.authors.post({
|
|
474
474
|
inputRecord: createJsonApiDocument('authors', {
|
|
475
475
|
name: 'Author'
|
|
476
476
|
}, {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
})
|
|
477
|
+
country: createRelationship({ type: 'countries', id: String(country.id) })
|
|
478
|
+
})
|
|
479
|
+
})
|
|
480
480
|
|
|
481
481
|
const book = await api.resources.books.post({
|
|
482
482
|
inputRecord: createJsonApiDocument('books', {
|
|
483
483
|
title: 'Book with Reviews'
|
|
484
484
|
}, {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
})
|
|
485
|
+
publisher: createRelationship({ type: 'publishers', id: String(publisher.id) })
|
|
486
|
+
})
|
|
487
|
+
})
|
|
488
488
|
|
|
489
489
|
await api.resources.book_authors.post({
|
|
490
490
|
inputRecord: createJsonApiDocument('book_authors', {}, {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
})
|
|
491
|
+
book: createRelationship({ type: 'books', id: String(book.id) }),
|
|
492
|
+
author: createRelationship({ type: 'authors', id: String(author.id) })
|
|
493
|
+
})
|
|
494
|
+
})
|
|
495
495
|
|
|
496
496
|
// Create reviews
|
|
497
497
|
await api.resources.reviews.post({
|
|
@@ -499,19 +499,19 @@ describe('Nested Include Operations', () => {
|
|
|
499
499
|
content: 'Great book!',
|
|
500
500
|
rating: 5
|
|
501
501
|
}, {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
})
|
|
502
|
+
book: createRelationship({ type: 'books', id: String(book.id) })
|
|
503
|
+
})
|
|
504
|
+
})
|
|
505
505
|
|
|
506
506
|
await api.resources.reviews.post({
|
|
507
507
|
inputRecord: createJsonApiDocument('reviews', {
|
|
508
508
|
content: 'Good read',
|
|
509
509
|
rating: 4
|
|
510
510
|
}, {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
})
|
|
514
|
-
})
|
|
511
|
+
book: createRelationship({ type: 'books', id: String(book.id) })
|
|
512
|
+
})
|
|
513
|
+
})
|
|
514
|
+
})
|
|
515
515
|
|
|
516
516
|
it('should respect sparse fieldsets with nested includes', async () => {
|
|
517
517
|
const result = await api.resources.authors.query({
|
|
@@ -524,68 +524,68 @@ describe('Nested Include Operations', () => {
|
|
|
524
524
|
}
|
|
525
525
|
},
|
|
526
526
|
simplified: false
|
|
527
|
-
})
|
|
527
|
+
})
|
|
528
|
+
|
|
529
|
+
validateJsonApiStructure(result, { isCollection: true })
|
|
528
530
|
|
|
529
|
-
validateJsonApiStructure(result, { isCollection: true });
|
|
530
|
-
|
|
531
531
|
// Check sparse fieldsets are applied
|
|
532
|
-
const author = result.data[0]
|
|
533
|
-
assert.deepEqual(Object.keys(author.attributes), ['name'], 'Author should only have name field')
|
|
534
|
-
|
|
535
|
-
const book = result.included.find(r => r.type === 'books')
|
|
536
|
-
assert(book, 'Should include book')
|
|
532
|
+
const author = result.data[0]
|
|
533
|
+
assert.deepEqual(Object.keys(author.attributes), ['name'], 'Author should only have name field')
|
|
534
|
+
|
|
535
|
+
const book = result.included.find(r => r.type === 'books')
|
|
536
|
+
assert(book, 'Should include book')
|
|
537
537
|
// Note: Some internal fields like pages and _relationshipMetadata might still be present
|
|
538
|
-
assert(book.attributes.title, 'Book should have title field')
|
|
539
|
-
assert(!book.attributes.isbn, 'Book should not have isbn field')
|
|
540
|
-
|
|
541
|
-
const review = result.included.find(r => r.type === 'reviews')
|
|
542
|
-
assert(review, 'Should include review')
|
|
543
|
-
assert(review.attributes.rating !== undefined, 'Review should have rating field')
|
|
544
|
-
assert(review.attributes.content === undefined, 'Review should not have content field')
|
|
545
|
-
|
|
538
|
+
assert(book.attributes.title, 'Book should have title field')
|
|
539
|
+
assert(!book.attributes.isbn, 'Book should not have isbn field')
|
|
540
|
+
|
|
541
|
+
const review = result.included.find(r => r.type === 'reviews')
|
|
542
|
+
assert(review, 'Should include review')
|
|
543
|
+
assert(review.attributes.rating !== undefined, 'Review should have rating field')
|
|
544
|
+
assert(review.attributes.content === undefined, 'Review should not have content field')
|
|
545
|
+
|
|
546
546
|
// Relationships should still be populated
|
|
547
|
-
assert(book.relationships?.reviews?.data, 'Book should have reviews relationship')
|
|
548
|
-
})
|
|
549
|
-
})
|
|
547
|
+
assert(book.relationships?.reviews?.data, 'Book should have reviews relationship')
|
|
548
|
+
})
|
|
549
|
+
})
|
|
550
550
|
|
|
551
551
|
describe('Circular Include Handling', () => {
|
|
552
552
|
beforeEach(async () => {
|
|
553
553
|
// Create simple circular relationship data
|
|
554
554
|
const country = await api.resources.countries.post({
|
|
555
555
|
inputRecord: createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
556
|
-
})
|
|
556
|
+
})
|
|
557
557
|
|
|
558
558
|
const publisher = await api.resources.publishers.post({
|
|
559
559
|
inputRecord: createJsonApiDocument('publishers', {
|
|
560
560
|
name: 'Publisher'
|
|
561
561
|
}, {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
})
|
|
562
|
+
country: createRelationship({ type: 'countries', id: String(country.id) })
|
|
563
|
+
})
|
|
564
|
+
})
|
|
565
565
|
|
|
566
566
|
const author = await api.resources.authors.post({
|
|
567
567
|
inputRecord: createJsonApiDocument('authors', {
|
|
568
568
|
name: 'Author'
|
|
569
569
|
}, {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
})
|
|
570
|
+
country: createRelationship({ type: 'countries', id: String(country.id) })
|
|
571
|
+
})
|
|
572
|
+
})
|
|
573
573
|
|
|
574
574
|
const book = await api.resources.books.post({
|
|
575
575
|
inputRecord: createJsonApiDocument('books', {
|
|
576
576
|
title: 'Book'
|
|
577
577
|
}, {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
})
|
|
578
|
+
publisher: createRelationship({ type: 'publishers', id: String(publisher.id) })
|
|
579
|
+
})
|
|
580
|
+
})
|
|
581
581
|
|
|
582
582
|
await api.resources.book_authors.post({
|
|
583
583
|
inputRecord: createJsonApiDocument('book_authors', {}, {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
})
|
|
588
|
-
})
|
|
584
|
+
book: createRelationship({ type: 'books', id: String(book.id) }),
|
|
585
|
+
author: createRelationship({ type: 'authors', id: String(author.id) })
|
|
586
|
+
})
|
|
587
|
+
})
|
|
588
|
+
})
|
|
589
589
|
|
|
590
590
|
it('should handle circular includes gracefully', async () => {
|
|
591
591
|
const result = await api.resources.books.query({
|
|
@@ -593,25 +593,25 @@ describe('Nested Include Operations', () => {
|
|
|
593
593
|
include: ['authors', 'authors.books', 'authors.books.authors']
|
|
594
594
|
},
|
|
595
595
|
simplified: false
|
|
596
|
-
})
|
|
596
|
+
})
|
|
597
|
+
|
|
598
|
+
validateJsonApiStructure(result, { isCollection: true })
|
|
597
599
|
|
|
598
|
-
validateJsonApiStructure(result, { isCollection: true });
|
|
599
|
-
|
|
600
600
|
// Should not cause infinite loops or errors
|
|
601
|
-
const books = result.data.concat(result.included.filter(r => r.type === 'books'))
|
|
602
|
-
const authors = result.included.filter(r => r.type === 'authors')
|
|
603
|
-
|
|
604
|
-
assert(books.length > 0, 'Should have books')
|
|
605
|
-
assert(authors.length > 0, 'Should have authors')
|
|
606
|
-
|
|
601
|
+
const books = result.data.concat(result.included.filter(r => r.type === 'books'))
|
|
602
|
+
const authors = result.included.filter(r => r.type === 'authors')
|
|
603
|
+
|
|
604
|
+
assert(books.length > 0, 'Should have books')
|
|
605
|
+
assert(authors.length > 0, 'Should have authors')
|
|
606
|
+
|
|
607
607
|
// All relationships should be properly populated
|
|
608
608
|
books.forEach(book => {
|
|
609
|
-
assert(book.relationships?.authors?.data, 'Book should have authors relationship')
|
|
610
|
-
})
|
|
611
|
-
|
|
609
|
+
assert(book.relationships?.authors?.data, 'Book should have authors relationship')
|
|
610
|
+
})
|
|
611
|
+
|
|
612
612
|
authors.forEach(author => {
|
|
613
|
-
assert(author.relationships?.books?.data, 'Author should have books relationship')
|
|
614
|
-
})
|
|
615
|
-
})
|
|
616
|
-
})
|
|
617
|
-
})
|
|
613
|
+
assert(author.relationships?.books?.data, 'Author should have books relationship')
|
|
614
|
+
})
|
|
615
|
+
})
|
|
616
|
+
})
|
|
617
|
+
})
|