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
package/tests/curl.test.js
DELETED
|
@@ -1,467 +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 { spawn } from 'child_process'
|
|
5
|
-
import { createBasicApi } from './fixtures/api-configs.js'
|
|
6
|
-
import {
|
|
7
|
-
validateJsonApiStructure,
|
|
8
|
-
resourceIdentifier,
|
|
9
|
-
cleanTables,
|
|
10
|
-
createJsonApiDocument,
|
|
11
|
-
createRelationship,
|
|
12
|
-
createToManyRelationship
|
|
13
|
-
} from './helpers/test-utils.js'
|
|
14
|
-
import express from 'express'
|
|
15
|
-
import http from 'http'
|
|
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 instances that persist across tests
|
|
27
|
-
let basicApi
|
|
28
|
-
let app
|
|
29
|
-
let server
|
|
30
|
-
const TEST_PORT = 3456
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Execute a CURL command and return the response
|
|
34
|
-
* @param {string[]} args - The curl command arguments
|
|
35
|
-
* @returns {Promise<{stdout: string, stderr: string, exitCode: number}>}
|
|
36
|
-
*/
|
|
37
|
-
async function executeCurl (args) {
|
|
38
|
-
return new Promise((resolve, reject) => {
|
|
39
|
-
const curl = spawn('curl', args, {
|
|
40
|
-
shell: false
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
let stdout = ''
|
|
44
|
-
let stderr = ''
|
|
45
|
-
|
|
46
|
-
curl.stdout.on('data', (data) => {
|
|
47
|
-
stdout += data.toString()
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
curl.stderr.on('data', (data) => {
|
|
51
|
-
stderr += data.toString()
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
curl.on('close', (code) => {
|
|
55
|
-
resolve({
|
|
56
|
-
stdout,
|
|
57
|
-
stderr,
|
|
58
|
-
exitCode: code
|
|
59
|
-
})
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
curl.on('error', (error) => {
|
|
63
|
-
reject(error)
|
|
64
|
-
})
|
|
65
|
-
})
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
describe('CURL HTTP Abstraction Layer Tests', () => {
|
|
69
|
-
before(async () => {
|
|
70
|
-
// Initialize API with Express plugin
|
|
71
|
-
basicApi = await createBasicApi(knex, { includeExpress: true })
|
|
72
|
-
|
|
73
|
-
// Create Express app
|
|
74
|
-
app = express()
|
|
75
|
-
|
|
76
|
-
// Mount the API routes
|
|
77
|
-
basicApi.http.express.mount(app, '')
|
|
78
|
-
|
|
79
|
-
// Start server
|
|
80
|
-
server = http.createServer(app)
|
|
81
|
-
await new Promise((resolve) => {
|
|
82
|
-
server.listen(TEST_PORT, () => {
|
|
83
|
-
console.log(`Test server started on port ${TEST_PORT}`)
|
|
84
|
-
resolve()
|
|
85
|
-
})
|
|
86
|
-
})
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
after(async () => {
|
|
90
|
-
// Close server
|
|
91
|
-
await new Promise((resolve) => {
|
|
92
|
-
server.close(() => {
|
|
93
|
-
console.log('Test server closed')
|
|
94
|
-
resolve()
|
|
95
|
-
})
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
// Close database connection
|
|
99
|
-
await knex.destroy()
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
describe('Basic CURL Operations', () => {
|
|
103
|
-
const testData = {}
|
|
104
|
-
|
|
105
|
-
beforeEach(async () => {
|
|
106
|
-
// Clean all tables
|
|
107
|
-
await cleanTables(knex, [
|
|
108
|
-
'basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors'
|
|
109
|
-
])
|
|
110
|
-
|
|
111
|
-
// Create test data using API methods
|
|
112
|
-
const countryDoc = createJsonApiDocument('countries', { name: 'CURL Test Country', code: 'CT' })
|
|
113
|
-
const countryResult = await basicApi.resources.countries.post({
|
|
114
|
-
inputRecord: countryDoc,
|
|
115
|
-
simplified: false
|
|
116
|
-
})
|
|
117
|
-
testData.country = countryResult.data
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
it('should GET a resource using CURL', async () => {
|
|
121
|
-
const result = await executeCurl(['-s', '-H', 'Accept: application/vnd.api+json', `http://localhost:${TEST_PORT}/api/countries/${testData.country.id}`])
|
|
122
|
-
|
|
123
|
-
assert.equal(result.exitCode, 0, 'CURL should exit successfully')
|
|
124
|
-
|
|
125
|
-
const response = JSON.parse(result.stdout)
|
|
126
|
-
validateJsonApiStructure(response, false)
|
|
127
|
-
|
|
128
|
-
assert.equal(response.data.type, 'countries')
|
|
129
|
-
assert.equal(response.data.id, testData.country.id)
|
|
130
|
-
assert.equal(response.data.attributes.name, 'CURL Test Country')
|
|
131
|
-
assert.equal(response.data.attributes.code, 'CT')
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
it('should GET collection using CURL', async () => {
|
|
135
|
-
const result = await executeCurl(['-s', '-H', 'Accept: application/vnd.api+json', `http://localhost:${TEST_PORT}/api/countries`])
|
|
136
|
-
|
|
137
|
-
assert.equal(result.exitCode, 0, 'CURL should exit successfully')
|
|
138
|
-
|
|
139
|
-
const response = JSON.parse(result.stdout)
|
|
140
|
-
validateJsonApiStructure(response, true)
|
|
141
|
-
|
|
142
|
-
assert(Array.isArray(response.data), 'Response data should be an array')
|
|
143
|
-
assert.equal(response.data.length, 1, 'Should have one country')
|
|
144
|
-
assert.equal(response.data[0].type, 'countries')
|
|
145
|
-
assert.equal(response.data[0].attributes.name, 'CURL Test Country')
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
it('should POST a new resource using CURL', async () => {
|
|
149
|
-
const newCountryDoc = createJsonApiDocument('countries', { name: 'New CURL Country', code: 'NC' })
|
|
150
|
-
const jsonPayload = JSON.stringify(newCountryDoc)
|
|
151
|
-
|
|
152
|
-
const result = await executeCurl(['-s', '-X', 'POST', '-H', 'Content-Type: application/vnd.api+json', '-H', 'Accept: application/vnd.api+json', '-d', jsonPayload, `http://localhost:${TEST_PORT}/api/countries`])
|
|
153
|
-
|
|
154
|
-
assert.equal(result.exitCode, 0, 'CURL should exit successfully')
|
|
155
|
-
|
|
156
|
-
const response = JSON.parse(result.stdout)
|
|
157
|
-
validateJsonApiStructure(response, false)
|
|
158
|
-
|
|
159
|
-
assert.equal(response.data.type, 'countries')
|
|
160
|
-
assert(response.data.id, 'Should have an ID')
|
|
161
|
-
assert.equal(response.data.attributes.name, 'New CURL Country')
|
|
162
|
-
assert.equal(response.data.attributes.code, 'NC')
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
it('should PATCH a resource using CURL', async () => {
|
|
166
|
-
const patchDoc = {
|
|
167
|
-
data: {
|
|
168
|
-
type: 'countries',
|
|
169
|
-
id: String(testData.country.id),
|
|
170
|
-
attributes: {
|
|
171
|
-
name: 'Updated CURL Country'
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
const jsonPayload = JSON.stringify(patchDoc)
|
|
176
|
-
|
|
177
|
-
const result = await executeCurl(['-s', '-X', 'PATCH', '-H', 'Content-Type: application/vnd.api+json', '-H', 'Accept: application/vnd.api+json', '-d', jsonPayload, `http://localhost:${TEST_PORT}/api/countries/${testData.country.id}`])
|
|
178
|
-
|
|
179
|
-
assert.equal(result.exitCode, 0, 'CURL should exit successfully')
|
|
180
|
-
|
|
181
|
-
const response = JSON.parse(result.stdout)
|
|
182
|
-
|
|
183
|
-
// PATCH is configured to return minimal JSON:API response
|
|
184
|
-
assert(response.data, 'Should have data')
|
|
185
|
-
assert.equal(response.data.type, 'countries', 'Should have correct type')
|
|
186
|
-
assert.equal(response.data.id, String(testData.country.id), 'Should return correct ID')
|
|
187
|
-
|
|
188
|
-
// Verify the update by fetching the record
|
|
189
|
-
const verifyResult = await basicApi.resources.countries.get({
|
|
190
|
-
id: testData.country.id,
|
|
191
|
-
simplified: false
|
|
192
|
-
})
|
|
193
|
-
assert.equal(verifyResult.data.attributes.name, 'Updated CURL Country')
|
|
194
|
-
assert.equal(verifyResult.data.attributes.code, 'CT', 'Code should remain unchanged')
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
it('should DELETE a resource using CURL', async () => {
|
|
198
|
-
const result = await executeCurl(['-s', '-X', 'DELETE', '-w', '%{http_code}', '-o', '/dev/null', `http://localhost:${TEST_PORT}/api/countries/${testData.country.id}`])
|
|
199
|
-
|
|
200
|
-
assert.equal(result.exitCode, 0, 'CURL should exit successfully')
|
|
201
|
-
assert.equal(result.stdout.trim(), '204', 'Should return 204 No Content')
|
|
202
|
-
|
|
203
|
-
// Verify deletion
|
|
204
|
-
try {
|
|
205
|
-
await basicApi.resources.countries.get({
|
|
206
|
-
id: testData.country.id,
|
|
207
|
-
simplified: false
|
|
208
|
-
})
|
|
209
|
-
assert.fail('Should have thrown not found error')
|
|
210
|
-
} catch (error) {
|
|
211
|
-
assert.equal(error.code, 'REST_API_RESOURCE')
|
|
212
|
-
assert.equal(error.subtype, 'not_found')
|
|
213
|
-
}
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
it('should handle query parameters using CURL', async () => {
|
|
217
|
-
// Create additional countries
|
|
218
|
-
await basicApi.resources.countries.post({
|
|
219
|
-
inputRecord: createJsonApiDocument('countries', { name: 'Another Country', code: 'AC' }),
|
|
220
|
-
simplified: false
|
|
221
|
-
})
|
|
222
|
-
await basicApi.resources.countries.post({
|
|
223
|
-
inputRecord: createJsonApiDocument('countries', { name: 'Third Country', code: 'TC2' }),
|
|
224
|
-
simplified: false
|
|
225
|
-
})
|
|
226
|
-
|
|
227
|
-
// Test with filter (URL encode the space and square brackets)
|
|
228
|
-
const result = await executeCurl(['-s', '-H', 'Accept: application/vnd.api+json', `http://localhost:${TEST_PORT}/api/countries?filter%5Bname%5D=Another%20Country`])
|
|
229
|
-
|
|
230
|
-
assert.equal(result.exitCode, 0, 'CURL should exit successfully')
|
|
231
|
-
|
|
232
|
-
const response = JSON.parse(result.stdout)
|
|
233
|
-
validateJsonApiStructure(response, true)
|
|
234
|
-
|
|
235
|
-
assert.equal(response.data.length, 1, 'Should filter to one country')
|
|
236
|
-
assert.equal(response.data[0].attributes.name, 'Another Country')
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
it('should handle includes using CURL', async () => {
|
|
240
|
-
// Create publisher with country relationship
|
|
241
|
-
const publisherDoc = createJsonApiDocument('publishers',
|
|
242
|
-
{ name: 'CURL Test Publisher' },
|
|
243
|
-
{ country: createRelationship(resourceIdentifier('countries', testData.country.id)) }
|
|
244
|
-
)
|
|
245
|
-
const publisherResult = await basicApi.resources.publishers.post({
|
|
246
|
-
inputRecord: publisherDoc,
|
|
247
|
-
simplified: false
|
|
248
|
-
})
|
|
249
|
-
|
|
250
|
-
const result = await executeCurl(['-s', '-H', 'Accept: application/vnd.api+json', `http://localhost:${TEST_PORT}/api/publishers/${publisherResult.data.id}?include=country`])
|
|
251
|
-
|
|
252
|
-
assert.equal(result.exitCode, 0, 'CURL should exit successfully')
|
|
253
|
-
|
|
254
|
-
const response = JSON.parse(result.stdout)
|
|
255
|
-
validateJsonApiStructure(response, false)
|
|
256
|
-
|
|
257
|
-
assert.equal(response.data.type, 'publishers')
|
|
258
|
-
assert(response.included, 'Should have included data')
|
|
259
|
-
assert.equal(response.included.length, 1, 'Should include one resource')
|
|
260
|
-
assert.equal(response.included[0].type, 'countries')
|
|
261
|
-
assert.equal(response.included[0].id, testData.country.id)
|
|
262
|
-
})
|
|
263
|
-
|
|
264
|
-
it('should handle errors using CURL', async () => {
|
|
265
|
-
// Test 404 error
|
|
266
|
-
const result404 = await executeCurl(['-s', '-w', '\\n%{http_code}', '-H', 'Accept: application/vnd.api+json', `http://localhost:${TEST_PORT}/api/countries/999999`])
|
|
267
|
-
|
|
268
|
-
assert.equal(result404.exitCode, 0, 'CURL should exit successfully')
|
|
269
|
-
|
|
270
|
-
const lines = result404.stdout.trim().split('\n')
|
|
271
|
-
const httpCode = lines[lines.length - 1]
|
|
272
|
-
const responseBody = lines.slice(0, -1).join('\n')
|
|
273
|
-
|
|
274
|
-
assert.equal(httpCode, '404', 'Should return 404 status')
|
|
275
|
-
|
|
276
|
-
const response = JSON.parse(responseBody)
|
|
277
|
-
assert(response.errors, 'Should have errors array')
|
|
278
|
-
assert.equal(response.errors[0].status, '404')
|
|
279
|
-
assert.equal(response.errors[0].title, 'Not Found')
|
|
280
|
-
|
|
281
|
-
// Test validation error - omit required name field
|
|
282
|
-
const invalidDoc = createJsonApiDocument('countries', { code: 'XX' }) // Missing required name
|
|
283
|
-
const jsonPayload = JSON.stringify(invalidDoc)
|
|
284
|
-
|
|
285
|
-
const resultValidation = await executeCurl(['-s', '-w', '\n%{http_code}', '-X', 'POST', '-H', 'Content-Type: application/vnd.api+json', '-H', 'Accept: application/vnd.api+json', '-d', jsonPayload, `http://localhost:${TEST_PORT}/api/countries`])
|
|
286
|
-
|
|
287
|
-
const validationLines = resultValidation.stdout.trim().split('\n')
|
|
288
|
-
const validationCode = validationLines[validationLines.length - 1]
|
|
289
|
-
const validationBody = validationLines.slice(0, -1).join('\n')
|
|
290
|
-
|
|
291
|
-
assert.equal(validationCode, '422', 'Should return 422 for validation error')
|
|
292
|
-
|
|
293
|
-
const validationResponse = JSON.parse(validationBody)
|
|
294
|
-
assert(validationResponse.errors, 'Should have errors array')
|
|
295
|
-
assert.equal(validationResponse.errors[0].status, '422')
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
it('should handle different content types using CURL', async () => {
|
|
299
|
-
// Test with application/json content type
|
|
300
|
-
const newCountryDoc = createJsonApiDocument('countries', { name: 'JSON Country', code: 'JC' })
|
|
301
|
-
const jsonPayload = JSON.stringify(newCountryDoc)
|
|
302
|
-
|
|
303
|
-
const resultJson = await executeCurl(['-s', '-X', 'POST', '-H', 'Content-Type: application/json', '-H', 'Accept: application/vnd.api+json', '-d', jsonPayload, `http://localhost:${TEST_PORT}/api/countries`])
|
|
304
|
-
|
|
305
|
-
assert.equal(resultJson.exitCode, 0, 'CURL should exit successfully with application/json')
|
|
306
|
-
|
|
307
|
-
const responseJson = JSON.parse(resultJson.stdout)
|
|
308
|
-
validateJsonApiStructure(responseJson, false)
|
|
309
|
-
assert.equal(responseJson.data.attributes.name, 'JSON Country')
|
|
310
|
-
|
|
311
|
-
// Test with unsupported content type (should fail if strictContentType is enabled)
|
|
312
|
-
const resultXml = await executeCurl(['-s', '-w', '\n%{http_code}', '-X', 'POST', '-H', 'Content-Type: application/xml', '-H', 'Accept: application/vnd.api+json', '-d', jsonPayload, `http://localhost:${TEST_PORT}/api/countries`])
|
|
313
|
-
|
|
314
|
-
const xmlLines = resultXml.stdout.trim().split('\n')
|
|
315
|
-
const xmlCode = xmlLines[xmlLines.length - 1]
|
|
316
|
-
|
|
317
|
-
assert.equal(xmlCode, '415', 'Should return 415 Unsupported Media Type')
|
|
318
|
-
})
|
|
319
|
-
|
|
320
|
-
it('should handle headers correctly using CURL', async () => {
|
|
321
|
-
// Test with custom headers (-v outputs to stderr)
|
|
322
|
-
const result = await executeCurl(['-s', '-H', 'Accept: application/vnd.api+json', '-H', 'X-Custom-Header: test-value', '-H', 'User-Agent: CURL-Test/1.0', '-v', `http://localhost:${TEST_PORT}/api/countries/${testData.country.id}`])
|
|
323
|
-
|
|
324
|
-
assert.equal(result.exitCode, 0, 'CURL should exit successfully')
|
|
325
|
-
|
|
326
|
-
// Check that headers were sent (visible in verbose output on stderr)
|
|
327
|
-
assert(result.stderr.includes('X-Custom-Header: test-value'), 'Should send custom header')
|
|
328
|
-
assert(result.stderr.includes('User-Agent: CURL-Test/1.0'), 'Should send custom user agent')
|
|
329
|
-
})
|
|
330
|
-
})
|
|
331
|
-
|
|
332
|
-
describe('Complex CURL Operations', () => {
|
|
333
|
-
const testData = {}
|
|
334
|
-
|
|
335
|
-
beforeEach(async () => {
|
|
336
|
-
await cleanTables(knex, [
|
|
337
|
-
'basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors'
|
|
338
|
-
])
|
|
339
|
-
|
|
340
|
-
// Create complex test data
|
|
341
|
-
const countryDoc = createJsonApiDocument('countries', { name: 'Complex Country', code: 'CC' })
|
|
342
|
-
const countryResult = await basicApi.resources.countries.post({
|
|
343
|
-
inputRecord: countryDoc,
|
|
344
|
-
simplified: false
|
|
345
|
-
})
|
|
346
|
-
testData.country = countryResult.data
|
|
347
|
-
|
|
348
|
-
const publisherDoc = createJsonApiDocument('publishers',
|
|
349
|
-
{ name: 'Complex Publisher' },
|
|
350
|
-
{ country: createRelationship(resourceIdentifier('countries', testData.country.id)) }
|
|
351
|
-
)
|
|
352
|
-
const publisherResult = await basicApi.resources.publishers.post({
|
|
353
|
-
inputRecord: publisherDoc,
|
|
354
|
-
simplified: false
|
|
355
|
-
})
|
|
356
|
-
testData.publisher = publisherResult.data
|
|
357
|
-
|
|
358
|
-
const author1Doc = createJsonApiDocument('authors', { name: 'Complex Author One' })
|
|
359
|
-
const author2Doc = createJsonApiDocument('authors', { name: 'Complex Author Two' })
|
|
360
|
-
const author1Result = await basicApi.resources.authors.post({
|
|
361
|
-
inputRecord: author1Doc,
|
|
362
|
-
simplified: false
|
|
363
|
-
})
|
|
364
|
-
const author2Result = await basicApi.resources.authors.post({
|
|
365
|
-
inputRecord: author2Doc,
|
|
366
|
-
simplified: false
|
|
367
|
-
})
|
|
368
|
-
testData.authors = [author1Result.data, author2Result.data]
|
|
369
|
-
|
|
370
|
-
const bookDoc = createJsonApiDocument('books',
|
|
371
|
-
{ title: 'Complex Book' },
|
|
372
|
-
{
|
|
373
|
-
country: createRelationship(resourceIdentifier('countries', testData.country.id)),
|
|
374
|
-
publisher: createRelationship(resourceIdentifier('publishers', testData.publisher.id)),
|
|
375
|
-
authors: createToManyRelationship([
|
|
376
|
-
resourceIdentifier('authors', testData.authors[0].id),
|
|
377
|
-
resourceIdentifier('authors', testData.authors[1].id)
|
|
378
|
-
])
|
|
379
|
-
}
|
|
380
|
-
)
|
|
381
|
-
const bookResult = await basicApi.resources.books.post({
|
|
382
|
-
inputRecord: bookDoc,
|
|
383
|
-
simplified: false
|
|
384
|
-
})
|
|
385
|
-
testData.book = bookResult.data
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
it('should handle nested includes using CURL', async () => {
|
|
389
|
-
const result = await executeCurl(['-s', '-H', 'Accept: application/vnd.api+json', `http://localhost:${TEST_PORT}/api/books/${testData.book.id}?include=publisher.country,authors`])
|
|
390
|
-
|
|
391
|
-
assert.equal(result.exitCode, 0, 'CURL should exit successfully')
|
|
392
|
-
|
|
393
|
-
const response = JSON.parse(result.stdout)
|
|
394
|
-
validateJsonApiStructure(response, false)
|
|
395
|
-
|
|
396
|
-
assert.equal(response.data.type, 'books')
|
|
397
|
-
assert(response.included, 'Should have included data')
|
|
398
|
-
|
|
399
|
-
// Should include publisher, country, and 2 authors
|
|
400
|
-
assert.equal(response.included.length, 4, 'Should include 4 resources')
|
|
401
|
-
|
|
402
|
-
const includedTypes = response.included.map(r => r.type)
|
|
403
|
-
assert(includedTypes.includes('publishers'), 'Should include publisher')
|
|
404
|
-
assert(includedTypes.includes('countries'), 'Should include country')
|
|
405
|
-
assert.equal(includedTypes.filter(t => t === 'authors').length, 2, 'Should include 2 authors')
|
|
406
|
-
})
|
|
407
|
-
|
|
408
|
-
it('should handle pagination using CURL', async () => {
|
|
409
|
-
// Create multiple books
|
|
410
|
-
for (let i = 0; i < 5; i++) {
|
|
411
|
-
await basicApi.resources.books.post({
|
|
412
|
-
inputRecord: createJsonApiDocument('books',
|
|
413
|
-
{ title: `Book ${i}` },
|
|
414
|
-
{
|
|
415
|
-
country: createRelationship(resourceIdentifier('countries', testData.country.id)),
|
|
416
|
-
publisher: createRelationship(resourceIdentifier('publishers', testData.publisher.id))
|
|
417
|
-
}
|
|
418
|
-
),
|
|
419
|
-
simplified: false
|
|
420
|
-
})
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// Test pagination (URL encode square brackets)
|
|
424
|
-
const result = await executeCurl(['-s', '-H', 'Accept: application/vnd.api+json', `http://localhost:${TEST_PORT}/api/books?page%5Bsize%5D=3&page%5Bnumber%5D=1`])
|
|
425
|
-
|
|
426
|
-
assert.equal(result.exitCode, 0, 'CURL should exit successfully')
|
|
427
|
-
|
|
428
|
-
const response = JSON.parse(result.stdout)
|
|
429
|
-
validateJsonApiStructure(response, true)
|
|
430
|
-
|
|
431
|
-
assert.equal(response.data.length, 3, 'Should return 3 books per page')
|
|
432
|
-
// Note: Links are now always included, using relative URLs whenreturnBasePath isn't configured
|
|
433
|
-
})
|
|
434
|
-
|
|
435
|
-
it('should handle sorting using CURL', async () => {
|
|
436
|
-
const result = await executeCurl(['-s', '-H', 'Accept: application/vnd.api+json', `http://localhost:${TEST_PORT}/api/books?sort=-title`])
|
|
437
|
-
|
|
438
|
-
assert.equal(result.exitCode, 0, 'CURL should exit successfully')
|
|
439
|
-
|
|
440
|
-
const response = JSON.parse(result.stdout)
|
|
441
|
-
validateJsonApiStructure(response, true)
|
|
442
|
-
|
|
443
|
-
// Books should be sorted by title descending
|
|
444
|
-
const titles = response.data.map(book => book.attributes.title)
|
|
445
|
-
const sortedTitles = [...titles].sort().reverse()
|
|
446
|
-
assert.deepEqual(titles, sortedTitles, 'Books should be sorted by title descending')
|
|
447
|
-
})
|
|
448
|
-
|
|
449
|
-
it('should handle sparse fieldsets using CURL', async () => {
|
|
450
|
-
const result = await executeCurl(['-s', '-H', 'Accept: application/vnd.api+json', `http://localhost:${TEST_PORT}/api/books/${testData.book.id}?fields%5Bbooks%5D=title&fields%5Bpublishers%5D=name&include=publisher`])
|
|
451
|
-
|
|
452
|
-
assert.equal(result.exitCode, 0, 'CURL should exit successfully')
|
|
453
|
-
|
|
454
|
-
const response = JSON.parse(result.stdout)
|
|
455
|
-
validateJsonApiStructure(response, false)
|
|
456
|
-
|
|
457
|
-
// Main resource should only have title attribute
|
|
458
|
-
assert.equal(Object.keys(response.data.attributes).length, 1, 'Should only have title attribute')
|
|
459
|
-
assert(response.data.attributes.title, 'Should have title')
|
|
460
|
-
|
|
461
|
-
// Included publisher should only have name attribute
|
|
462
|
-
const publisher = response.included.find(r => r.type === 'publishers')
|
|
463
|
-
assert.equal(Object.keys(publisher.attributes).length, 1, 'Publisher should only have name attribute')
|
|
464
|
-
assert(publisher.attributes.name, 'Publisher should have name')
|
|
465
|
-
})
|
|
466
|
-
})
|
|
467
|
-
})
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { describe, it, before, after, beforeEach } 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
|
-
import {
|
|
8
|
-
cleanTables,
|
|
9
|
-
validateJsonApiStructure
|
|
10
|
-
} from './helpers/test-utils.js'
|
|
11
|
-
|
|
12
|
-
const knex = knexLib({
|
|
13
|
-
client: 'better-sqlite3',
|
|
14
|
-
connection: {
|
|
15
|
-
filename: ':memory:'
|
|
16
|
-
},
|
|
17
|
-
useNullAsDefault: true
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
let api
|
|
21
|
-
|
|
22
|
-
function createMysqlStyleInsertDb (baseKnex) {
|
|
23
|
-
return (tableName) => {
|
|
24
|
-
const query = baseKnex(tableName)
|
|
25
|
-
const originalInsert = query.insert.bind(query)
|
|
26
|
-
|
|
27
|
-
query.insert = (...args) => {
|
|
28
|
-
const insertBuilder = originalInsert(...args)
|
|
29
|
-
const originalReturning = insertBuilder.returning.bind(insertBuilder)
|
|
30
|
-
|
|
31
|
-
insertBuilder.returning = async (...returnArgs) => {
|
|
32
|
-
await originalReturning(...returnArgs)
|
|
33
|
-
return [0]
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return insertBuilder
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return query
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
describe('Custom id POST fallback when insert returning is unusable', () => {
|
|
44
|
-
before(async () => {
|
|
45
|
-
api = new Api({
|
|
46
|
-
name: 'custom-id-mysql-returning-fallback-test'
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
await api.use(RestApiPlugin, {
|
|
50
|
-
simplifiedApi: true,
|
|
51
|
-
simplifiedTransport: false,
|
|
52
|
-
returnRecordApi: {
|
|
53
|
-
post: 'full',
|
|
54
|
-
put: 'full',
|
|
55
|
-
patch: 'full'
|
|
56
|
-
}
|
|
57
|
-
})
|
|
58
|
-
await api.use(RestApiKnexPlugin, { knex })
|
|
59
|
-
|
|
60
|
-
await api.addResource('user_settings', {
|
|
61
|
-
tableName: 'mysqlish_user_settings',
|
|
62
|
-
idProperty: 'user_id',
|
|
63
|
-
searchSchema: {
|
|
64
|
-
id: { type: 'id', actualField: 'id' }
|
|
65
|
-
},
|
|
66
|
-
schema: {
|
|
67
|
-
theme: { type: 'string', required: true, max: 32 }
|
|
68
|
-
}
|
|
69
|
-
})
|
|
70
|
-
await api.resources.user_settings.createKnexTable()
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
after(async () => {
|
|
74
|
-
await knex.destroy()
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
beforeEach(async () => {
|
|
78
|
-
await cleanTables(knex, ['mysqlish_user_settings'])
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('returns the explicit id for simplified POST when the insert result is 0', async () => {
|
|
82
|
-
const mysqlStyleDb = createMysqlStyleInsertDb(knex)
|
|
83
|
-
|
|
84
|
-
const created = await api.resources.user_settings.post({
|
|
85
|
-
inputRecord: {
|
|
86
|
-
id: '6',
|
|
87
|
-
theme: 'dark'
|
|
88
|
-
},
|
|
89
|
-
simplified: true,
|
|
90
|
-
transaction: mysqlStyleDb
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
assert.equal(created.id, '6')
|
|
94
|
-
assert.equal(created.theme, 'dark')
|
|
95
|
-
|
|
96
|
-
const dbRow = await knex('mysqlish_user_settings').where('user_id', 6).first()
|
|
97
|
-
assert.ok(dbRow)
|
|
98
|
-
assert.equal(String(dbRow.user_id), '6')
|
|
99
|
-
assert.equal(dbRow.theme, 'dark')
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it('returns the explicit id for non-simplified POST when the insert result is 0', async () => {
|
|
103
|
-
const mysqlStyleDb = createMysqlStyleInsertDb(knex)
|
|
104
|
-
|
|
105
|
-
const created = await api.resources.user_settings.post({
|
|
106
|
-
inputRecord: {
|
|
107
|
-
data: {
|
|
108
|
-
type: 'user_settings',
|
|
109
|
-
id: '8',
|
|
110
|
-
attributes: {
|
|
111
|
-
theme: 'light'
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
},
|
|
115
|
-
simplified: false,
|
|
116
|
-
transaction: mysqlStyleDb
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
validateJsonApiStructure(created)
|
|
120
|
-
assert.equal(created.data.id, '8')
|
|
121
|
-
assert.equal(created.data.attributes.theme, 'light')
|
|
122
|
-
|
|
123
|
-
const dbRow = await knex('mysqlish_user_settings').where('user_id', 8).first()
|
|
124
|
-
assert.ok(dbRow)
|
|
125
|
-
assert.equal(String(dbRow.user_id), '8')
|
|
126
|
-
assert.equal(dbRow.theme, 'light')
|
|
127
|
-
})
|
|
128
|
-
})
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { describe, it, before, after, beforeEach } 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
|
-
import { cleanTables } from './helpers/test-utils.js'
|
|
8
|
-
|
|
9
|
-
const knex = knexLib({
|
|
10
|
-
client: 'better-sqlite3',
|
|
11
|
-
connection: {
|
|
12
|
-
filename: ':memory:'
|
|
13
|
-
},
|
|
14
|
-
useNullAsDefault: true
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
let api
|
|
18
|
-
|
|
19
|
-
describe('Simplified POST with explicit resource ids', () => {
|
|
20
|
-
before(async () => {
|
|
21
|
-
api = new Api({
|
|
22
|
-
name: 'custom-id-simplified-post-test'
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
await api.use(RestApiPlugin, {
|
|
26
|
-
simplifiedApi: true,
|
|
27
|
-
simplifiedTransport: false,
|
|
28
|
-
returnRecordApi: {
|
|
29
|
-
post: 'full',
|
|
30
|
-
put: 'full',
|
|
31
|
-
patch: 'full'
|
|
32
|
-
}
|
|
33
|
-
})
|
|
34
|
-
await api.use(RestApiKnexPlugin, { knex })
|
|
35
|
-
|
|
36
|
-
await api.addResource('user_settings', {
|
|
37
|
-
tableName: 'test_user_settings',
|
|
38
|
-
idProperty: 'user_id',
|
|
39
|
-
searchSchema: {
|
|
40
|
-
id: { type: 'id', actualField: 'id' }
|
|
41
|
-
},
|
|
42
|
-
schema: {
|
|
43
|
-
theme: { type: 'string', required: true, max: 32 }
|
|
44
|
-
}
|
|
45
|
-
})
|
|
46
|
-
await api.resources.user_settings.createKnexTable()
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
after(async () => {
|
|
50
|
-
await knex.destroy()
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
beforeEach(async () => {
|
|
54
|
-
await cleanTables(knex, ['test_user_settings'])
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('persists the provided resource id when posting simplified records to a custom-id resource', async () => {
|
|
58
|
-
const created = await api.resources.user_settings.post({
|
|
59
|
-
id: '7',
|
|
60
|
-
theme: 'dark'
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
assert.equal(created.id, '7')
|
|
64
|
-
assert.equal(created.theme, 'dark')
|
|
65
|
-
|
|
66
|
-
const dbRow = await knex('test_user_settings').where('user_id', 7).first()
|
|
67
|
-
assert.ok(dbRow)
|
|
68
|
-
assert.equal(String(dbRow.user_id), '7')
|
|
69
|
-
assert.equal(dbRow.theme, 'dark')
|
|
70
|
-
|
|
71
|
-
const fetched = await api.resources.user_settings.get({ id: '7' })
|
|
72
|
-
assert.equal(fetched.id, '7')
|
|
73
|
-
assert.equal(fetched.theme, 'dark')
|
|
74
|
-
})
|
|
75
|
-
})
|