json-rest-api 1.0.28 → 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 +746 -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} +90 -31
- 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 -174
- 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 +67 -330
- 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 +205 -30
- package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
- package/plugins/core/lib/querying/sort-helpers.js +20 -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 +238 -187
- 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 +111 -287
- 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 +109 -44
- 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 +142 -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 -2509
- 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 -1069
- 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 -385
- 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 -145
- 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 +38 -69
- 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 +39 -82
- 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 -1238
- 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 -150
- 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 -118
- package/tests/fixtures/api-configs.js +0 -2509
- 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-cursor-multifield.test.js +0 -643
- 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/test-development-guide.md +0 -370
- package/tests/virtual-fields.test.js +0 -404
|
@@ -1,657 +0,0 @@
|
|
|
1
|
-
import { describe, it, before, beforeEach, after } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import knexLib from 'knex'
|
|
4
|
-
import { createBulkOperationsApi } from './fixtures/api-configs.js'
|
|
5
|
-
import {
|
|
6
|
-
validateJsonApiStructure,
|
|
7
|
-
resourceIdentifier,
|
|
8
|
-
cleanTables,
|
|
9
|
-
createJsonApiDocument,
|
|
10
|
-
createRelationship,
|
|
11
|
-
createToManyRelationship
|
|
12
|
-
} from './helpers/test-utils.js'
|
|
13
|
-
|
|
14
|
-
// Create Knex instance for tests
|
|
15
|
-
const knex = knexLib({
|
|
16
|
-
client: 'better-sqlite3',
|
|
17
|
-
connection: {
|
|
18
|
-
filename: ':memory:'
|
|
19
|
-
},
|
|
20
|
-
useNullAsDefault: true
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
// API instance that persists across tests
|
|
24
|
-
let api
|
|
25
|
-
|
|
26
|
-
describe('Bulk Operations', () => {
|
|
27
|
-
before(async () => {
|
|
28
|
-
// Initialize API once
|
|
29
|
-
api = await createBulkOperationsApi(knex)
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
after(async () => {
|
|
33
|
-
// Close database connection to allow tests to exit
|
|
34
|
-
await knex.destroy()
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
describe('Bulk Create (bulkPost)', () => {
|
|
38
|
-
beforeEach(async () => {
|
|
39
|
-
// Clean all tables before each test
|
|
40
|
-
await cleanTables(knex, [
|
|
41
|
-
'basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors'
|
|
42
|
-
])
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('should create multiple records atomically', async () => {
|
|
46
|
-
const records = [
|
|
47
|
-
createJsonApiDocument('authors', { name: 'Author One' }),
|
|
48
|
-
createJsonApiDocument('authors', { name: 'Author Two' }),
|
|
49
|
-
createJsonApiDocument('authors', { name: 'Author Three' })
|
|
50
|
-
]
|
|
51
|
-
|
|
52
|
-
const result = await api.scopes.authors.bulkPost({
|
|
53
|
-
inputRecords: records,
|
|
54
|
-
atomic: true
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
// Validate response structure
|
|
58
|
-
assert(result.data, 'Should have data array')
|
|
59
|
-
assert(Array.isArray(result.data), 'Data should be an array')
|
|
60
|
-
assert.equal(result.data.length, 3, 'Should return 3 created records')
|
|
61
|
-
|
|
62
|
-
// Validate meta
|
|
63
|
-
assert.equal(result.meta.total, 3)
|
|
64
|
-
assert.equal(result.meta.succeeded, 3)
|
|
65
|
-
assert.equal(result.meta.failed, 0)
|
|
66
|
-
assert.equal(result.meta.atomic, true)
|
|
67
|
-
|
|
68
|
-
// Validate each created record
|
|
69
|
-
result.data.forEach((record, index) => {
|
|
70
|
-
validateJsonApiStructure({ data: record }, false)
|
|
71
|
-
assert.equal(record.type, 'authors')
|
|
72
|
-
assert(record.id, 'Should have generated ID')
|
|
73
|
-
assert.equal(record.attributes.name, `Author ${['One', 'Two', 'Three'][index]}`)
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
// Verify via API query
|
|
77
|
-
const queryResult = await api.resources.authors.query({ simplified: false })
|
|
78
|
-
assert.equal(queryResult.data.length, 3, 'Should have 3 records via query')
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('should rollback all records on error in atomic mode', async () => {
|
|
82
|
-
const records = [
|
|
83
|
-
createJsonApiDocument('authors', { name: 'Valid Author' }),
|
|
84
|
-
createJsonApiDocument('authors', {}), // Invalid - missing required name
|
|
85
|
-
createJsonApiDocument('authors', { name: 'Another Valid' })
|
|
86
|
-
]
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
await api.scopes.authors.bulkPost({
|
|
90
|
-
inputRecords: records,
|
|
91
|
-
atomic: true
|
|
92
|
-
})
|
|
93
|
-
assert.fail('Should have thrown error')
|
|
94
|
-
} catch (error) {
|
|
95
|
-
assert(error.message.includes('validation failed') || error.message.includes('required'), 'Error should mention validation or required field')
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Verify no records were created via API
|
|
99
|
-
const queryResult = await api.resources.authors.query({ simplified: false })
|
|
100
|
-
assert.equal(queryResult.data.length, 0, 'Should have no records due to rollback')
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
it('should allow partial success in non-atomic mode', async () => {
|
|
104
|
-
const records = [
|
|
105
|
-
createJsonApiDocument('authors', { name: 'Valid Author 1' }),
|
|
106
|
-
createJsonApiDocument('authors', {}), // Invalid
|
|
107
|
-
createJsonApiDocument('authors', { name: 'Valid Author 2' }),
|
|
108
|
-
createJsonApiDocument('authors', { name: 'Valid Author 3' })
|
|
109
|
-
]
|
|
110
|
-
|
|
111
|
-
const result = await api.scopes.authors.bulkPost({
|
|
112
|
-
inputRecords: records,
|
|
113
|
-
atomic: false
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
// Check succeeded records
|
|
117
|
-
assert.equal(result.data.length, 3, 'Should have 3 successful records')
|
|
118
|
-
assert.equal(result.meta.succeeded, 3)
|
|
119
|
-
assert.equal(result.meta.failed, 1)
|
|
120
|
-
|
|
121
|
-
// Check errors
|
|
122
|
-
assert(result.errors, 'Should have errors array')
|
|
123
|
-
assert.equal(result.errors.length, 1)
|
|
124
|
-
|
|
125
|
-
// Error should be for missing name
|
|
126
|
-
assert.equal(result.errors[0].index, 1)
|
|
127
|
-
assert(result.errors[0].error.message.includes('validation failed') || result.errors[0].error.message.includes('required'))
|
|
128
|
-
|
|
129
|
-
// Verify via API
|
|
130
|
-
const queryResult = await api.resources.authors.query({ simplified: false })
|
|
131
|
-
assert.equal(queryResult.data.length, 3)
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
it('should create records with relationships', async () => {
|
|
135
|
-
// First create a country
|
|
136
|
-
const countryResult = await api.resources.countries.post({
|
|
137
|
-
inputRecord: createJsonApiDocument('countries', { name: 'Test Country', code: 'TC' }),
|
|
138
|
-
simplified: false
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
const records = [
|
|
142
|
-
createJsonApiDocument('publishers',
|
|
143
|
-
{ name: 'Publisher One' },
|
|
144
|
-
{ country: createRelationship(resourceIdentifier('countries', countryResult.data.id)) }
|
|
145
|
-
),
|
|
146
|
-
createJsonApiDocument('publishers',
|
|
147
|
-
{ name: 'Publisher Two' },
|
|
148
|
-
{ country: createRelationship(resourceIdentifier('countries', countryResult.data.id)) }
|
|
149
|
-
)
|
|
150
|
-
]
|
|
151
|
-
|
|
152
|
-
const result = await api.scopes.publishers.bulkPost({
|
|
153
|
-
inputRecords: records
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
assert.equal(result.data.length, 2)
|
|
157
|
-
result.data.forEach(publisher => {
|
|
158
|
-
assert.equal(publisher.relationships.country.data.id, countryResult.data.id)
|
|
159
|
-
})
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
it('should enforce max bulk operations limit', async () => {
|
|
163
|
-
const tooManyRecords = Array.from({ length: 101 }, (_, i) =>
|
|
164
|
-
createJsonApiDocument('authors', { name: `Author ${i}` })
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
try {
|
|
168
|
-
await api.scopes.authors.bulkPost({
|
|
169
|
-
inputRecords: tooManyRecords
|
|
170
|
-
})
|
|
171
|
-
assert.fail('Should have thrown error')
|
|
172
|
-
} catch (error) {
|
|
173
|
-
assert(error.message.includes('limited to 100'))
|
|
174
|
-
}
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
it('should respect atomic query parameter override', async () => {
|
|
178
|
-
const records = [
|
|
179
|
-
createJsonApiDocument('authors', { name: 'Valid' }),
|
|
180
|
-
createJsonApiDocument('authors', {}) // Invalid
|
|
181
|
-
]
|
|
182
|
-
|
|
183
|
-
// Override default atomic mode
|
|
184
|
-
const result = await api.scopes.authors.bulkPost({
|
|
185
|
-
inputRecords: records,
|
|
186
|
-
atomic: false // Override to non-atomic
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
assert.equal(result.meta.succeeded, 1)
|
|
190
|
-
assert.equal(result.meta.failed, 1)
|
|
191
|
-
assert.equal(result.meta.atomic, false)
|
|
192
|
-
})
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
describe('Bulk Update (bulkPatch)', () => {
|
|
196
|
-
const testData = {}
|
|
197
|
-
|
|
198
|
-
beforeEach(async () => {
|
|
199
|
-
await cleanTables(knex, [
|
|
200
|
-
'basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors'
|
|
201
|
-
])
|
|
202
|
-
|
|
203
|
-
// Create test data via API
|
|
204
|
-
const authors = await Promise.all([
|
|
205
|
-
api.resources.authors.post({
|
|
206
|
-
inputRecord: createJsonApiDocument('authors', { name: 'Author One' }),
|
|
207
|
-
simplified: false
|
|
208
|
-
}),
|
|
209
|
-
api.resources.authors.post({
|
|
210
|
-
inputRecord: createJsonApiDocument('authors', { name: 'Author Two' }),
|
|
211
|
-
simplified: false
|
|
212
|
-
}),
|
|
213
|
-
api.resources.authors.post({
|
|
214
|
-
inputRecord: createJsonApiDocument('authors', { name: 'Author Three' }),
|
|
215
|
-
simplified: false
|
|
216
|
-
})
|
|
217
|
-
])
|
|
218
|
-
|
|
219
|
-
testData.authorIds = authors.map(a => a.data.id)
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
it('should update multiple records atomically', async () => {
|
|
223
|
-
const operations = [
|
|
224
|
-
{
|
|
225
|
-
id: testData.authorIds[0],
|
|
226
|
-
data: {
|
|
227
|
-
type: 'authors',
|
|
228
|
-
id: testData.authorIds[0],
|
|
229
|
-
attributes: { name: 'Updated Author One' }
|
|
230
|
-
}
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
id: testData.authorIds[1],
|
|
234
|
-
data: {
|
|
235
|
-
type: 'authors',
|
|
236
|
-
id: testData.authorIds[1],
|
|
237
|
-
attributes: { name: 'Updated Author Two' }
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
]
|
|
241
|
-
|
|
242
|
-
const result = await api.scopes.authors.bulkPatch({
|
|
243
|
-
operations,
|
|
244
|
-
atomic: true
|
|
245
|
-
})
|
|
246
|
-
|
|
247
|
-
assert.equal(result.data.length, 2)
|
|
248
|
-
assert.equal(result.meta.succeeded, 2)
|
|
249
|
-
assert.equal(result.meta.failed, 0)
|
|
250
|
-
|
|
251
|
-
// Verify updates in response
|
|
252
|
-
assert.equal(result.data[0].attributes.name, 'Updated Author One')
|
|
253
|
-
assert.equal(result.data[1].attributes.name, 'Updated Author Two')
|
|
254
|
-
|
|
255
|
-
// Verify via API
|
|
256
|
-
const author1 = await api.resources.authors.get({
|
|
257
|
-
id: testData.authorIds[0],
|
|
258
|
-
simplified: false
|
|
259
|
-
})
|
|
260
|
-
assert.equal(author1.data.attributes.name, 'Updated Author One')
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
it('should handle non-existent IDs in non-atomic mode', async () => {
|
|
264
|
-
const operations = [
|
|
265
|
-
{
|
|
266
|
-
id: testData.authorIds[0],
|
|
267
|
-
data: {
|
|
268
|
-
type: 'authors',
|
|
269
|
-
id: testData.authorIds[0],
|
|
270
|
-
attributes: { name: 'Updated' }
|
|
271
|
-
}
|
|
272
|
-
},
|
|
273
|
-
{
|
|
274
|
-
id: '999999', // Non-existent
|
|
275
|
-
data: {
|
|
276
|
-
type: 'authors',
|
|
277
|
-
id: '999999',
|
|
278
|
-
attributes: { name: 'Should Fail' }
|
|
279
|
-
}
|
|
280
|
-
},
|
|
281
|
-
{
|
|
282
|
-
id: testData.authorIds[2],
|
|
283
|
-
data: {
|
|
284
|
-
type: 'authors',
|
|
285
|
-
id: testData.authorIds[2],
|
|
286
|
-
attributes: { name: 'Also Updated' }
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
]
|
|
290
|
-
|
|
291
|
-
const result = await api.scopes.authors.bulkPatch({
|
|
292
|
-
operations,
|
|
293
|
-
atomic: false
|
|
294
|
-
})
|
|
295
|
-
|
|
296
|
-
assert.equal(result.meta.succeeded, 2)
|
|
297
|
-
assert.equal(result.meta.failed, 1)
|
|
298
|
-
assert.equal(result.errors.length, 1)
|
|
299
|
-
assert.equal(result.errors[0].id, '999999')
|
|
300
|
-
})
|
|
301
|
-
|
|
302
|
-
it('should validate operation structure', async () => {
|
|
303
|
-
const invalidOperations = [
|
|
304
|
-
{ id: testData.authorIds[0] }, // Missing data
|
|
305
|
-
{ data: { type: 'authors', attributes: { name: 'Test' } } }, // Missing id
|
|
306
|
-
{
|
|
307
|
-
id: testData.authorIds[1],
|
|
308
|
-
data: {
|
|
309
|
-
type: 'authors',
|
|
310
|
-
id: testData.authorIds[1],
|
|
311
|
-
attributes: { name: 'Valid' }
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
]
|
|
315
|
-
|
|
316
|
-
const result = await api.scopes.authors.bulkPatch({
|
|
317
|
-
operations: invalidOperations,
|
|
318
|
-
atomic: false
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
assert.equal(result.meta.succeeded, 1)
|
|
322
|
-
assert.equal(result.meta.failed, 2)
|
|
323
|
-
assert.equal(result.errors[0].error.code, 'INVALID_OPERATION')
|
|
324
|
-
assert.equal(result.errors[1].error.code, 'INVALID_OPERATION')
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
it('should rollback all updates on error in atomic mode', async () => {
|
|
328
|
-
// Get original name via API
|
|
329
|
-
const originalAuthor = await api.resources.authors.get({
|
|
330
|
-
id: testData.authorIds[0],
|
|
331
|
-
simplified: false
|
|
332
|
-
})
|
|
333
|
-
|
|
334
|
-
const operations = [
|
|
335
|
-
{
|
|
336
|
-
id: testData.authorIds[0],
|
|
337
|
-
data: {
|
|
338
|
-
type: 'authors',
|
|
339
|
-
id: testData.authorIds[0],
|
|
340
|
-
attributes: { name: 'Should Be Rolled Back' }
|
|
341
|
-
}
|
|
342
|
-
},
|
|
343
|
-
{
|
|
344
|
-
id: '999999', // Will fail
|
|
345
|
-
data: {
|
|
346
|
-
type: 'authors',
|
|
347
|
-
id: '999999',
|
|
348
|
-
attributes: { name: 'Non-existent' }
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
]
|
|
352
|
-
|
|
353
|
-
try {
|
|
354
|
-
await api.scopes.authors.bulkPatch({
|
|
355
|
-
operations,
|
|
356
|
-
atomic: true
|
|
357
|
-
})
|
|
358
|
-
assert.fail('Should have thrown error')
|
|
359
|
-
} catch (error) {
|
|
360
|
-
// Expected error
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// Verify no changes were made via API
|
|
364
|
-
const author = await api.resources.authors.get({
|
|
365
|
-
id: testData.authorIds[0],
|
|
366
|
-
simplified: false
|
|
367
|
-
})
|
|
368
|
-
assert.equal(author.data.attributes.name, originalAuthor.data.attributes.name, 'Name should not have changed')
|
|
369
|
-
})
|
|
370
|
-
})
|
|
371
|
-
|
|
372
|
-
describe('Bulk Delete (bulkDelete)', () => {
|
|
373
|
-
const testData = {}
|
|
374
|
-
|
|
375
|
-
beforeEach(async () => {
|
|
376
|
-
await cleanTables(knex, [
|
|
377
|
-
'basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors'
|
|
378
|
-
])
|
|
379
|
-
|
|
380
|
-
// Create test data via API
|
|
381
|
-
const authors = await Promise.all(
|
|
382
|
-
Array.from({ length: 5 }, (_, i) =>
|
|
383
|
-
api.resources.authors.post({
|
|
384
|
-
inputRecord: createJsonApiDocument('authors', { name: `Author ${i + 1}` }),
|
|
385
|
-
simplified: false
|
|
386
|
-
})
|
|
387
|
-
)
|
|
388
|
-
)
|
|
389
|
-
|
|
390
|
-
testData.authorIds = authors.map(a => a.data.id)
|
|
391
|
-
})
|
|
392
|
-
|
|
393
|
-
it('should delete multiple records atomically', async () => {
|
|
394
|
-
const idsToDelete = [testData.authorIds[0], testData.authorIds[2], testData.authorIds[4]]
|
|
395
|
-
|
|
396
|
-
const result = await api.scopes.authors.bulkDelete({
|
|
397
|
-
ids: idsToDelete,
|
|
398
|
-
atomic: true
|
|
399
|
-
})
|
|
400
|
-
|
|
401
|
-
assert.equal(result.meta.total, 3)
|
|
402
|
-
assert.equal(result.meta.succeeded, 3)
|
|
403
|
-
assert.equal(result.meta.failed, 0)
|
|
404
|
-
assert.deepEqual(result.meta.deleted, idsToDelete)
|
|
405
|
-
|
|
406
|
-
// Verify via API query
|
|
407
|
-
const queryResult = await api.resources.authors.query({ simplified: false })
|
|
408
|
-
assert.equal(queryResult.data.length, 2, 'Should have 2 remaining records')
|
|
409
|
-
|
|
410
|
-
// Verify specific records were deleted
|
|
411
|
-
const remainingIds = queryResult.data.map(r => r.id)
|
|
412
|
-
assert(!remainingIds.includes(testData.authorIds[0]))
|
|
413
|
-
assert(remainingIds.includes(testData.authorIds[1]))
|
|
414
|
-
assert(!remainingIds.includes(testData.authorIds[2]))
|
|
415
|
-
})
|
|
416
|
-
|
|
417
|
-
it('should handle mixed valid/invalid IDs in non-atomic mode', async () => {
|
|
418
|
-
const idsToDelete = [
|
|
419
|
-
testData.authorIds[0],
|
|
420
|
-
'999999', // Non-existent
|
|
421
|
-
testData.authorIds[2],
|
|
422
|
-
'888888', // Non-existent
|
|
423
|
-
testData.authorIds[4]
|
|
424
|
-
]
|
|
425
|
-
|
|
426
|
-
const result = await api.scopes.authors.bulkDelete({
|
|
427
|
-
ids: idsToDelete,
|
|
428
|
-
atomic: false
|
|
429
|
-
})
|
|
430
|
-
|
|
431
|
-
assert.equal(result.meta.total, 5)
|
|
432
|
-
assert.equal(result.meta.succeeded, 3)
|
|
433
|
-
assert.equal(result.meta.failed, 2)
|
|
434
|
-
assert.equal(result.meta.deleted.length, 3)
|
|
435
|
-
|
|
436
|
-
assert.equal(result.errors.length, 2)
|
|
437
|
-
assert.equal(result.errors[0].id, '999999')
|
|
438
|
-
assert.equal(result.errors[1].id, '888888')
|
|
439
|
-
|
|
440
|
-
// Verify correct records were deleted via API
|
|
441
|
-
const queryResult = await api.resources.authors.query({ simplified: false })
|
|
442
|
-
assert.equal(queryResult.data.length, 2)
|
|
443
|
-
})
|
|
444
|
-
|
|
445
|
-
it('should rollback all deletes on error in atomic mode', async () => {
|
|
446
|
-
const idsToDelete = [
|
|
447
|
-
testData.authorIds[0],
|
|
448
|
-
testData.authorIds[1],
|
|
449
|
-
'999999' // Will fail
|
|
450
|
-
]
|
|
451
|
-
|
|
452
|
-
try {
|
|
453
|
-
await api.scopes.authors.bulkDelete({
|
|
454
|
-
ids: idsToDelete,
|
|
455
|
-
atomic: true
|
|
456
|
-
})
|
|
457
|
-
assert.fail('Should have thrown error')
|
|
458
|
-
} catch (error) {
|
|
459
|
-
// Expected error
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// Verify no records were deleted via API
|
|
463
|
-
const queryResult = await api.resources.authors.query({ simplified: false })
|
|
464
|
-
assert.equal(queryResult.data.length, 5, 'Should still have all 5 records')
|
|
465
|
-
})
|
|
466
|
-
|
|
467
|
-
it('should handle empty ID array', async () => {
|
|
468
|
-
try {
|
|
469
|
-
await api.scopes.authors.bulkDelete({
|
|
470
|
-
ids: []
|
|
471
|
-
})
|
|
472
|
-
assert.fail('Should have thrown error')
|
|
473
|
-
} catch (error) {
|
|
474
|
-
assert(error.message.includes('array'))
|
|
475
|
-
}
|
|
476
|
-
})
|
|
477
|
-
|
|
478
|
-
it('should respect relationships when deleting', async () => {
|
|
479
|
-
// Create country and publishers with relationship
|
|
480
|
-
const country = await api.resources.countries.post({
|
|
481
|
-
inputRecord: createJsonApiDocument('countries', { name: 'Test Country', code: 'TC' }),
|
|
482
|
-
simplified: false
|
|
483
|
-
})
|
|
484
|
-
|
|
485
|
-
const publishers = await Promise.all([
|
|
486
|
-
api.resources.publishers.post({
|
|
487
|
-
inputRecord: createJsonApiDocument('publishers',
|
|
488
|
-
{ name: 'Publisher 1' },
|
|
489
|
-
{ country: createRelationship(resourceIdentifier('countries', country.data.id)) }
|
|
490
|
-
),
|
|
491
|
-
simplified: false
|
|
492
|
-
}),
|
|
493
|
-
api.resources.publishers.post({
|
|
494
|
-
inputRecord: createJsonApiDocument('publishers',
|
|
495
|
-
{ name: 'Publisher 2' },
|
|
496
|
-
{ country: createRelationship(resourceIdentifier('countries', country.data.id)) }
|
|
497
|
-
),
|
|
498
|
-
simplified: false
|
|
499
|
-
})
|
|
500
|
-
])
|
|
501
|
-
|
|
502
|
-
// Create books referencing publishers
|
|
503
|
-
const book = await api.resources.books.post({
|
|
504
|
-
inputRecord: createJsonApiDocument('books',
|
|
505
|
-
{ title: 'Test Book' },
|
|
506
|
-
{
|
|
507
|
-
country: createRelationship(resourceIdentifier('countries', country.data.id)),
|
|
508
|
-
publisher: createRelationship(resourceIdentifier('publishers', publishers[0].data.id))
|
|
509
|
-
}
|
|
510
|
-
),
|
|
511
|
-
simplified: false
|
|
512
|
-
})
|
|
513
|
-
|
|
514
|
-
// Try to delete publisher that has books
|
|
515
|
-
const publisherIds = publishers.map(p => p.data.id)
|
|
516
|
-
|
|
517
|
-
const result = await api.scopes.publishers.bulkDelete({
|
|
518
|
-
ids: publisherIds,
|
|
519
|
-
atomic: false
|
|
520
|
-
})
|
|
521
|
-
|
|
522
|
-
// At least one should succeed (the one without books)
|
|
523
|
-
assert(result.meta.succeeded >= 1)
|
|
524
|
-
})
|
|
525
|
-
})
|
|
526
|
-
|
|
527
|
-
describe('Batch Processing', () => {
|
|
528
|
-
beforeEach(async () => {
|
|
529
|
-
await cleanTables(knex, [
|
|
530
|
-
'basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors'
|
|
531
|
-
])
|
|
532
|
-
})
|
|
533
|
-
|
|
534
|
-
it('should process large batches correctly', async () => {
|
|
535
|
-
// Create 50 records to test batching (batch size is 10 in config)
|
|
536
|
-
const records = Array.from({ length: 50 }, (_, i) =>
|
|
537
|
-
createJsonApiDocument('authors', { name: `Batch Author ${i + 1}` })
|
|
538
|
-
)
|
|
539
|
-
|
|
540
|
-
const result = await api.scopes.authors.bulkPost({
|
|
541
|
-
inputRecords: records,
|
|
542
|
-
atomic: true
|
|
543
|
-
})
|
|
544
|
-
|
|
545
|
-
assert.equal(result.meta.total, 50)
|
|
546
|
-
assert.equal(result.meta.succeeded, 50)
|
|
547
|
-
assert.equal(result.data.length, 50)
|
|
548
|
-
|
|
549
|
-
// Verify via API with increased page size
|
|
550
|
-
const queryResult = await api.resources.authors.query({
|
|
551
|
-
simplified: false,
|
|
552
|
-
queryParams: { page: { size: 100 } }
|
|
553
|
-
})
|
|
554
|
-
assert.equal(queryResult.data.length, 50)
|
|
555
|
-
})
|
|
556
|
-
})
|
|
557
|
-
|
|
558
|
-
describe('Complex Scenarios', () => {
|
|
559
|
-
beforeEach(async () => {
|
|
560
|
-
await cleanTables(knex, [
|
|
561
|
-
'basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors'
|
|
562
|
-
])
|
|
563
|
-
})
|
|
564
|
-
|
|
565
|
-
it('should handle mixed operations workflow', async () => {
|
|
566
|
-
// Step 1: Bulk create authors
|
|
567
|
-
const authorRecords = Array.from({ length: 5 }, (_, i) =>
|
|
568
|
-
createJsonApiDocument('authors', { name: `Author ${i + 1}` })
|
|
569
|
-
)
|
|
570
|
-
|
|
571
|
-
const createResult = await api.scopes.authors.bulkPost({
|
|
572
|
-
inputRecords: authorRecords
|
|
573
|
-
})
|
|
574
|
-
|
|
575
|
-
const authorIds = createResult.data.map(a => a.id)
|
|
576
|
-
|
|
577
|
-
// Step 2: Bulk update some of them
|
|
578
|
-
const updateOperations = [
|
|
579
|
-
{
|
|
580
|
-
id: authorIds[0],
|
|
581
|
-
data: {
|
|
582
|
-
type: 'authors',
|
|
583
|
-
id: authorIds[0],
|
|
584
|
-
attributes: { name: 'Senior Author 1' }
|
|
585
|
-
}
|
|
586
|
-
},
|
|
587
|
-
{
|
|
588
|
-
id: authorIds[2],
|
|
589
|
-
data: {
|
|
590
|
-
type: 'authors',
|
|
591
|
-
id: authorIds[2],
|
|
592
|
-
attributes: { name: 'Senior Author 3' }
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
]
|
|
596
|
-
|
|
597
|
-
const updateResult = await api.scopes.authors.bulkPatch({
|
|
598
|
-
operations: updateOperations
|
|
599
|
-
})
|
|
600
|
-
|
|
601
|
-
assert.equal(updateResult.meta.succeeded, 2)
|
|
602
|
-
|
|
603
|
-
// Step 3: Bulk delete some others
|
|
604
|
-
const deleteResult = await api.scopes.authors.bulkDelete({
|
|
605
|
-
ids: [authorIds[3], authorIds[4]]
|
|
606
|
-
})
|
|
607
|
-
|
|
608
|
-
assert.equal(deleteResult.meta.succeeded, 2)
|
|
609
|
-
|
|
610
|
-
// Verify final state via API
|
|
611
|
-
const finalQuery = await api.resources.authors.query({ simplified: false })
|
|
612
|
-
assert.equal(finalQuery.data.length, 3)
|
|
613
|
-
|
|
614
|
-
// Check for senior authors
|
|
615
|
-
const seniorAuthors = finalQuery.data.filter(a => a.attributes.name.includes('Senior'))
|
|
616
|
-
assert.equal(seniorAuthors.length, 2)
|
|
617
|
-
})
|
|
618
|
-
|
|
619
|
-
it('should maintain data integrity with relationships', async () => {
|
|
620
|
-
// Create countries
|
|
621
|
-
const countryResult = await api.scopes.countries.bulkPost({
|
|
622
|
-
inputRecords: [
|
|
623
|
-
createJsonApiDocument('countries', { name: 'USA', code: 'US' }),
|
|
624
|
-
createJsonApiDocument('countries', { name: 'UK', code: 'GB' })
|
|
625
|
-
]
|
|
626
|
-
})
|
|
627
|
-
|
|
628
|
-
const countryIds = countryResult.data.map(c => c.id)
|
|
629
|
-
|
|
630
|
-
// Create publishers with country relationships
|
|
631
|
-
const publisherRecords = [
|
|
632
|
-
createJsonApiDocument('publishers',
|
|
633
|
-
{ name: 'US Publisher' },
|
|
634
|
-
{ country: createRelationship(resourceIdentifier('countries', countryIds[0])) }
|
|
635
|
-
),
|
|
636
|
-
createJsonApiDocument('publishers',
|
|
637
|
-
{ name: 'UK Publisher' },
|
|
638
|
-
{ country: createRelationship(resourceIdentifier('countries', countryIds[1])) }
|
|
639
|
-
)
|
|
640
|
-
]
|
|
641
|
-
|
|
642
|
-
const publisherResult = await api.scopes.publishers.bulkPost({
|
|
643
|
-
inputRecords: publisherRecords
|
|
644
|
-
})
|
|
645
|
-
|
|
646
|
-
assert.equal(publisherResult.data.length, 2)
|
|
647
|
-
|
|
648
|
-
// Verify relationships were set correctly via API
|
|
649
|
-
const allPublishers = await api.resources.publishers.query({
|
|
650
|
-
simplified: false
|
|
651
|
-
})
|
|
652
|
-
const usPublisher = allPublishers.data.find(p => p.attributes.name === 'US Publisher')
|
|
653
|
-
assert(usPublisher, 'Should find US Publisher')
|
|
654
|
-
assert.equal(usPublisher.relationships.country.data.id, countryIds[0])
|
|
655
|
-
})
|
|
656
|
-
})
|
|
657
|
-
})
|