json-rest-api 1.0.29 → 2.0.0
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/LICENSE +10 -670
- package/LICENSE-GPL-3.0 +674 -0
- package/LICENSE-MIT +21 -0
- package/README.md +39 -30
- package/docs/API.md +744 -1273
- package/docs/GUIDE/01-getting-started.md +338 -0
- package/docs/GUIDE/02-resources-and-relationships.md +33 -0
- package/docs/GUIDE/03-running-example.md +87 -0
- package/docs/GUIDE/04-creating-and-querying.md +181 -0
- package/docs/GUIDE/05-belongs-to.md +177 -0
- package/docs/GUIDE/06-has-many.md +184 -0
- package/docs/GUIDE/07-polymorphic-relationships.md +191 -0
- package/docs/GUIDE/08-many-to-many.md +184 -0
- package/docs/GUIDE/09-pagination-and-sorting.md +149 -0
- package/docs/GUIDE/10-put-and-patch.md +174 -0
- package/docs/GUIDE/11-relationship-endpoints.md +174 -0
- package/docs/GUIDE/12-field-transformations.md +287 -0
- package/docs/GUIDE/13-hooks-and-lifecycle.md +479 -0
- package/docs/GUIDE/14-custom-resource-methods.md +125 -0
- package/docs/GUIDE/15-query-projections.md +266 -0
- package/docs/GUIDE/{GUIDE_X_Autofiltering.md → 16-autofiltering.md} +77 -15
- package/docs/GUIDE/{GUIDE_X_Row_Policies.md → 17-row-policies.md} +137 -44
- package/docs/GUIDE/18-labels.md +86 -0
- package/docs/GUIDE/19-managed-transactions.md +179 -0
- package/docs/GUIDE/20-transaction-outcomes.md +207 -0
- package/docs/GUIDE/21-schema-and-migrations.md +634 -0
- package/docs/GUIDE/22-version-field-migrations.md +208 -0
- package/docs/GUIDE/23-fastify.md +179 -0
- package/docs/GUIDE/24-cors.md +172 -0
- package/docs/GUIDE/{GUIDE_X_URL_Management.md → 25-url-management.md} +7 -1
- package/docs/GUIDE/26-file-uploads.md +278 -0
- package/docs/GUIDE/27-bulk-operations.md +277 -0
- package/docs/GUIDE/28-socketio.md +325 -0
- package/docs/GUIDE/29-writing-plugins.md +278 -0
- package/docs/GUIDE/30-backend-capabilities.md +100 -0
- package/docs/GUIDE/31-positioning.md +181 -0
- package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +74 -30
- package/docs/GUIDE/33-migrating-to-v2.md +3827 -0
- package/docs/GUIDE/index.md +51 -59
- package/docs/QUICKSTART.md +120 -287
- package/docs/architecture.md +75 -0
- package/docs/contributing.md +178 -0
- package/index.d.ts +36 -0
- package/index.js +1 -22
- package/lib/enhanced-logger.js +91 -85
- package/lib/error-context.js +445 -5
- package/lib/error-formatter.js +219 -59
- package/lib/knex-transaction.js +64 -0
- package/lib/logger-types.d.ts +22 -0
- package/lib/missing-package.js +4 -0
- package/lib/rest-api-errors.js +103 -8
- package/lib/runtime/json-rest-api.js +172 -0
- package/lib/transaction-types.d.ts +59 -0
- package/package.json +97 -27
- package/plugins/core/bulk-operations-plugin.js +191 -184
- package/plugins/core/connectors/express-plugin.js +148 -219
- package/plugins/core/connectors/fastify-plugin.js +125 -82
- package/plugins/core/connectors/lib/busboy-detector.js +74 -156
- package/plugins/core/connectors/lib/connector-core.js +144 -20
- package/plugins/core/connectors/lib/formidable-detector.js +63 -120
- package/plugins/core/connectors/lib/http-validators.js +77 -0
- package/plugins/core/connectors/lib/multipart-helpers.js +18 -0
- package/plugins/core/connectors/lib/request-helpers.js +10 -1
- package/plugins/core/connectors/lib/transport-http-helpers.js +89 -8
- package/plugins/core/connectors/lib/transport-route-schemas.js +7 -4
- package/plugins/core/file-handling-plugin.js +87 -177
- package/plugins/core/lib/anyapi/anyapi-registry.js +213 -164
- package/plugins/core/lib/anyapi/canonical-link-store.js +507 -0
- package/plugins/core/lib/anyapi/canonical-relationship-reader.js +598 -0
- package/plugins/core/lib/anyapi/schema-utils.js +25 -16
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
- package/plugins/core/lib/dbIntrospection.js +228 -21
- package/plugins/core/lib/dbTablesOperations.js +409 -92
- package/plugins/core/lib/fractional-positioning.js +12 -48
- package/plugins/core/lib/querying/identifier-query.js +19 -0
- package/plugins/core/lib/querying/include-query-helpers.js +364 -0
- package/plugins/core/lib/querying/include-to-many.js +428 -0
- package/plugins/core/lib/querying/include-to-one.js +579 -0
- package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
- package/plugins/core/lib/querying/knex-pagination-helpers.js +48 -317
- package/plugins/core/lib/querying/knex-process-includes.js +5 -110
- package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
- package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
- package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
- package/plugins/core/lib/querying/knex-window-queries.js +62 -253
- package/plugins/core/lib/querying/query-builder-utils.js +44 -4
- package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
- package/plugins/core/lib/querying/query-constraint.js +27 -0
- package/plugins/core/lib/querying/query-field-sort-helpers.js +134 -37
- package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
- package/plugins/core/lib/querying/sort-helpers.js +13 -1
- package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
- package/plugins/core/lib/querying/url-helpers.js +11 -4
- package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
- package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +179 -194
- package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
- package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
- package/plugins/core/lib/querying-writing/field-utils.js +113 -92
- package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
- package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +100 -290
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
- package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
- package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
- package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
- package/plugins/core/lib/querying-writing/request-contracts.js +93 -38
- package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
- package/plugins/core/lib/querying-writing/response-options.js +35 -0
- package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
- package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
- package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
- package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
- package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
- package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
- package/plugins/core/lib/storage/storage-adapter.js +128 -62
- package/plugins/core/lib/storage/storage-mapping.js +78 -28
- package/plugins/core/lib/storage/storage-types.d.ts +502 -0
- package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
- package/plugins/core/lib/writing/relationship-processor.js +94 -158
- package/plugins/core/lib/writing/resource-version.js +172 -0
- package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
- package/plugins/core/rest-api-anyapi-knex-plugin.js +116 -2485
- package/plugins/core/rest-api-autofilter-plugin.js +17 -19
- package/plugins/core/rest-api-cors-plugin.js +52 -205
- package/plugins/core/rest-api-knex-plugin.js +45 -1077
- package/plugins/core/rest-api-label-plugin.js +25 -38
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
- package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
- package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
- package/plugins/core/rest-api-plugin-methods/common.js +291 -395
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
- package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -148
- package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
- package/plugins/core/rest-api-plugin-methods/get.js +36 -68
- package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
- package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
- package/plugins/core/rest-api-plugin-methods/post.js +51 -51
- package/plugins/core/rest-api-plugin-methods/put.js +69 -148
- package/plugins/core/rest-api-plugin-methods/query.js +37 -81
- package/plugins/core/rest-api-plugin-methods/release.js +2 -1
- package/plugins/core/rest-api-plugin.js +32 -46
- package/plugins/core/rest-api-positioning-plugin.js +183 -628
- package/plugins/core/rest-api-query-projections-plugin.js +19 -102
- package/plugins/core/rest-api-row-policy-plugin.js +3 -3
- package/plugins/core/socketio-plugin.js +364 -344
- package/plugins/storage/local-storage.js +40 -36
- package/types/autofilter.d.ts +57 -0
- package/types/bulk-methods.d.ts +63 -0
- package/types/errors.d.ts +118 -0
- package/types/fastify-options.d.ts +19 -0
- package/types/file-detectors.d.ts +33 -0
- package/types/file-storage.d.ts +48 -0
- package/types/hook-context.d.ts +180 -0
- package/types/plugin-options.d.ts +53 -0
- package/types/relationship-methods.d.ts +74 -0
- package/types/representations.d.ts +50 -0
- package/types/resource-methods.d.ts +95 -0
- package/types/resource-schema.d.ts +157 -0
- package/types/row-policy.d.ts +31 -0
- package/types/runtime.d.ts +132 -0
- package/types/transactions.d.ts +21 -0
- package/.claude/agents/library-guide-expert.md +0 -30
- package/.claude/agents/rest-api-internals-expert.md +0 -54
- package/.claude/settings.local.json +0 -70
- package/AGENTS.md +0 -34
- package/CLAUDE.md +0 -160
- package/CRUSH.md +0 -55
- package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
- package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
- package/TODO.todo +0 -13
- package/connectors_ideas/fastify.js +0 -217
- package/connectors_ideas/koa-plugin.js +0 -112
- package/docs/.bundle/config +0 -2
- package/docs/COMPARISON.md +0 -433
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
- package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
- package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
- package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
- package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
- package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1247
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
- package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
- package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
- package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
- package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
- package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
- package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
- package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
- package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
- package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
- package/docs/Gemfile +0 -6
- package/docs/Gemfile.lock +0 -308
- package/docs/ONBOARDING.md +0 -315
- package/docs/_config.yml +0 -36
- package/docs/_layouts/default.html +0 -123
- package/docs/apple-touch-icon.svg +0 -4
- package/docs/assets/css/style.scss +0 -392
- package/docs/favicon.svg +0 -4
- package/docs/index.md +0 -164
- package/docs/mini-guides/file-uploads-complete.md +0 -161
- package/eslint.config.js +0 -13
- package/examples/url-override-example.js +0 -137
- package/fixing_plan.md +0 -112
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
- package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
- package/plugins/storage/s3-storage.js +0 -93
- package/quickTest.js +0 -136
- package/scripts/debug-anyapi-review-filter.mjs +0 -63
- package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
- package/scripts/inspect-anyapi-descriptor.mjs +0 -19
- package/scripts/run-docs.js +0 -133
- package/test-debug.js +0 -59
- package/tests/README.md +0 -202
- package/tests/TEST_TEMPLATE.test.js +0 -178
- package/tests/anyapi-basic.test.js +0 -105
- package/tests/anyapi-cursor-pagination.test.js +0 -148
- package/tests/anyapi-custom-idproperty.test.js +0 -163
- package/tests/autofilter.test.js +0 -463
- package/tests/bulk-operations.test.js +0 -657
- package/tests/computed-fields.test.js +0 -586
- package/tests/cors.test.js +0 -834
- package/tests/curl.test.js +0 -467
- package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
- package/tests/custom-id-simplified-post.test.js +0 -75
- package/tests/custom-idproperty-relationships.test.js +0 -932
- package/tests/db-introspection.test.js +0 -521
- package/tests/db-table-operations.test.js +0 -798
- package/tests/fastify-plugin.test.js +0 -184
- package/tests/field-getters.test.js +0 -370
- package/tests/field-setters.test.js +0 -306
- package/tests/file-handling.test.js +0 -155
- package/tests/fixtures/api-configs.js +0 -2566
- package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
- package/tests/helpers/fake-fastify.js +0 -99
- package/tests/helpers/storage-mode.js +0 -65
- package/tests/helpers/test-utils.js +0 -203
- package/tests/http-connectors-parity.test.js +0 -395
- package/tests/id-normalization.test.js +0 -444
- package/tests/include-depth-validation.test.js +0 -331
- package/tests/includes.test.js +0 -427
- package/tests/local-storage.test.js +0 -96
- package/tests/logical-id-write-validation.test.js +0 -151
- package/tests/nested-includes.test.js +0 -617
- package/tests/pagination-boundaries.test.js +0 -154
- package/tests/pagination-contract.test.js +0 -122
- package/tests/pagination-cursor-multifield.test.js +0 -641
- package/tests/pagination-enhanced.test.js +0 -563
- package/tests/polymorphic-relationships.test.js +0 -347
- package/tests/polymorphic-simple.test.js +0 -148
- package/tests/positioning-advanced.test.js +0 -692
- package/tests/positioning-extended.test.js +0 -915
- package/tests/positioning-multifilter.test.js +0 -699
- package/tests/positioning.test.js +0 -560
- package/tests/post-commit-hooks.test.js +0 -120
- package/tests/projected-fields.test.js +0 -319
- package/tests/public-exports.test.js +0 -15
- package/tests/put-create-parity.test.js +0 -216
- package/tests/queries.test.js +0 -521
- package/tests/query-limits.test.js +0 -584
- package/tests/relationship-endpoints.test.js +0 -620
- package/tests/relationship-include-storage-adapter.test.js +0 -56
- package/tests/relationships.test.js +0 -681
- package/tests/rest-api.test.js +0 -337
- package/tests/return-record-settings.test.js +0 -184
- package/tests/row-policy.test.js +0 -489
- package/tests/s3-storage.test.js +0 -38
- package/tests/searchschema-knex-behavior.test.js +0 -187
- package/tests/searchschema-merge.test.js +0 -151
- package/tests/socketio.test.js +0 -860
- package/tests/storage-mapping.test.js +0 -362
- package/tests/temporal-boundaries.test.js +0 -208
- package/tests/test-development-guide.md +0 -370
- package/tests/virtual-fields.test.js +0 -404
|
@@ -1,620 +0,0 @@
|
|
|
1
|
-
import { describe, it, before, after, beforeEach } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import express from 'express'
|
|
4
|
-
import request from 'supertest'
|
|
5
|
-
import knexLib from 'knex'
|
|
6
|
-
import {
|
|
7
|
-
validateJsonApiStructure,
|
|
8
|
-
cleanTables,
|
|
9
|
-
createJsonApiDocument,
|
|
10
|
-
createRelationship,
|
|
11
|
-
createToManyRelationship,
|
|
12
|
-
resourceIdentifier,
|
|
13
|
-
countRecords
|
|
14
|
-
} from './helpers/test-utils.js'
|
|
15
|
-
import { createBasicApi } from './fixtures/api-configs.js'
|
|
16
|
-
|
|
17
|
-
// Create Knex instance for tests
|
|
18
|
-
const knex = knexLib({
|
|
19
|
-
client: 'better-sqlite3',
|
|
20
|
-
connection: {
|
|
21
|
-
filename: ':memory:'
|
|
22
|
-
},
|
|
23
|
-
useNullAsDefault: true
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
// API instance that persists across tests
|
|
27
|
-
let api
|
|
28
|
-
let app
|
|
29
|
-
const relationshipCommitEvents = []
|
|
30
|
-
const relationshipRollbackEvents = []
|
|
31
|
-
|
|
32
|
-
describe('Relationship Endpoints Plugin', () => {
|
|
33
|
-
before(async () => {
|
|
34
|
-
// Initialize API with the relationships plugin
|
|
35
|
-
app = express()
|
|
36
|
-
api = await createBasicApi(knex, {
|
|
37
|
-
express: {
|
|
38
|
-
mountPath: '' // No mount path for this test
|
|
39
|
-
},
|
|
40
|
-
includeExpress: true
|
|
41
|
-
})
|
|
42
|
-
api.http.express.mount(app)
|
|
43
|
-
|
|
44
|
-
await api.customize({
|
|
45
|
-
hooks: {
|
|
46
|
-
afterCommit: {
|
|
47
|
-
functionName: 'relationship-after-commit-test-tracker',
|
|
48
|
-
handler: async ({ context }) => {
|
|
49
|
-
if (context.method?.endsWith('Relationship')) {
|
|
50
|
-
relationshipCommitEvents.push({
|
|
51
|
-
method: context.method,
|
|
52
|
-
shouldCommit: context.shouldCommit,
|
|
53
|
-
hasTransaction: !!context.transaction
|
|
54
|
-
})
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
afterRollback: {
|
|
59
|
-
functionName: 'relationship-after-rollback-test-tracker',
|
|
60
|
-
handler: async ({ context }) => {
|
|
61
|
-
if (context.method?.endsWith('Relationship')) {
|
|
62
|
-
relationshipRollbackEvents.push({
|
|
63
|
-
method: context.method,
|
|
64
|
-
shouldCommit: context.shouldCommit,
|
|
65
|
-
hasTransaction: !!context.transaction
|
|
66
|
-
})
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
})
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
after(async () => {
|
|
75
|
-
// Close database connection
|
|
76
|
-
await knex.destroy()
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
describe('Many-to-Many Relationship Endpoints', () => {
|
|
80
|
-
let countryId
|
|
81
|
-
let book1Id
|
|
82
|
-
let author1Id
|
|
83
|
-
let author2Id
|
|
84
|
-
let author3Id
|
|
85
|
-
|
|
86
|
-
beforeEach(async () => {
|
|
87
|
-
await cleanTables(knex, [
|
|
88
|
-
'basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors'
|
|
89
|
-
])
|
|
90
|
-
|
|
91
|
-
// Create test data
|
|
92
|
-
const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
93
|
-
const countryResult = await api.resources.countries.post({
|
|
94
|
-
inputRecord: countryDoc,
|
|
95
|
-
simplified: false
|
|
96
|
-
})
|
|
97
|
-
countryId = countryResult.data.id
|
|
98
|
-
|
|
99
|
-
// Create a book
|
|
100
|
-
const bookDoc = createJsonApiDocument('books',
|
|
101
|
-
{ title: 'Test Book' },
|
|
102
|
-
{ country: createRelationship(resourceIdentifier('countries', countryId)) }
|
|
103
|
-
)
|
|
104
|
-
const bookResult = await api.resources.books.post({
|
|
105
|
-
inputRecord: bookDoc,
|
|
106
|
-
simplified: false
|
|
107
|
-
})
|
|
108
|
-
book1Id = bookResult.data.id
|
|
109
|
-
|
|
110
|
-
// Create authors
|
|
111
|
-
const author1Doc = createJsonApiDocument('authors', { name: 'Author One' })
|
|
112
|
-
const author1Result = await api.resources.authors.post({
|
|
113
|
-
inputRecord: author1Doc,
|
|
114
|
-
simplified: false
|
|
115
|
-
})
|
|
116
|
-
author1Id = author1Result.data.id
|
|
117
|
-
|
|
118
|
-
const author2Doc = createJsonApiDocument('authors', { name: 'Author Two' })
|
|
119
|
-
const author2Result = await api.resources.authors.post({
|
|
120
|
-
inputRecord: author2Doc,
|
|
121
|
-
simplified: false
|
|
122
|
-
})
|
|
123
|
-
author2Id = author2Result.data.id
|
|
124
|
-
|
|
125
|
-
const author3Doc = createJsonApiDocument('authors', { name: 'Author Three' })
|
|
126
|
-
const author3Result = await api.resources.authors.post({
|
|
127
|
-
inputRecord: author3Doc,
|
|
128
|
-
simplified: false
|
|
129
|
-
})
|
|
130
|
-
author3Id = author3Result.data.id
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
it('should get empty relationship data for new book', async () => {
|
|
134
|
-
const result = await api.resources.books.getRelationship({
|
|
135
|
-
id: book1Id,
|
|
136
|
-
relationshipName: 'authors'
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
assert(result.links, 'Should have links')
|
|
140
|
-
assert.equal(result.links.self, `/books/${book1Id}/relationships/authors`)
|
|
141
|
-
assert.equal(result.links.related, `/books/${book1Id}/authors`)
|
|
142
|
-
assert(Array.isArray(result.data), 'Data should be an array')
|
|
143
|
-
assert.equal(result.data.length, 0, 'Should have no authors')
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
it('should add authors to book via POST relationship endpoint', async () => {
|
|
147
|
-
// Add authors via relationship endpoint
|
|
148
|
-
await api.resources.books.postRelationship({
|
|
149
|
-
id: book1Id,
|
|
150
|
-
relationshipName: 'authors',
|
|
151
|
-
relationshipData: [
|
|
152
|
-
resourceIdentifier('authors', author1Id),
|
|
153
|
-
resourceIdentifier('authors', author2Id)
|
|
154
|
-
]
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
// Verify authors were added via getRelationship
|
|
158
|
-
const relResult = await api.resources.books.getRelationship({
|
|
159
|
-
id: book1Id,
|
|
160
|
-
relationshipName: 'authors'
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
assert.equal(relResult.data.length, 2, 'Should have 2 authors')
|
|
164
|
-
assert.deepEqual(relResult.data[0], resourceIdentifier('authors', author1Id))
|
|
165
|
-
assert.deepEqual(relResult.data[1], resourceIdentifier('authors', author2Id))
|
|
166
|
-
|
|
167
|
-
// Verify pivot table records
|
|
168
|
-
const pivotCount = await countRecords(knex, 'basic_book_authors')
|
|
169
|
-
assert.equal(pivotCount, 2, 'Should have 2 pivot records')
|
|
170
|
-
|
|
171
|
-
// Verify through GET with include
|
|
172
|
-
const getResult = await api.resources.books.get({
|
|
173
|
-
id: book1Id,
|
|
174
|
-
queryParams: {
|
|
175
|
-
include: ['authors']
|
|
176
|
-
},
|
|
177
|
-
simplified: false
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
assert(getResult.included, 'Should have included data')
|
|
181
|
-
assert.equal(getResult.included.length, 2, 'Should include 2 authors')
|
|
182
|
-
assert(getResult.data.relationships.authors, 'Should have authors relationship')
|
|
183
|
-
assert.equal(getResult.data.relationships.authors.data.length, 2)
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
it('should get related resources via GET related endpoint', async () => {
|
|
187
|
-
// Add authors first
|
|
188
|
-
await api.resources.books.postRelationship({
|
|
189
|
-
id: book1Id,
|
|
190
|
-
relationshipName: 'authors',
|
|
191
|
-
relationshipData: [
|
|
192
|
-
resourceIdentifier('authors', author1Id),
|
|
193
|
-
resourceIdentifier('authors', author2Id)
|
|
194
|
-
]
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
// Get related resources
|
|
198
|
-
const result = await api.resources.books.getRelated({
|
|
199
|
-
id: book1Id,
|
|
200
|
-
relationshipName: 'authors'
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
// Verify response
|
|
204
|
-
validateJsonApiStructure(result, true) // true for collection
|
|
205
|
-
assert.equal(result.data.length, 2, 'Should have 2 authors')
|
|
206
|
-
assert.equal(result.data[0].type, 'authors')
|
|
207
|
-
assert.equal(result.data[1].type, 'authors')
|
|
208
|
-
|
|
209
|
-
const authorNames = result.data.map(a => a.attributes.name).sort()
|
|
210
|
-
assert.deepEqual(authorNames, ['Author One', 'Author Two'])
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
it('should remove specific authors via DELETE relationship endpoint', async () => {
|
|
214
|
-
// Add all authors first
|
|
215
|
-
await api.resources.books.postRelationship({
|
|
216
|
-
id: book1Id,
|
|
217
|
-
relationshipName: 'authors',
|
|
218
|
-
relationshipData: [
|
|
219
|
-
resourceIdentifier('authors', author1Id),
|
|
220
|
-
resourceIdentifier('authors', author2Id),
|
|
221
|
-
resourceIdentifier('authors', author3Id)
|
|
222
|
-
]
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
// Verify all were added
|
|
226
|
-
let pivotCount = await countRecords(knex, 'basic_book_authors')
|
|
227
|
-
assert.equal(pivotCount, 3, 'Should have 3 pivot records')
|
|
228
|
-
|
|
229
|
-
// Remove one author
|
|
230
|
-
await api.resources.books.deleteRelationship({
|
|
231
|
-
id: book1Id,
|
|
232
|
-
relationshipName: 'authors',
|
|
233
|
-
relationshipData: [
|
|
234
|
-
resourceIdentifier('authors', author2Id)
|
|
235
|
-
]
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
// Verify only two authors remain
|
|
239
|
-
const relResult = await api.resources.books.getRelationship({
|
|
240
|
-
id: book1Id,
|
|
241
|
-
relationshipName: 'authors'
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
assert.equal(relResult.data.length, 2, 'Should have 2 authors remaining')
|
|
245
|
-
assert.deepEqual(relResult.data[0], resourceIdentifier('authors', author1Id))
|
|
246
|
-
assert.deepEqual(relResult.data[1], resourceIdentifier('authors', author3Id))
|
|
247
|
-
|
|
248
|
-
// Verify pivot table
|
|
249
|
-
pivotCount = await countRecords(knex, 'basic_book_authors')
|
|
250
|
-
assert.equal(pivotCount, 2, 'Should have 2 pivot records')
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
it('should handle errors when relationship does not exist', async () => {
|
|
254
|
-
await assert.rejects(
|
|
255
|
-
api.resources.books.getRelationship({
|
|
256
|
-
id: book1Id,
|
|
257
|
-
relationshipName: 'nonexistent'
|
|
258
|
-
}),
|
|
259
|
-
{
|
|
260
|
-
message: /Relationship 'nonexistent' not found/
|
|
261
|
-
}
|
|
262
|
-
)
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
it('should handle errors when posting to belongsTo relationship', async () => {
|
|
266
|
-
await assert.rejects(
|
|
267
|
-
api.resources.books.postRelationship({
|
|
268
|
-
id: book1Id,
|
|
269
|
-
relationshipName: 'country',
|
|
270
|
-
relationshipData: [
|
|
271
|
-
resourceIdentifier('countries', countryId)
|
|
272
|
-
]
|
|
273
|
-
}),
|
|
274
|
-
{
|
|
275
|
-
message: /Cannot POST to to-one relationship/
|
|
276
|
-
}
|
|
277
|
-
)
|
|
278
|
-
})
|
|
279
|
-
|
|
280
|
-
it('should work with inverse relationship (authors to books)', async () => {
|
|
281
|
-
// First add the relationship from book side
|
|
282
|
-
await api.resources.books.postRelationship({
|
|
283
|
-
id: book1Id,
|
|
284
|
-
relationshipName: 'authors',
|
|
285
|
-
relationshipData: [
|
|
286
|
-
resourceIdentifier('authors', author1Id)
|
|
287
|
-
]
|
|
288
|
-
})
|
|
289
|
-
|
|
290
|
-
// Now check from author side
|
|
291
|
-
const relResult = await api.resources.authors.getRelationship({
|
|
292
|
-
id: author1Id,
|
|
293
|
-
relationshipName: 'books'
|
|
294
|
-
})
|
|
295
|
-
|
|
296
|
-
assert.equal(relResult.data.length, 1, 'Author should have 1 book')
|
|
297
|
-
assert.deepEqual(relResult.data[0], resourceIdentifier('books', book1Id))
|
|
298
|
-
|
|
299
|
-
// Get related books
|
|
300
|
-
const booksResult = await api.resources.authors.getRelated({
|
|
301
|
-
id: author1Id,
|
|
302
|
-
relationshipName: 'books'
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
validateJsonApiStructure(booksResult, true)
|
|
306
|
-
assert.equal(booksResult.data.length, 1)
|
|
307
|
-
assert.equal(booksResult.data[0].attributes.title, 'Test Book')
|
|
308
|
-
})
|
|
309
|
-
})
|
|
310
|
-
|
|
311
|
-
describe('One-to-Many Relationship Endpoints', () => {
|
|
312
|
-
let countryId
|
|
313
|
-
let publisherId
|
|
314
|
-
let book1Id
|
|
315
|
-
let book2Id
|
|
316
|
-
|
|
317
|
-
beforeEach(async () => {
|
|
318
|
-
await cleanTables(knex, [
|
|
319
|
-
'basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors'
|
|
320
|
-
])
|
|
321
|
-
|
|
322
|
-
// Create test data
|
|
323
|
-
const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
324
|
-
const countryResult = await api.resources.countries.post({
|
|
325
|
-
inputRecord: countryDoc,
|
|
326
|
-
simplified: false
|
|
327
|
-
})
|
|
328
|
-
countryId = countryResult.data.id
|
|
329
|
-
|
|
330
|
-
// Create a publisher
|
|
331
|
-
const publisherDoc = createJsonApiDocument('publishers',
|
|
332
|
-
{ name: 'Test Publisher' },
|
|
333
|
-
{ country: createRelationship(resourceIdentifier('countries', countryId)) }
|
|
334
|
-
)
|
|
335
|
-
const publisherResult = await api.resources.publishers.post({
|
|
336
|
-
inputRecord: publisherDoc,
|
|
337
|
-
simplified: false
|
|
338
|
-
})
|
|
339
|
-
publisherId = publisherResult.data.id
|
|
340
|
-
|
|
341
|
-
// Create books
|
|
342
|
-
const book1Doc = createJsonApiDocument('books',
|
|
343
|
-
{ title: 'Book One' },
|
|
344
|
-
{
|
|
345
|
-
country: createRelationship(resourceIdentifier('countries', countryId)),
|
|
346
|
-
publisher: createRelationship(resourceIdentifier('publishers', publisherId))
|
|
347
|
-
}
|
|
348
|
-
)
|
|
349
|
-
const book1Result = await api.resources.books.post({
|
|
350
|
-
inputRecord: book1Doc,
|
|
351
|
-
simplified: false
|
|
352
|
-
})
|
|
353
|
-
book1Id = book1Result.data.id
|
|
354
|
-
|
|
355
|
-
const book2Doc = createJsonApiDocument('books',
|
|
356
|
-
{ title: 'Book Two' },
|
|
357
|
-
{
|
|
358
|
-
country: createRelationship(resourceIdentifier('countries', countryId)),
|
|
359
|
-
publisher: createRelationship(resourceIdentifier('publishers', publisherId))
|
|
360
|
-
}
|
|
361
|
-
)
|
|
362
|
-
const book2Result = await api.resources.books.post({
|
|
363
|
-
inputRecord: book2Doc,
|
|
364
|
-
simplified: false
|
|
365
|
-
})
|
|
366
|
-
book2Id = book2Result.data.id
|
|
367
|
-
})
|
|
368
|
-
|
|
369
|
-
it('should get one-to-many relationship data', async () => {
|
|
370
|
-
const result = await api.resources.publishers.getRelationship({
|
|
371
|
-
id: publisherId,
|
|
372
|
-
relationshipName: 'books'
|
|
373
|
-
})
|
|
374
|
-
|
|
375
|
-
assert(result.links, 'Should have links')
|
|
376
|
-
assert.equal(result.links.self, `/publishers/${publisherId}/relationships/books`)
|
|
377
|
-
assert.equal(result.links.related, `/publishers/${publisherId}/books`)
|
|
378
|
-
assert(Array.isArray(result.data), 'Data should be an array')
|
|
379
|
-
assert.equal(result.data.length, 2, 'Should have 2 books')
|
|
380
|
-
})
|
|
381
|
-
|
|
382
|
-
it('should get related resources for one-to-many', async () => {
|
|
383
|
-
const result = await api.resources.publishers.getRelated({
|
|
384
|
-
id: publisherId,
|
|
385
|
-
relationshipName: 'books'
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
validateJsonApiStructure(result, true)
|
|
389
|
-
assert.equal(result.data.length, 2)
|
|
390
|
-
const titles = result.data.map(b => b.attributes.title).sort()
|
|
391
|
-
assert.deepEqual(titles, ['Book One', 'Book Two'])
|
|
392
|
-
})
|
|
393
|
-
|
|
394
|
-
// Note: belongsTo relationships (like 'publisher') are not accessible via relationship endpoints
|
|
395
|
-
// They are foreign key fields, not true JSON:API relationships
|
|
396
|
-
})
|
|
397
|
-
|
|
398
|
-
describe('Relationship Cardinality Validation', () => {
|
|
399
|
-
let countryId
|
|
400
|
-
let bookId
|
|
401
|
-
let authorId
|
|
402
|
-
|
|
403
|
-
const assertRelationshipIdValidationError = (error) => {
|
|
404
|
-
assert.equal(error.code, 'REST_API_VALIDATION')
|
|
405
|
-
assert.match(error.message, /id/i)
|
|
406
|
-
return true
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
beforeEach(async () => {
|
|
410
|
-
await cleanTables(knex, [
|
|
411
|
-
'basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors'
|
|
412
|
-
])
|
|
413
|
-
|
|
414
|
-
const country = await api.resources.countries.post({
|
|
415
|
-
inputRecord: createJsonApiDocument('countries', { name: 'USA', code: 'US' }),
|
|
416
|
-
simplified: false
|
|
417
|
-
})
|
|
418
|
-
countryId = country.data.id
|
|
419
|
-
|
|
420
|
-
const book = await api.resources.books.post({
|
|
421
|
-
inputRecord: createJsonApiDocument(
|
|
422
|
-
'books',
|
|
423
|
-
{ title: 'Cardinality Book' },
|
|
424
|
-
{ country: createRelationship(resourceIdentifier('countries', countryId)) }
|
|
425
|
-
),
|
|
426
|
-
simplified: false
|
|
427
|
-
})
|
|
428
|
-
bookId = book.data.id
|
|
429
|
-
|
|
430
|
-
const author = await api.resources.authors.post({
|
|
431
|
-
inputRecord: createJsonApiDocument('authors', { name: 'Author One' }),
|
|
432
|
-
simplified: false
|
|
433
|
-
})
|
|
434
|
-
authorId = author.data.id
|
|
435
|
-
})
|
|
436
|
-
|
|
437
|
-
it('rejects arrays for to-one relationship PATCH calls', async () => {
|
|
438
|
-
await assert.rejects(
|
|
439
|
-
api.resources.books.patchRelationship({
|
|
440
|
-
id: bookId,
|
|
441
|
-
relationshipName: 'country',
|
|
442
|
-
relationshipData: [resourceIdentifier('countries', countryId)]
|
|
443
|
-
}),
|
|
444
|
-
(error) => {
|
|
445
|
-
assert.equal(error.code, 'REST_API_VALIDATION')
|
|
446
|
-
assert.match(error.message, /single resource identifier or null/)
|
|
447
|
-
assert(error.details.fields.includes('data.relationships.country.data'))
|
|
448
|
-
return true
|
|
449
|
-
}
|
|
450
|
-
)
|
|
451
|
-
})
|
|
452
|
-
|
|
453
|
-
it('rejects scalars for to-many relationship PATCH calls', async () => {
|
|
454
|
-
await assert.rejects(
|
|
455
|
-
api.resources.books.patchRelationship({
|
|
456
|
-
id: bookId,
|
|
457
|
-
relationshipName: 'authors',
|
|
458
|
-
relationshipData: resourceIdentifier('authors', authorId)
|
|
459
|
-
}),
|
|
460
|
-
(error) => {
|
|
461
|
-
assert.equal(error.code, 'REST_API_VALIDATION')
|
|
462
|
-
assert.match(error.message, /array of resource identifiers/)
|
|
463
|
-
assert(error.details.fields.includes('data.relationships.authors.data'))
|
|
464
|
-
return true
|
|
465
|
-
}
|
|
466
|
-
)
|
|
467
|
-
})
|
|
468
|
-
|
|
469
|
-
it('rejects invalid relationship cardinality through Express transport', async () => {
|
|
470
|
-
const toOneResponse = await request(app)
|
|
471
|
-
.patch(`/books/${bookId}/relationships/country`)
|
|
472
|
-
.set('Content-Type', 'application/vnd.api+json')
|
|
473
|
-
.set('Accept', 'application/vnd.api+json')
|
|
474
|
-
.send({
|
|
475
|
-
data: [resourceIdentifier('countries', countryId)]
|
|
476
|
-
})
|
|
477
|
-
|
|
478
|
-
assert.equal(toOneResponse.status, 422)
|
|
479
|
-
assert.match(toOneResponse.body.errors?.[0]?.detail || '', /single resource identifier or null/)
|
|
480
|
-
|
|
481
|
-
const toManyResponse = await request(app)
|
|
482
|
-
.patch(`/books/${bookId}/relationships/authors`)
|
|
483
|
-
.set('Content-Type', 'application/vnd.api+json')
|
|
484
|
-
.set('Accept', 'application/vnd.api+json')
|
|
485
|
-
.send({
|
|
486
|
-
data: resourceIdentifier('authors', authorId)
|
|
487
|
-
})
|
|
488
|
-
|
|
489
|
-
assert.equal(toManyResponse.status, 422)
|
|
490
|
-
assert.match(toManyResponse.body.errors?.[0]?.detail || '', /array of resource identifiers/)
|
|
491
|
-
})
|
|
492
|
-
|
|
493
|
-
it('rejects malformed relationship identifiers through direct relationship methods', async () => {
|
|
494
|
-
await assert.rejects(
|
|
495
|
-
api.resources.books.postRelationship({
|
|
496
|
-
id: bookId,
|
|
497
|
-
relationshipName: 'authors',
|
|
498
|
-
relationshipData: [{ type: 'authors' }]
|
|
499
|
-
}),
|
|
500
|
-
assertRelationshipIdValidationError
|
|
501
|
-
)
|
|
502
|
-
|
|
503
|
-
await assert.rejects(
|
|
504
|
-
api.resources.books.patchRelationship({
|
|
505
|
-
id: bookId,
|
|
506
|
-
relationshipName: 'country',
|
|
507
|
-
relationshipData: { type: 'countries' }
|
|
508
|
-
}),
|
|
509
|
-
assertRelationshipIdValidationError
|
|
510
|
-
)
|
|
511
|
-
})
|
|
512
|
-
|
|
513
|
-
it('rejects malformed relationship identifiers through Express transport', async () => {
|
|
514
|
-
const response = await request(app)
|
|
515
|
-
.delete(`/books/${bookId}/relationships/authors`)
|
|
516
|
-
.set('Content-Type', 'application/vnd.api+json')
|
|
517
|
-
.set('Accept', 'application/vnd.api+json')
|
|
518
|
-
.send({
|
|
519
|
-
data: [{ type: 'authors' }]
|
|
520
|
-
})
|
|
521
|
-
|
|
522
|
-
assert.equal(response.status, 422)
|
|
523
|
-
assert.match(response.body.errors?.[0]?.detail || '', /id/i)
|
|
524
|
-
})
|
|
525
|
-
})
|
|
526
|
-
|
|
527
|
-
describe('Relationship Transaction Hooks', () => {
|
|
528
|
-
let countryId
|
|
529
|
-
let secondCountryId
|
|
530
|
-
let bookId
|
|
531
|
-
let authorId
|
|
532
|
-
|
|
533
|
-
beforeEach(async () => {
|
|
534
|
-
relationshipCommitEvents.length = 0
|
|
535
|
-
relationshipRollbackEvents.length = 0
|
|
536
|
-
|
|
537
|
-
await cleanTables(knex, [
|
|
538
|
-
'basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors'
|
|
539
|
-
])
|
|
540
|
-
|
|
541
|
-
const country = await api.resources.countries.post({
|
|
542
|
-
inputRecord: createJsonApiDocument('countries', { name: 'USA', code: 'US' }),
|
|
543
|
-
simplified: false
|
|
544
|
-
})
|
|
545
|
-
countryId = country.data.id
|
|
546
|
-
|
|
547
|
-
const secondCountry = await api.resources.countries.post({
|
|
548
|
-
inputRecord: createJsonApiDocument('countries', { name: 'Canada', code: 'CA' }),
|
|
549
|
-
simplified: false
|
|
550
|
-
})
|
|
551
|
-
secondCountryId = secondCountry.data.id
|
|
552
|
-
|
|
553
|
-
const book = await api.resources.books.post({
|
|
554
|
-
inputRecord: createJsonApiDocument(
|
|
555
|
-
'books',
|
|
556
|
-
{ title: 'Hook Book' },
|
|
557
|
-
{ country: createRelationship(resourceIdentifier('countries', countryId)) }
|
|
558
|
-
),
|
|
559
|
-
simplified: false
|
|
560
|
-
})
|
|
561
|
-
bookId = book.data.id
|
|
562
|
-
|
|
563
|
-
const author = await api.resources.authors.post({
|
|
564
|
-
inputRecord: createJsonApiDocument('authors', { name: 'Hook Author' }),
|
|
565
|
-
simplified: false
|
|
566
|
-
})
|
|
567
|
-
authorId = author.data.id
|
|
568
|
-
|
|
569
|
-
relationshipCommitEvents.length = 0
|
|
570
|
-
relationshipRollbackEvents.length = 0
|
|
571
|
-
})
|
|
572
|
-
|
|
573
|
-
it('runs afterCommit for relationship methods that own their transaction', async () => {
|
|
574
|
-
await api.resources.books.postRelationship({
|
|
575
|
-
id: bookId,
|
|
576
|
-
relationshipName: 'authors',
|
|
577
|
-
relationshipData: [resourceIdentifier('authors', authorId)]
|
|
578
|
-
})
|
|
579
|
-
|
|
580
|
-
await api.resources.books.patchRelationship({
|
|
581
|
-
id: bookId,
|
|
582
|
-
relationshipName: 'country',
|
|
583
|
-
relationshipData: resourceIdentifier('countries', secondCountryId)
|
|
584
|
-
})
|
|
585
|
-
|
|
586
|
-
await api.resources.books.deleteRelationship({
|
|
587
|
-
id: bookId,
|
|
588
|
-
relationshipName: 'authors',
|
|
589
|
-
relationshipData: [resourceIdentifier('authors', authorId)]
|
|
590
|
-
})
|
|
591
|
-
|
|
592
|
-
assert.deepEqual(
|
|
593
|
-
relationshipCommitEvents.map((event) => event.method),
|
|
594
|
-
['postRelationship', 'patchRelationship', 'deleteRelationship']
|
|
595
|
-
)
|
|
596
|
-
assert(relationshipCommitEvents.every((event) => event.shouldCommit))
|
|
597
|
-
assert(relationshipCommitEvents.every((event) => event.hasTransaction))
|
|
598
|
-
assert.equal(relationshipRollbackEvents.length, 0)
|
|
599
|
-
})
|
|
600
|
-
|
|
601
|
-
it('preserves afterRollback for failed relationship methods that own their transaction', async () => {
|
|
602
|
-
await assert.rejects(
|
|
603
|
-
api.resources.books.postRelationship({
|
|
604
|
-
id: bookId,
|
|
605
|
-
relationshipName: 'missing',
|
|
606
|
-
relationshipData: []
|
|
607
|
-
}),
|
|
608
|
-
/Relationship 'missing' not found/
|
|
609
|
-
)
|
|
610
|
-
|
|
611
|
-
assert.deepEqual(
|
|
612
|
-
relationshipRollbackEvents.map((event) => event.method),
|
|
613
|
-
['postRelationship']
|
|
614
|
-
)
|
|
615
|
-
assert(relationshipRollbackEvents[0].shouldCommit)
|
|
616
|
-
assert(relationshipRollbackEvents[0].hasTransaction)
|
|
617
|
-
assert.equal(relationshipCommitEvents.length, 0)
|
|
618
|
-
})
|
|
619
|
-
})
|
|
620
|
-
})
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { after, before, beforeEach, describe, it } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import knexLib from 'knex'
|
|
4
|
-
import {
|
|
5
|
-
createRelationshipIncludeStorageAdapterApi,
|
|
6
|
-
seedRelationshipIncludeStorageAdapterApi
|
|
7
|
-
} from './fixtures/api-configs.js'
|
|
8
|
-
import { cleanTables } from './helpers/test-utils.js'
|
|
9
|
-
|
|
10
|
-
const knex = knexLib({
|
|
11
|
-
client: 'better-sqlite3',
|
|
12
|
-
connection: {
|
|
13
|
-
filename: ':memory:'
|
|
14
|
-
},
|
|
15
|
-
useNullAsDefault: true
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
describe('Relationship include storage adapters', () => {
|
|
19
|
-
let api
|
|
20
|
-
|
|
21
|
-
before(async () => {
|
|
22
|
-
api = await createRelationshipIncludeStorageAdapterApi(knex)
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
beforeEach(async () => {
|
|
26
|
-
await cleanTables(knex, [
|
|
27
|
-
'uninitialized_bookings',
|
|
28
|
-
'uninitialized_pets'
|
|
29
|
-
])
|
|
30
|
-
await seedRelationshipIncludeStorageAdapterApi(knex)
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
after(async () => {
|
|
34
|
-
await knex.destroy()
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it('maps sparse fields for an included scope whose adapter is not initialized', async () => {
|
|
38
|
-
assert.equal(api.resources.pets.vars.storageAdapter, undefined)
|
|
39
|
-
|
|
40
|
-
const result = await api.resources.bookings.get({
|
|
41
|
-
id: 41,
|
|
42
|
-
queryParams: {
|
|
43
|
-
include: ['pet'],
|
|
44
|
-
fields: {
|
|
45
|
-
bookings: 'reference,petId',
|
|
46
|
-
pets: 'firstName'
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
simplified: false
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
const includedPet = result.included.find(resource => resource.type === 'pets')
|
|
53
|
-
assert(includedPet)
|
|
54
|
-
assert.deepEqual(includedPet.attributes, { firstName: 'Coco' })
|
|
55
|
-
})
|
|
56
|
-
})
|