json-rest-api 1.0.14 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/connectors_ideas/fastify.js +66 -68
- package/connectors_ideas/koa-plugin.js +50 -51
- package/eslint.config.js +11 -2
- package/examples/url-override-example.js +63 -63
- package/index.js +13 -13
- package/lib/enhanced-logger.js +51 -51
- package/lib/error-formatter.js +61 -61
- package/lib/rest-api-errors.js +28 -28
- package/package.json +1 -1
- package/plugins/core/bulk-operations-plugin.js +142 -145
- package/plugins/core/connectors/express-plugin.js +210 -217
- package/plugins/core/connectors/lib/busboy-detector.js +84 -84
- package/plugins/core/connectors/lib/formidable-detector.js +50 -50
- package/plugins/core/connectors/lib/request-helpers.js +74 -74
- package/plugins/core/file-handling-plugin.js +125 -125
- package/plugins/core/lib/anyapi/anyapi-registry.js +254 -254
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +287 -287
- package/plugins/core/lib/anyapi/schema-utils.js +101 -101
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +41 -41
- package/plugins/core/lib/dbTablesOperations.js +134 -135
- package/plugins/core/lib/fractional-positioning.js +74 -76
- package/plugins/core/lib/querying/knex-cross-table-search.js +320 -320
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +144 -144
- package/plugins/core/lib/querying/knex-pagination-helpers.js +164 -164
- package/plugins/core/lib/querying/knex-process-includes.js +50 -50
- package/plugins/core/lib/querying/knex-query-helpers-base.js +31 -31
- package/plugins/core/lib/querying/knex-query-helpers.js +451 -452
- package/plugins/core/lib/querying/knex-relationship-includes.js +1160 -1170
- package/plugins/core/lib/querying/knex-window-queries.js +89 -89
- package/plugins/core/lib/querying/url-helpers.js +35 -35
- package/plugins/core/lib/querying-writing/compile-schemas.js +97 -98
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +31 -31
- package/plugins/core/lib/querying-writing/database-capabilities.js +94 -94
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +76 -76
- package/plugins/core/lib/querying-writing/default-data-helpers.js +27 -27
- package/plugins/core/lib/querying-writing/field-utils.js +44 -42
- package/plugins/core/lib/querying-writing/knex-constants.js +15 -16
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +117 -115
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +41 -41
- package/plugins/core/lib/querying-writing/payload-validators.js +275 -275
- package/plugins/core/lib/querying-writing/schema-helpers.js +129 -129
- package/plugins/core/lib/querying-writing/scope-validations.js +143 -146
- package/plugins/core/lib/querying-writing/simplified-helpers.js +133 -129
- package/plugins/core/lib/storage/storage-adapter.js +68 -68
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +31 -31
- package/plugins/core/lib/writing/many-to-many-manipulations.js +81 -81
- package/plugins/core/lib/writing/relationship-processor.js +58 -58
- package/plugins/core/rest-api-access.js +8 -8
- package/plugins/core/rest-api-anyapi-knex-plugin.js +920 -920
- package/plugins/core/rest-api-cors-plugin.js +170 -171
- package/plugins/core/rest-api-knex-plugin.js +482 -493
- package/plugins/core/rest-api-multihome-plugin.js +247 -251
- package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +5 -5
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +20 -21
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +44 -44
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +23 -23
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +15 -15
- package/plugins/core/rest-api-plugin-methods/add-route.js +9 -9
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +5 -6
- package/plugins/core/rest-api-plugin-methods/common.js +295 -297
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +35 -37
- package/plugins/core/rest-api-plugin-methods/delete.js +57 -59
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +172 -171
- package/plugins/core/rest-api-plugin-methods/get-related.js +96 -96
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +16 -18
- package/plugins/core/rest-api-plugin-methods/get.js +86 -86
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +16 -17
- package/plugins/core/rest-api-plugin-methods/patch.js +63 -64
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +54 -55
- package/plugins/core/rest-api-plugin-methods/post.js +61 -62
- package/plugins/core/rest-api-plugin-methods/put.js +86 -87
- package/plugins/core/rest-api-plugin-methods/query.js +88 -88
- package/plugins/core/rest-api-plugin-methods/release.js +5 -6
- package/plugins/core/rest-api-plugin.js +76 -78
- package/plugins/core/rest-api-positioning-plugin.js +251 -252
- package/plugins/core/socketio-plugin.js +240 -236
- package/plugins/storage/local-storage.js +103 -103
- package/plugins/storage/s3-storage.js +41 -42
- package/quickTest.js +37 -37
- package/scripts/debug-anyapi-review-filter.mjs +18 -18
- package/scripts/inspect-anyapi-descriptor-country.mjs +10 -10
- package/scripts/inspect-anyapi-descriptor.mjs +10 -10
- package/scripts/run-docs.js +112 -113
- package/test-debug.js +28 -28
- package/tests/TEST_TEMPLATE.test.js +51 -51
- package/tests/anyapi-basic.test.js +42 -42
- package/tests/anyapi-cursor-pagination.test.js +48 -48
- package/tests/bulk-operations.test.js +208 -208
- package/tests/computed-fields.test.js +204 -204
- package/tests/cors.test.js +412 -413
- package/tests/curl.test.js +275 -276
- package/tests/custom-idproperty-relationships.test.js +266 -266
- package/tests/field-getters.test.js +132 -132
- package/tests/field-setters.test.js +110 -110
- package/tests/fixtures/api-configs.js +699 -699
- package/tests/helpers/storage-mode.js +32 -32
- package/tests/helpers/test-utils.js +76 -76
- package/tests/include-depth-validation.test.js +76 -76
- package/tests/includes.test.js +145 -145
- package/tests/multihome.test.js +175 -175
- package/tests/nested-includes.test.js +255 -255
- package/tests/pagination-cursor-multifield.test.js +164 -164
- package/tests/pagination-enhanced.test.js +225 -225
- package/tests/polymorphic-relationships.test.js +96 -96
- package/tests/polymorphic-simple.test.js +44 -44
- package/tests/positioning-advanced.test.js +226 -226
- package/tests/positioning-extended.test.js +399 -399
- package/tests/positioning-multifilter.test.js +204 -204
- package/tests/positioning.test.js +266 -266
- package/tests/queries.test.js +184 -184
- package/tests/query-limits.test.js +154 -155
- package/tests/relationship-endpoints.test.js +127 -127
- package/tests/relationships.test.js +178 -179
- package/tests/rest-api-access.test.js +86 -86
- package/tests/rest-api.test.js +103 -103
- package/tests/searchschema-merge.test.js +93 -93
- package/tests/socketio.test.js +326 -326
- package/tests/virtual-fields.test.js +164 -164
package/tests/curl.test.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
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'
|
|
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
6
|
import {
|
|
7
7
|
validateJsonApiStructure,
|
|
8
8
|
resourceIdentifier,
|
|
@@ -10,9 +10,9 @@ import {
|
|
|
10
10
|
createJsonApiDocument,
|
|
11
11
|
createRelationship,
|
|
12
12
|
createToManyRelationship
|
|
13
|
-
} from './helpers/test-utils.js'
|
|
14
|
-
import express from 'express'
|
|
15
|
-
import http from 'http'
|
|
13
|
+
} from './helpers/test-utils.js'
|
|
14
|
+
import express from 'express'
|
|
15
|
+
import http from 'http'
|
|
16
16
|
|
|
17
17
|
// Create Knex instance for tests
|
|
18
18
|
const knex = knexLib({
|
|
@@ -21,147 +21,146 @@ const knex = knexLib({
|
|
|
21
21
|
filename: ':memory:'
|
|
22
22
|
},
|
|
23
23
|
useNullAsDefault: true
|
|
24
|
-
})
|
|
24
|
+
})
|
|
25
25
|
|
|
26
26
|
// API instances that persist across tests
|
|
27
|
-
let basicApi
|
|
28
|
-
let app
|
|
29
|
-
let server
|
|
30
|
-
const TEST_PORT = 3456
|
|
27
|
+
let basicApi
|
|
28
|
+
let app
|
|
29
|
+
let server
|
|
30
|
+
const TEST_PORT = 3456
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Execute a CURL command and return the response
|
|
34
34
|
* @param {string[]} args - The curl command arguments
|
|
35
35
|
* @returns {Promise<{stdout: string, stderr: string, exitCode: number}>}
|
|
36
36
|
*/
|
|
37
|
-
async function executeCurl(args) {
|
|
37
|
+
async function executeCurl (args) {
|
|
38
38
|
return new Promise((resolve, reject) => {
|
|
39
39
|
const curl = spawn('curl', args, {
|
|
40
40
|
shell: false
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
let stdout = ''
|
|
44
|
-
let stderr = ''
|
|
45
|
-
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
let stdout = ''
|
|
44
|
+
let stderr = ''
|
|
45
|
+
|
|
46
46
|
curl.stdout.on('data', (data) => {
|
|
47
|
-
stdout += data.toString()
|
|
48
|
-
})
|
|
49
|
-
|
|
47
|
+
stdout += data.toString()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
50
|
curl.stderr.on('data', (data) => {
|
|
51
|
-
stderr += data.toString()
|
|
52
|
-
})
|
|
53
|
-
|
|
51
|
+
stderr += data.toString()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
54
|
curl.on('close', (code) => {
|
|
55
55
|
resolve({
|
|
56
56
|
stdout,
|
|
57
57
|
stderr,
|
|
58
58
|
exitCode: code
|
|
59
|
-
})
|
|
60
|
-
})
|
|
61
|
-
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
|
|
62
62
|
curl.on('error', (error) => {
|
|
63
|
-
reject(error)
|
|
64
|
-
})
|
|
65
|
-
})
|
|
63
|
+
reject(error)
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
describe('CURL HTTP Abstraction Layer Tests', () => {
|
|
69
69
|
before(async () => {
|
|
70
70
|
// Initialize API with Express plugin
|
|
71
|
-
basicApi = await createBasicApi(knex, { includeExpress: true })
|
|
72
|
-
|
|
71
|
+
basicApi = await createBasicApi(knex, { includeExpress: true })
|
|
72
|
+
|
|
73
73
|
// Create Express app
|
|
74
|
-
app = express()
|
|
75
|
-
|
|
74
|
+
app = express()
|
|
75
|
+
|
|
76
76
|
// Mount the API routes
|
|
77
|
-
basicApi.http.express.mount(app, '')
|
|
78
|
-
|
|
77
|
+
basicApi.http.express.mount(app, '')
|
|
78
|
+
|
|
79
79
|
// Start server
|
|
80
|
-
server = http.createServer(app)
|
|
80
|
+
server = http.createServer(app)
|
|
81
81
|
await new Promise((resolve) => {
|
|
82
82
|
server.listen(TEST_PORT, () => {
|
|
83
|
-
console.log(`Test server started on port ${TEST_PORT}`)
|
|
84
|
-
resolve()
|
|
85
|
-
})
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
});
|
|
83
|
+
console.log(`Test server started on port ${TEST_PORT}`)
|
|
84
|
+
resolve()
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
})
|
|
89
88
|
|
|
90
89
|
after(async () => {
|
|
91
90
|
// Close server
|
|
92
91
|
await new Promise((resolve) => {
|
|
93
92
|
server.close(() => {
|
|
94
|
-
console.log('Test server closed')
|
|
95
|
-
resolve()
|
|
96
|
-
})
|
|
97
|
-
})
|
|
98
|
-
|
|
93
|
+
console.log('Test server closed')
|
|
94
|
+
resolve()
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
99
98
|
// Close database connection
|
|
100
|
-
await knex.destroy()
|
|
101
|
-
})
|
|
99
|
+
await knex.destroy()
|
|
100
|
+
})
|
|
102
101
|
|
|
103
102
|
describe('Basic CURL Operations', () => {
|
|
104
|
-
|
|
103
|
+
const testData = {}
|
|
105
104
|
|
|
106
105
|
beforeEach(async () => {
|
|
107
106
|
// Clean all tables
|
|
108
107
|
await cleanTables(knex, [
|
|
109
108
|
'basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors'
|
|
110
|
-
])
|
|
109
|
+
])
|
|
111
110
|
|
|
112
111
|
// Create test data using API methods
|
|
113
|
-
const countryDoc = createJsonApiDocument('countries', { name: 'CURL Test Country', code: 'CT' })
|
|
112
|
+
const countryDoc = createJsonApiDocument('countries', { name: 'CURL Test Country', code: 'CT' })
|
|
114
113
|
const countryResult = await basicApi.resources.countries.post({
|
|
115
114
|
inputRecord: countryDoc,
|
|
116
115
|
simplified: false
|
|
117
|
-
})
|
|
118
|
-
testData.country = countryResult.data
|
|
119
|
-
})
|
|
116
|
+
})
|
|
117
|
+
testData.country = countryResult.data
|
|
118
|
+
})
|
|
120
119
|
|
|
121
120
|
it('should GET a resource using CURL', async () => {
|
|
122
|
-
const result = await executeCurl(['-s', '-H', 'Accept: application/vnd.api+json', `http://localhost:${TEST_PORT}/api/countries/${testData.country.id}`])
|
|
123
|
-
|
|
124
|
-
assert.equal(result.exitCode, 0, 'CURL should exit successfully')
|
|
125
|
-
|
|
126
|
-
const response = JSON.parse(result.stdout)
|
|
127
|
-
validateJsonApiStructure(response, false)
|
|
128
|
-
|
|
129
|
-
assert.equal(response.data.type, 'countries')
|
|
130
|
-
assert.equal(response.data.id, testData.country.id)
|
|
131
|
-
assert.equal(response.data.attributes.name, 'CURL Test Country')
|
|
132
|
-
assert.equal(response.data.attributes.code, 'CT')
|
|
133
|
-
})
|
|
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
|
+
})
|
|
134
133
|
|
|
135
134
|
it('should GET collection using CURL', async () => {
|
|
136
|
-
const result = await executeCurl(['-s', '-H', 'Accept: application/vnd.api+json', `http://localhost:${TEST_PORT}/api/countries`])
|
|
137
|
-
|
|
138
|
-
assert.equal(result.exitCode, 0, 'CURL should exit successfully')
|
|
139
|
-
|
|
140
|
-
const response = JSON.parse(result.stdout)
|
|
141
|
-
validateJsonApiStructure(response, true)
|
|
142
|
-
|
|
143
|
-
assert(Array.isArray(response.data), 'Response data should be an array')
|
|
144
|
-
assert.equal(response.data.length, 1, 'Should have one country')
|
|
145
|
-
assert.equal(response.data[0].type, 'countries')
|
|
146
|
-
assert.equal(response.data[0].attributes.name, 'CURL Test Country')
|
|
147
|
-
})
|
|
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
|
+
})
|
|
148
147
|
|
|
149
148
|
it('should POST a new resource using CURL', async () => {
|
|
150
|
-
const newCountryDoc = createJsonApiDocument('countries', { name: 'New CURL Country', code: 'NC' })
|
|
151
|
-
const jsonPayload = JSON.stringify(newCountryDoc)
|
|
152
|
-
|
|
153
|
-
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`])
|
|
154
|
-
|
|
155
|
-
assert.equal(result.exitCode, 0, 'CURL should exit successfully')
|
|
156
|
-
|
|
157
|
-
const response = JSON.parse(result.stdout)
|
|
158
|
-
validateJsonApiStructure(response, false)
|
|
159
|
-
|
|
160
|
-
assert.equal(response.data.type, 'countries')
|
|
161
|
-
assert(response.data.id, 'Should have an ID')
|
|
162
|
-
assert.equal(response.data.attributes.name, 'New CURL Country')
|
|
163
|
-
assert.equal(response.data.attributes.code, 'NC')
|
|
164
|
-
})
|
|
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
|
+
})
|
|
165
164
|
|
|
166
165
|
it('should PATCH a resource using CURL', async () => {
|
|
167
166
|
const patchDoc = {
|
|
@@ -172,201 +171,201 @@ describe('CURL HTTP Abstraction Layer Tests', () => {
|
|
|
172
171
|
name: 'Updated CURL Country'
|
|
173
172
|
}
|
|
174
173
|
}
|
|
175
|
-
}
|
|
176
|
-
const jsonPayload = JSON.stringify(patchDoc)
|
|
177
|
-
|
|
178
|
-
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}`])
|
|
179
|
-
|
|
180
|
-
assert.equal(result.exitCode, 0, 'CURL should exit successfully')
|
|
181
|
-
|
|
182
|
-
const response = JSON.parse(result.stdout)
|
|
183
|
-
|
|
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
|
+
|
|
184
183
|
// PATCH is configured to return minimal JSON:API response
|
|
185
|
-
assert(response.data, 'Should have data')
|
|
186
|
-
assert.equal(response.data.type, 'countries', 'Should have correct type')
|
|
187
|
-
assert.equal(response.data.id, String(testData.country.id), 'Should return correct ID')
|
|
188
|
-
|
|
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
|
+
|
|
189
188
|
// Verify the update by fetching the record
|
|
190
189
|
const verifyResult = await basicApi.resources.countries.get({
|
|
191
190
|
id: testData.country.id,
|
|
192
191
|
simplified: false
|
|
193
|
-
})
|
|
194
|
-
assert.equal(verifyResult.data.attributes.name, 'Updated CURL Country')
|
|
195
|
-
assert.equal(verifyResult.data.attributes.code, 'CT', 'Code should remain unchanged')
|
|
196
|
-
})
|
|
192
|
+
})
|
|
193
|
+
assert.equal(verifyResult.data.attributes.name, 'Updated CURL Country')
|
|
194
|
+
assert.equal(verifyResult.data.attributes.code, 'CT', 'Code should remain unchanged')
|
|
195
|
+
})
|
|
197
196
|
|
|
198
197
|
it('should DELETE a resource using CURL', async () => {
|
|
199
|
-
const result = await executeCurl(['-s', '-X', 'DELETE', '-w', '%{http_code}', '-o', '/dev/null', `http://localhost:${TEST_PORT}/api/countries/${testData.country.id}`])
|
|
200
|
-
|
|
201
|
-
assert.equal(result.exitCode, 0, 'CURL should exit successfully')
|
|
202
|
-
assert.equal(result.stdout.trim(), '204', 'Should return 204 No Content')
|
|
203
|
-
|
|
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
|
+
|
|
204
203
|
// Verify deletion
|
|
205
204
|
try {
|
|
206
205
|
await basicApi.resources.countries.get({
|
|
207
206
|
id: testData.country.id,
|
|
208
207
|
simplified: false
|
|
209
|
-
})
|
|
210
|
-
assert.fail('Should have thrown not found error')
|
|
208
|
+
})
|
|
209
|
+
assert.fail('Should have thrown not found error')
|
|
211
210
|
} catch (error) {
|
|
212
|
-
assert.equal(error.code, 'REST_API_RESOURCE')
|
|
213
|
-
assert.equal(error.subtype, 'not_found')
|
|
211
|
+
assert.equal(error.code, 'REST_API_RESOURCE')
|
|
212
|
+
assert.equal(error.subtype, 'not_found')
|
|
214
213
|
}
|
|
215
|
-
})
|
|
214
|
+
})
|
|
216
215
|
|
|
217
216
|
it('should handle query parameters using CURL', async () => {
|
|
218
217
|
// Create additional countries
|
|
219
218
|
await basicApi.resources.countries.post({
|
|
220
219
|
inputRecord: createJsonApiDocument('countries', { name: 'Another Country', code: 'AC' }),
|
|
221
220
|
simplified: false
|
|
222
|
-
})
|
|
221
|
+
})
|
|
223
222
|
await basicApi.resources.countries.post({
|
|
224
223
|
inputRecord: createJsonApiDocument('countries', { name: 'Third Country', code: 'TC2' }),
|
|
225
224
|
simplified: false
|
|
226
|
-
})
|
|
225
|
+
})
|
|
227
226
|
|
|
228
227
|
// Test with filter (URL encode the space and square brackets)
|
|
229
|
-
const result = await executeCurl(['-s', '-H', 'Accept: application/vnd.api+json', `http://localhost:${TEST_PORT}/api/countries?filter%5Bname%5D=Another%20Country`])
|
|
230
|
-
|
|
231
|
-
assert.equal(result.exitCode, 0, 'CURL should exit successfully')
|
|
232
|
-
|
|
233
|
-
const response = JSON.parse(result.stdout)
|
|
234
|
-
validateJsonApiStructure(response, true)
|
|
235
|
-
|
|
236
|
-
assert.equal(response.data.length, 1, 'Should filter to one country')
|
|
237
|
-
assert.equal(response.data[0].attributes.name, 'Another Country')
|
|
238
|
-
})
|
|
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
|
+
})
|
|
239
238
|
|
|
240
239
|
it('should handle includes using CURL', async () => {
|
|
241
240
|
// Create publisher with country relationship
|
|
242
241
|
const publisherDoc = createJsonApiDocument('publishers',
|
|
243
242
|
{ name: 'CURL Test Publisher' },
|
|
244
243
|
{ country: createRelationship(resourceIdentifier('countries', testData.country.id)) }
|
|
245
|
-
)
|
|
244
|
+
)
|
|
246
245
|
const publisherResult = await basicApi.resources.publishers.post({
|
|
247
246
|
inputRecord: publisherDoc,
|
|
248
247
|
simplified: false
|
|
249
|
-
})
|
|
250
|
-
|
|
251
|
-
const result = await executeCurl(['-s', '-H', 'Accept: application/vnd.api+json', `http://localhost:${TEST_PORT}/api/publishers/${publisherResult.data.id}?include=country`])
|
|
252
|
-
|
|
253
|
-
assert.equal(result.exitCode, 0, 'CURL should exit successfully')
|
|
254
|
-
|
|
255
|
-
const response = JSON.parse(result.stdout)
|
|
256
|
-
validateJsonApiStructure(response, false)
|
|
257
|
-
|
|
258
|
-
assert.equal(response.data.type, 'publishers')
|
|
259
|
-
assert(response.included, 'Should have included data')
|
|
260
|
-
assert.equal(response.included.length, 1, 'Should include one resource')
|
|
261
|
-
assert.equal(response.included[0].type, 'countries')
|
|
262
|
-
assert.equal(response.included[0].id, testData.country.id)
|
|
263
|
-
})
|
|
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
|
+
})
|
|
264
263
|
|
|
265
264
|
it('should handle errors using CURL', async () => {
|
|
266
265
|
// Test 404 error
|
|
267
|
-
const result404 = await executeCurl(['-s', '-w', '\\n%{http_code}', '-H', 'Accept: application/vnd.api+json', `http://localhost:${TEST_PORT}/api/countries/999999`])
|
|
268
|
-
|
|
269
|
-
assert.equal(result404.exitCode, 0, 'CURL should exit successfully')
|
|
270
|
-
|
|
271
|
-
const lines = result404.stdout.trim().split('\n')
|
|
272
|
-
const httpCode = lines[lines.length - 1]
|
|
273
|
-
const responseBody = lines.slice(0, -1).join('\n')
|
|
274
|
-
|
|
275
|
-
assert.equal(httpCode, '404', 'Should return 404 status')
|
|
276
|
-
|
|
277
|
-
const response = JSON.parse(responseBody)
|
|
278
|
-
assert(response.errors, 'Should have errors array')
|
|
279
|
-
assert.equal(response.errors[0].status, '404')
|
|
280
|
-
assert.equal(response.errors[0].title, 'Not Found')
|
|
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')
|
|
281
280
|
|
|
282
281
|
// Test validation error - omit required name field
|
|
283
|
-
const invalidDoc = createJsonApiDocument('countries', { code: 'XX' })
|
|
284
|
-
const jsonPayload = JSON.stringify(invalidDoc)
|
|
285
|
-
|
|
286
|
-
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`])
|
|
287
|
-
|
|
288
|
-
const validationLines = resultValidation.stdout.trim().split('\n')
|
|
289
|
-
const validationCode = validationLines[validationLines.length - 1]
|
|
290
|
-
const validationBody = validationLines.slice(0, -1).join('\n')
|
|
291
|
-
|
|
292
|
-
assert.equal(validationCode, '422', 'Should return 422 for validation error')
|
|
293
|
-
|
|
294
|
-
const validationResponse = JSON.parse(validationBody)
|
|
295
|
-
assert(validationResponse.errors, 'Should have errors array')
|
|
296
|
-
assert.equal(validationResponse.errors[0].status, '422')
|
|
297
|
-
})
|
|
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
|
+
})
|
|
298
297
|
|
|
299
298
|
it('should handle different content types using CURL', async () => {
|
|
300
299
|
// Test with application/json content type
|
|
301
|
-
const newCountryDoc = createJsonApiDocument('countries', { name: 'JSON Country', code: 'JC' })
|
|
302
|
-
const jsonPayload = JSON.stringify(newCountryDoc)
|
|
303
|
-
|
|
304
|
-
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`])
|
|
305
|
-
|
|
306
|
-
assert.equal(resultJson.exitCode, 0, 'CURL should exit successfully with application/json')
|
|
307
|
-
|
|
308
|
-
const responseJson = JSON.parse(resultJson.stdout)
|
|
309
|
-
validateJsonApiStructure(responseJson, false)
|
|
310
|
-
assert.equal(responseJson.data.attributes.name, 'JSON Country')
|
|
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')
|
|
311
310
|
|
|
312
311
|
// Test with unsupported content type (should fail if strictContentType is enabled)
|
|
313
|
-
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`])
|
|
314
|
-
|
|
315
|
-
const xmlLines = resultXml.stdout.trim().split('\n')
|
|
316
|
-
const xmlCode = xmlLines[xmlLines.length - 1]
|
|
317
|
-
|
|
318
|
-
assert.equal(xmlCode, '415', 'Should return 415 Unsupported Media Type')
|
|
319
|
-
})
|
|
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
|
+
})
|
|
320
319
|
|
|
321
320
|
it('should handle headers correctly using CURL', async () => {
|
|
322
321
|
// Test with custom headers (-v outputs to stderr)
|
|
323
|
-
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}`])
|
|
324
|
-
|
|
325
|
-
assert.equal(result.exitCode, 0, 'CURL should exit successfully')
|
|
326
|
-
|
|
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
|
+
|
|
327
326
|
// Check that headers were sent (visible in verbose output on stderr)
|
|
328
|
-
assert(result.stderr.includes('X-Custom-Header: test-value'), 'Should send custom header')
|
|
329
|
-
assert(result.stderr.includes('User-Agent: CURL-Test/1.0'), 'Should send custom user agent')
|
|
330
|
-
})
|
|
331
|
-
})
|
|
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
|
+
})
|
|
332
331
|
|
|
333
332
|
describe('Complex CURL Operations', () => {
|
|
334
|
-
|
|
333
|
+
const testData = {}
|
|
335
334
|
|
|
336
335
|
beforeEach(async () => {
|
|
337
336
|
await cleanTables(knex, [
|
|
338
337
|
'basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors'
|
|
339
|
-
])
|
|
338
|
+
])
|
|
340
339
|
|
|
341
340
|
// Create complex test data
|
|
342
|
-
const countryDoc = createJsonApiDocument('countries', { name: 'Complex Country', code: 'CC' })
|
|
341
|
+
const countryDoc = createJsonApiDocument('countries', { name: 'Complex Country', code: 'CC' })
|
|
343
342
|
const countryResult = await basicApi.resources.countries.post({
|
|
344
343
|
inputRecord: countryDoc,
|
|
345
344
|
simplified: false
|
|
346
|
-
})
|
|
347
|
-
testData.country = countryResult.data
|
|
345
|
+
})
|
|
346
|
+
testData.country = countryResult.data
|
|
348
347
|
|
|
349
348
|
const publisherDoc = createJsonApiDocument('publishers',
|
|
350
349
|
{ name: 'Complex Publisher' },
|
|
351
350
|
{ country: createRelationship(resourceIdentifier('countries', testData.country.id)) }
|
|
352
|
-
)
|
|
351
|
+
)
|
|
353
352
|
const publisherResult = await basicApi.resources.publishers.post({
|
|
354
353
|
inputRecord: publisherDoc,
|
|
355
354
|
simplified: false
|
|
356
|
-
})
|
|
357
|
-
testData.publisher = publisherResult.data
|
|
355
|
+
})
|
|
356
|
+
testData.publisher = publisherResult.data
|
|
358
357
|
|
|
359
|
-
const author1Doc = createJsonApiDocument('authors', { name: 'Complex Author One' })
|
|
360
|
-
const author2Doc = createJsonApiDocument('authors', { name: 'Complex Author Two' })
|
|
358
|
+
const author1Doc = createJsonApiDocument('authors', { name: 'Complex Author One' })
|
|
359
|
+
const author2Doc = createJsonApiDocument('authors', { name: 'Complex Author Two' })
|
|
361
360
|
const author1Result = await basicApi.resources.authors.post({
|
|
362
361
|
inputRecord: author1Doc,
|
|
363
362
|
simplified: false
|
|
364
|
-
})
|
|
363
|
+
})
|
|
365
364
|
const author2Result = await basicApi.resources.authors.post({
|
|
366
365
|
inputRecord: author2Doc,
|
|
367
366
|
simplified: false
|
|
368
|
-
})
|
|
369
|
-
testData.authors = [author1Result.data, author2Result.data]
|
|
367
|
+
})
|
|
368
|
+
testData.authors = [author1Result.data, author2Result.data]
|
|
370
369
|
|
|
371
370
|
const bookDoc = createJsonApiDocument('books',
|
|
372
371
|
{ title: 'Complex Book' },
|
|
@@ -378,33 +377,33 @@ describe('CURL HTTP Abstraction Layer Tests', () => {
|
|
|
378
377
|
resourceIdentifier('authors', testData.authors[1].id)
|
|
379
378
|
])
|
|
380
379
|
}
|
|
381
|
-
)
|
|
380
|
+
)
|
|
382
381
|
const bookResult = await basicApi.resources.books.post({
|
|
383
382
|
inputRecord: bookDoc,
|
|
384
383
|
simplified: false
|
|
385
|
-
})
|
|
386
|
-
testData.book = bookResult.data
|
|
387
|
-
})
|
|
384
|
+
})
|
|
385
|
+
testData.book = bookResult.data
|
|
386
|
+
})
|
|
388
387
|
|
|
389
388
|
it('should handle nested includes using CURL', async () => {
|
|
390
|
-
const result = await executeCurl(['-s', '-H', 'Accept: application/vnd.api+json', `http://localhost:${TEST_PORT}/api/books/${testData.book.id}?include=publisher.country,authors`])
|
|
391
|
-
|
|
392
|
-
assert.equal(result.exitCode, 0, 'CURL should exit successfully')
|
|
393
|
-
|
|
394
|
-
const response = JSON.parse(result.stdout)
|
|
395
|
-
validateJsonApiStructure(response, false)
|
|
396
|
-
|
|
397
|
-
assert.equal(response.data.type, 'books')
|
|
398
|
-
assert(response.included, 'Should have included data')
|
|
399
|
-
|
|
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
|
+
|
|
400
399
|
// Should include publisher, country, and 2 authors
|
|
401
|
-
assert.equal(response.included.length, 4, 'Should include 4 resources')
|
|
402
|
-
|
|
403
|
-
const includedTypes = response.included.map(r => r.type)
|
|
404
|
-
assert(includedTypes.includes('publishers'), 'Should include publisher')
|
|
405
|
-
assert(includedTypes.includes('countries'), 'Should include country')
|
|
406
|
-
assert.equal(includedTypes.filter(t => t === 'authors').length, 2, 'Should include 2 authors')
|
|
407
|
-
})
|
|
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
|
+
})
|
|
408
407
|
|
|
409
408
|
it('should handle pagination using CURL', async () => {
|
|
410
409
|
// Create multiple books
|
|
@@ -418,51 +417,51 @@ describe('CURL HTTP Abstraction Layer Tests', () => {
|
|
|
418
417
|
}
|
|
419
418
|
),
|
|
420
419
|
simplified: false
|
|
421
|
-
})
|
|
420
|
+
})
|
|
422
421
|
}
|
|
423
422
|
|
|
424
423
|
// Test pagination (URL encode square brackets)
|
|
425
|
-
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`])
|
|
426
|
-
|
|
427
|
-
assert.equal(result.exitCode, 0, 'CURL should exit successfully')
|
|
428
|
-
|
|
429
|
-
const response = JSON.parse(result.stdout)
|
|
430
|
-
validateJsonApiStructure(response, true)
|
|
431
|
-
|
|
432
|
-
assert.equal(response.data.length, 3, 'Should return 3 books per page')
|
|
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')
|
|
433
432
|
// Note: Links are now always included, using relative URLs whenreturnBasePath isn't configured
|
|
434
|
-
})
|
|
433
|
+
})
|
|
435
434
|
|
|
436
435
|
it('should handle sorting using CURL', async () => {
|
|
437
|
-
const result = await executeCurl(['-s', '-H', 'Accept: application/vnd.api+json', `http://localhost:${TEST_PORT}/api/books?sort=-title`])
|
|
438
|
-
|
|
439
|
-
assert.equal(result.exitCode, 0, 'CURL should exit successfully')
|
|
440
|
-
|
|
441
|
-
const response = JSON.parse(result.stdout)
|
|
442
|
-
validateJsonApiStructure(response, true)
|
|
443
|
-
|
|
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
|
+
|
|
444
443
|
// Books should be sorted by title descending
|
|
445
|
-
const titles = response.data.map(book => book.attributes.title)
|
|
446
|
-
const sortedTitles = [...titles].sort().reverse()
|
|
447
|
-
assert.deepEqual(titles, sortedTitles, 'Books should be sorted by title descending')
|
|
448
|
-
})
|
|
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
|
+
})
|
|
449
448
|
|
|
450
449
|
it('should handle sparse fieldsets using CURL', async () => {
|
|
451
|
-
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`])
|
|
452
|
-
|
|
453
|
-
assert.equal(result.exitCode, 0, 'CURL should exit successfully')
|
|
454
|
-
|
|
455
|
-
const response = JSON.parse(result.stdout)
|
|
456
|
-
validateJsonApiStructure(response, false)
|
|
457
|
-
|
|
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
|
+
|
|
458
457
|
// Main resource should only have title attribute
|
|
459
|
-
assert.equal(Object.keys(response.data.attributes).length, 1, 'Should only have title attribute')
|
|
460
|
-
assert(response.data.attributes.title, 'Should have title')
|
|
461
|
-
|
|
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
|
+
|
|
462
461
|
// Included publisher should only have name attribute
|
|
463
|
-
const publisher = response.included.find(r => r.type === 'publishers')
|
|
464
|
-
assert.equal(Object.keys(publisher.attributes).length, 1, 'Publisher should only have name attribute')
|
|
465
|
-
assert(publisher.attributes.name, 'Publisher should have name')
|
|
466
|
-
})
|
|
467
|
-
})
|
|
468
|
-
})
|
|
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
|
+
})
|