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
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { describe, it, before, beforeEach, after } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import knexLib from 'knex'
|
|
4
|
-
import { createFieldGettersApi } from './fixtures/api-configs.js'
|
|
5
|
-
import { cleanTables } from './helpers/test-utils.js'
|
|
6
|
-
import { storageMode } from './helpers/storage-mode.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 { createFieldGettersApi } from './fixtures/api-configs.js'
|
|
5
|
+
import { cleanTables } from './helpers/test-utils.js'
|
|
6
|
+
import { storageMode } from './helpers/storage-mode.js'
|
|
7
7
|
|
|
8
8
|
// Create Knex instance for tests
|
|
9
9
|
const knex = knexLib({
|
|
@@ -12,26 +12,26 @@ const knex = knexLib({
|
|
|
12
12
|
filename: ':memory:'
|
|
13
13
|
},
|
|
14
14
|
useNullAsDefault: true
|
|
15
|
-
})
|
|
15
|
+
})
|
|
16
16
|
|
|
17
17
|
// API instance that persists across tests
|
|
18
|
-
let api
|
|
18
|
+
let api
|
|
19
19
|
|
|
20
20
|
describe('Field Getters', () => {
|
|
21
21
|
before(async () => {
|
|
22
22
|
// Initialize API once
|
|
23
|
-
api = await createFieldGettersApi(knex)
|
|
24
|
-
})
|
|
23
|
+
api = await createFieldGettersApi(knex)
|
|
24
|
+
})
|
|
25
25
|
|
|
26
26
|
after(async () => {
|
|
27
27
|
// Close database connection to allow tests to exit
|
|
28
|
-
await knex.destroy()
|
|
29
|
-
})
|
|
28
|
+
await knex.destroy()
|
|
29
|
+
})
|
|
30
30
|
|
|
31
31
|
describe('Basic Field Transformations', () => {
|
|
32
32
|
beforeEach(async () => {
|
|
33
|
-
await cleanTables(knex, ['getter_users'])
|
|
34
|
-
})
|
|
33
|
+
await cleanTables(knex, ['getter_users'])
|
|
34
|
+
})
|
|
35
35
|
|
|
36
36
|
it('should apply simple getter transformations', async () => {
|
|
37
37
|
// Create user with data that needs transformation
|
|
@@ -41,23 +41,23 @@ describe('Field Getters', () => {
|
|
|
41
41
|
phone: '1234567890',
|
|
42
42
|
metadata_json: '{"key": "value", "num": 123}',
|
|
43
43
|
tags_csv: 'tag1, tag2, tag3'
|
|
44
|
-
})
|
|
44
|
+
})
|
|
45
45
|
|
|
46
46
|
// Check transformations applied on create
|
|
47
|
-
assert.equal(user.email, 'user@example.com')
|
|
48
|
-
assert.equal(user.name, 'John Doe')
|
|
49
|
-
assert.equal(user.phone, '(123) 456-7890')
|
|
50
|
-
assert.deepEqual(user.metadata_json, { key: 'value', num: 123 })
|
|
51
|
-
assert.deepEqual(user.tags_csv, ['tag1', 'tag2', 'tag3'])
|
|
47
|
+
assert.equal(user.email, 'user@example.com')
|
|
48
|
+
assert.equal(user.name, 'John Doe')
|
|
49
|
+
assert.equal(user.phone, '(123) 456-7890')
|
|
50
|
+
assert.deepEqual(user.metadata_json, { key: 'value', num: 123 })
|
|
51
|
+
assert.deepEqual(user.tags_csv, ['tag1', 'tag2', 'tag3'])
|
|
52
52
|
|
|
53
53
|
// Verify getters apply on fetch too
|
|
54
|
-
const fetched = await api.resources.users.get({ id: user.id })
|
|
55
|
-
assert.equal(fetched.email, 'user@example.com')
|
|
56
|
-
assert.equal(fetched.name, 'John Doe')
|
|
57
|
-
assert.equal(fetched.phone, '(123) 456-7890')
|
|
58
|
-
assert.deepEqual(fetched.metadata_json, { key: 'value', num: 123 })
|
|
59
|
-
assert.deepEqual(fetched.tags_csv, ['tag1', 'tag2', 'tag3'])
|
|
60
|
-
})
|
|
54
|
+
const fetched = await api.resources.users.get({ id: user.id })
|
|
55
|
+
assert.equal(fetched.email, 'user@example.com')
|
|
56
|
+
assert.equal(fetched.name, 'John Doe')
|
|
57
|
+
assert.equal(fetched.phone, '(123) 456-7890')
|
|
58
|
+
assert.deepEqual(fetched.metadata_json, { key: 'value', num: 123 })
|
|
59
|
+
assert.deepEqual(fetched.tags_csv, ['tag1', 'tag2', 'tag3'])
|
|
60
|
+
})
|
|
61
61
|
|
|
62
62
|
it('should handle null and undefined values', async () => {
|
|
63
63
|
const user = await api.resources.users.post({
|
|
@@ -66,14 +66,14 @@ describe('Field Getters', () => {
|
|
|
66
66
|
phone: null,
|
|
67
67
|
metadata_json: null,
|
|
68
68
|
tags_csv: null
|
|
69
|
-
})
|
|
69
|
+
})
|
|
70
70
|
|
|
71
|
-
assert.equal(user.email, undefined)
|
|
72
|
-
assert.equal(user.name, undefined)
|
|
73
|
-
assert.equal(user.phone, null)
|
|
74
|
-
assert.deepEqual(user.metadata_json, {})
|
|
75
|
-
assert.deepEqual(user.tags_csv, [])
|
|
76
|
-
})
|
|
71
|
+
assert.equal(user.email, undefined) // null?.toLowerCase().trim() returns undefined
|
|
72
|
+
assert.equal(user.name, undefined) // undefined is passed, getter returns undefined
|
|
73
|
+
assert.equal(user.phone, null) // getter explicitly returns null for falsy values
|
|
74
|
+
assert.deepEqual(user.metadata_json, {})
|
|
75
|
+
assert.deepEqual(user.tags_csv, [])
|
|
76
|
+
})
|
|
77
77
|
|
|
78
78
|
it('should handle invalid data gracefully', async () => {
|
|
79
79
|
const user = await api.resources.users.post({
|
|
@@ -82,20 +82,20 @@ describe('Field Getters', () => {
|
|
|
82
82
|
phone: '123', // Too short
|
|
83
83
|
metadata_json: 'invalid json',
|
|
84
84
|
tags_csv: ', , empty, , values, '
|
|
85
|
-
})
|
|
85
|
+
})
|
|
86
86
|
|
|
87
|
-
assert.equal(user.email, 'mixed@case.com')
|
|
88
|
-
assert.equal(user.name, '')
|
|
89
|
-
assert.equal(user.phone, '123')
|
|
90
|
-
assert.deepEqual(user.metadata_json, {})
|
|
91
|
-
assert.deepEqual(user.tags_csv, ['empty', 'values'])
|
|
92
|
-
})
|
|
93
|
-
})
|
|
87
|
+
assert.equal(user.email, 'mixed@case.com')
|
|
88
|
+
assert.equal(user.name, '')
|
|
89
|
+
assert.equal(user.phone, '123') // Unchanged when can't format
|
|
90
|
+
assert.deepEqual(user.metadata_json, {}) // Empty object on parse error
|
|
91
|
+
assert.deepEqual(user.tags_csv, ['empty', 'values']) // Filters empty values
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
94
|
|
|
95
95
|
describe('Getters with Computed Fields', () => {
|
|
96
96
|
beforeEach(async () => {
|
|
97
|
-
await cleanTables(knex, ['getter_products'])
|
|
98
|
-
})
|
|
97
|
+
await cleanTables(knex, ['getter_products'])
|
|
98
|
+
})
|
|
99
99
|
|
|
100
100
|
it('should apply getters before computed fields', async () => {
|
|
101
101
|
const product = await api.resources.products.post({
|
|
@@ -103,18 +103,18 @@ describe('Field Getters', () => {
|
|
|
103
103
|
description: 'This is a very long product description that should be truncated',
|
|
104
104
|
price_str: '100.00',
|
|
105
105
|
tax_rate_str: '0.20'
|
|
106
|
-
})
|
|
106
|
+
})
|
|
107
107
|
|
|
108
108
|
// Getters should transform values
|
|
109
|
-
assert.equal(product.name, 'WIDGET')
|
|
110
|
-
assert.equal(product.description, 'This is a very long product description that sh...')
|
|
111
|
-
assert.equal(typeof product.price_str, 'number')
|
|
112
|
-
assert.equal(product.price_str, 100)
|
|
113
|
-
assert.equal(product.tax_rate_str, 0.20)
|
|
114
|
-
|
|
109
|
+
assert.equal(product.name, 'WIDGET')
|
|
110
|
+
assert.equal(product.description, 'This is a very long product description that sh...')
|
|
111
|
+
assert.equal(typeof product.price_str, 'number')
|
|
112
|
+
assert.equal(product.price_str, 100)
|
|
113
|
+
assert.equal(product.tax_rate_str, 0.20)
|
|
114
|
+
|
|
115
115
|
// Computed field should see getter-transformed values
|
|
116
|
-
assert.equal(product.total_price, 120)
|
|
117
|
-
})
|
|
116
|
+
assert.equal(product.total_price, 120)
|
|
117
|
+
})
|
|
118
118
|
|
|
119
119
|
it('should work with sparse fieldsets', async () => {
|
|
120
120
|
const product = await api.resources.products.post({
|
|
@@ -122,7 +122,7 @@ describe('Field Getters', () => {
|
|
|
122
122
|
description: 'Short desc',
|
|
123
123
|
price_str: '50.00',
|
|
124
124
|
tax_rate_str: '0.10'
|
|
125
|
-
})
|
|
125
|
+
})
|
|
126
126
|
|
|
127
127
|
// Fetch with sparse fieldsets
|
|
128
128
|
const sparse = await api.resources.products.get({
|
|
@@ -130,95 +130,95 @@ describe('Field Getters', () => {
|
|
|
130
130
|
queryParams: {
|
|
131
131
|
fields: { products: 'name,total_price' }
|
|
132
132
|
}
|
|
133
|
-
})
|
|
133
|
+
})
|
|
134
134
|
|
|
135
|
-
assert.equal(sparse.name, 'ANOTHER WIDGET')
|
|
136
|
-
assert.ok(Math.abs(sparse.total_price - 55) < 0.0001, `Expected total_price to be ~55, got ${sparse.total_price}`)
|
|
137
|
-
assert.equal(sparse.description, undefined)
|
|
138
|
-
assert.equal(sparse.price_str, undefined)
|
|
139
|
-
})
|
|
140
|
-
})
|
|
135
|
+
assert.equal(sparse.name, 'ANOTHER WIDGET')
|
|
136
|
+
assert.ok(Math.abs(sparse.total_price - 55) < 0.0001, `Expected total_price to be ~55, got ${sparse.total_price}`)
|
|
137
|
+
assert.equal(sparse.description, undefined)
|
|
138
|
+
assert.equal(sparse.price_str, undefined)
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
141
|
|
|
142
142
|
describe('Getter Dependencies', () => {
|
|
143
143
|
beforeEach(async () => {
|
|
144
|
-
await cleanTables(knex, ['getter_formatted'])
|
|
145
|
-
})
|
|
144
|
+
await cleanTables(knex, ['getter_formatted'])
|
|
145
|
+
})
|
|
146
146
|
|
|
147
147
|
it('should apply getters in dependency order', async () => {
|
|
148
148
|
const data = await api.resources.formatted_data.post({
|
|
149
149
|
step1: ' hello ',
|
|
150
150
|
step2: 'world',
|
|
151
151
|
step3: 'end'
|
|
152
|
-
})
|
|
152
|
+
})
|
|
153
153
|
|
|
154
154
|
// Check that getters ran in order
|
|
155
|
-
assert.equal(data.step1, 'hello')
|
|
156
|
-
assert.equal(data.step2, 'world [step1: hello]')
|
|
157
|
-
assert.equal(data.step3, 'end [step2: world [step1: hello]]')
|
|
158
|
-
})
|
|
155
|
+
assert.equal(data.step1, 'hello') // Trimmed
|
|
156
|
+
assert.equal(data.step2, 'world [step1: hello]') // Sees trimmed step1
|
|
157
|
+
assert.equal(data.step3, 'end [step2: world [step1: hello]]') // Sees processed step2
|
|
158
|
+
})
|
|
159
159
|
|
|
160
160
|
it('should handle missing dependencies gracefully', async () => {
|
|
161
161
|
const data = await api.resources.formatted_data.post({
|
|
162
162
|
step3: 'only step3'
|
|
163
163
|
// step1 and step2 are missing
|
|
164
|
-
})
|
|
164
|
+
})
|
|
165
165
|
|
|
166
|
-
assert.equal(data.step1, undefined)
|
|
167
|
-
assert.equal(data.step2, null)
|
|
168
|
-
assert.equal(data.step3, 'only step3 [step2: null]')
|
|
169
|
-
})
|
|
170
|
-
})
|
|
166
|
+
assert.equal(data.step1, undefined)
|
|
167
|
+
assert.equal(data.step2, null)
|
|
168
|
+
assert.equal(data.step3, 'only step3 [step2: null]')
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
171
|
|
|
172
172
|
describe('Async Getters', () => {
|
|
173
173
|
beforeEach(async () => {
|
|
174
|
-
await cleanTables(knex, ['getter_encrypted'])
|
|
175
|
-
})
|
|
174
|
+
await cleanTables(knex, ['getter_encrypted'])
|
|
175
|
+
})
|
|
176
176
|
|
|
177
177
|
it('should support async getter functions', async () => {
|
|
178
178
|
// Helper to simulate encryption
|
|
179
|
-
const encrypt = (value) => Buffer.from(value).toString('base64')
|
|
179
|
+
const encrypt = (value) => Buffer.from(value).toString('base64')
|
|
180
180
|
|
|
181
181
|
const payload = {
|
|
182
182
|
secret: encrypt('my-secret-value'),
|
|
183
183
|
data: encrypt('sensitive-info')
|
|
184
|
-
}
|
|
184
|
+
}
|
|
185
185
|
|
|
186
|
-
let id
|
|
186
|
+
let id
|
|
187
187
|
if (storageMode.isAnyApi()) {
|
|
188
188
|
// AnyAPI mode stores records in canonical slots, so use the API to ensure persistence
|
|
189
|
-
const created = await api.resources.encrypted_data.post(payload)
|
|
190
|
-
id = created.id
|
|
189
|
+
const created = await api.resources.encrypted_data.post(payload)
|
|
190
|
+
id = created.id
|
|
191
191
|
} else {
|
|
192
192
|
// Legacy mode can insert directly into the backing table for the same effect
|
|
193
193
|
const inserted = await knex('getter_encrypted')
|
|
194
194
|
.insert(payload)
|
|
195
195
|
.returning('id')
|
|
196
|
-
.then((rows) => rows)
|
|
197
|
-
const first = Array.isArray(inserted) ? inserted[0] : inserted
|
|
198
|
-
id = typeof first === 'object' && first !== null ? first.id : first
|
|
196
|
+
.then((rows) => rows)
|
|
197
|
+
const first = Array.isArray(inserted) ? inserted[0] : inserted
|
|
198
|
+
id = typeof first === 'object' && first !== null ? first.id : first
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
// Fetch should decrypt via async getters
|
|
202
|
-
const record = await api.resources.encrypted_data.get({ id })
|
|
203
|
-
assert.equal(record.secret, 'my-secret-value')
|
|
204
|
-
assert.equal(record.data, '[encrypted_data] sensitive-info')
|
|
205
|
-
})
|
|
202
|
+
const record = await api.resources.encrypted_data.get({ id })
|
|
203
|
+
assert.equal(record.secret, 'my-secret-value')
|
|
204
|
+
assert.equal(record.data, '[encrypted_data] sensitive-info')
|
|
205
|
+
})
|
|
206
206
|
|
|
207
207
|
it('should handle null values in async getters', async () => {
|
|
208
208
|
const record = await api.resources.encrypted_data.post({
|
|
209
209
|
secret: null,
|
|
210
210
|
data: null
|
|
211
|
-
})
|
|
211
|
+
})
|
|
212
212
|
|
|
213
|
-
assert.equal(record.secret, null)
|
|
214
|
-
assert.equal(record.data, null)
|
|
215
|
-
})
|
|
216
|
-
})
|
|
213
|
+
assert.equal(record.secret, null)
|
|
214
|
+
assert.equal(record.data, null)
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
217
|
|
|
218
218
|
describe('Getters in Included Resources', () => {
|
|
219
219
|
beforeEach(async () => {
|
|
220
|
-
await cleanTables(knex, ['getter_products', 'getter_reviews'])
|
|
221
|
-
})
|
|
220
|
+
await cleanTables(knex, ['getter_products', 'getter_reviews'])
|
|
221
|
+
})
|
|
222
222
|
|
|
223
223
|
it('should apply getters to included resources', async () => {
|
|
224
224
|
const product = await api.resources.products.post({
|
|
@@ -226,20 +226,20 @@ describe('Field Getters', () => {
|
|
|
226
226
|
description: 'A product',
|
|
227
227
|
price_str: '99.99',
|
|
228
228
|
tax_rate_str: '0'
|
|
229
|
-
})
|
|
229
|
+
})
|
|
230
230
|
|
|
231
231
|
// Add reviews
|
|
232
232
|
await api.resources.reviews.post({
|
|
233
233
|
product: product.id,
|
|
234
234
|
content: 'Great product!',
|
|
235
235
|
rating: 5
|
|
236
|
-
})
|
|
236
|
+
})
|
|
237
237
|
|
|
238
238
|
await api.resources.reviews.post({
|
|
239
239
|
product: product.id,
|
|
240
240
|
content: 'Not bad',
|
|
241
241
|
rating: 3
|
|
242
|
-
})
|
|
242
|
+
})
|
|
243
243
|
|
|
244
244
|
// Fetch with includes
|
|
245
245
|
const result = await api.resources.products.get({
|
|
@@ -247,16 +247,16 @@ describe('Field Getters', () => {
|
|
|
247
247
|
queryParams: {
|
|
248
248
|
include: ['reviews']
|
|
249
249
|
}
|
|
250
|
-
})
|
|
250
|
+
})
|
|
251
251
|
|
|
252
252
|
// Product getters should apply
|
|
253
|
-
assert.equal(result.name, 'TEST PRODUCT')
|
|
254
|
-
|
|
253
|
+
assert.equal(result.name, 'TEST PRODUCT')
|
|
254
|
+
|
|
255
255
|
// Review getters should apply to included resources
|
|
256
|
-
assert.equal(result.reviews.length, 2)
|
|
257
|
-
assert.equal(result.reviews[0].content, '[REVIEW] Great product!')
|
|
258
|
-
assert.equal(result.reviews[1].content, '[REVIEW] Not bad')
|
|
259
|
-
})
|
|
256
|
+
assert.equal(result.reviews.length, 2)
|
|
257
|
+
assert.equal(result.reviews[0].content, '[REVIEW] Great product!')
|
|
258
|
+
assert.equal(result.reviews[1].content, '[REVIEW] Not bad')
|
|
259
|
+
})
|
|
260
260
|
|
|
261
261
|
it('should work with sparse fieldsets on included resources', async () => {
|
|
262
262
|
const product = await api.resources.products.post({
|
|
@@ -264,13 +264,13 @@ describe('Field Getters', () => {
|
|
|
264
264
|
description: 'Description',
|
|
265
265
|
price_str: '10',
|
|
266
266
|
tax_rate_str: '0'
|
|
267
|
-
})
|
|
267
|
+
})
|
|
268
268
|
|
|
269
269
|
await api.resources.reviews.post({
|
|
270
270
|
product: product.id,
|
|
271
271
|
content: 'Review text',
|
|
272
272
|
rating: 4
|
|
273
|
-
})
|
|
273
|
+
})
|
|
274
274
|
|
|
275
275
|
const result = await api.resources.products.get({
|
|
276
276
|
id: product.id,
|
|
@@ -281,16 +281,16 @@ describe('Field Getters', () => {
|
|
|
281
281
|
reviews: 'content' // Only content, not rating
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
|
-
})
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
assert.equal(result.name, 'ANOTHER PRODUCT')
|
|
287
|
+
assert.equal(result.description, undefined)
|
|
285
288
|
|
|
286
|
-
assert.equal(result.
|
|
287
|
-
assert.equal(result.
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
assert.equal(result.reviews[0].rating, undefined);
|
|
292
|
-
});
|
|
293
|
-
});
|
|
289
|
+
assert.equal(result.reviews.length, 1)
|
|
290
|
+
assert.equal(result.reviews[0].content, '[REVIEW] Review text')
|
|
291
|
+
assert.equal(result.reviews[0].rating, undefined)
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
294
|
|
|
295
295
|
describe('Getter Error Handling', () => {
|
|
296
296
|
it('should handle getter errors gracefully', async () => {
|
|
@@ -305,30 +305,30 @@ describe('Field Getters', () => {
|
|
|
305
305
|
bad_field: {
|
|
306
306
|
type: 'string',
|
|
307
307
|
getter: (value) => {
|
|
308
|
-
throw new Error('Getter failed!')
|
|
308
|
+
throw new Error('Getter failed!')
|
|
309
309
|
}
|
|
310
310
|
}
|
|
311
311
|
},
|
|
312
312
|
tableName: 'getter_errors'
|
|
313
|
-
})
|
|
314
|
-
await api.resources.error_test.createKnexTable()
|
|
313
|
+
})
|
|
314
|
+
await api.resources.error_test.createKnexTable()
|
|
315
315
|
if (storageMode.isAnyApi()) {
|
|
316
|
-
storageMode.registerTable('getter_errors', 'error_test')
|
|
316
|
+
storageMode.registerTable('getter_errors', 'error_test')
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
const record = await api.resources.error_test.post({
|
|
320
320
|
good_field: 'hello',
|
|
321
321
|
bad_field: 'world'
|
|
322
|
-
})
|
|
322
|
+
})
|
|
323
323
|
|
|
324
324
|
// Good field should be transformed
|
|
325
|
-
assert.equal(record.good_field, 'HELLO')
|
|
325
|
+
assert.equal(record.good_field, 'HELLO')
|
|
326
326
|
// Bad field should keep original value (error logged but not thrown)
|
|
327
|
-
assert.equal(record.bad_field, 'world')
|
|
327
|
+
assert.equal(record.bad_field, 'world')
|
|
328
328
|
|
|
329
|
-
await cleanTables(knex, ['getter_errors'])
|
|
330
|
-
})
|
|
331
|
-
})
|
|
329
|
+
await cleanTables(knex, ['getter_errors'])
|
|
330
|
+
})
|
|
331
|
+
})
|
|
332
332
|
|
|
333
333
|
describe('Circular Dependencies', () => {
|
|
334
334
|
it('should detect circular getter dependencies', async () => {
|
|
@@ -349,8 +349,8 @@ describe('Field Getters', () => {
|
|
|
349
349
|
tableName: 'circular_test'
|
|
350
350
|
}),
|
|
351
351
|
/Circular dependency detected/
|
|
352
|
-
)
|
|
353
|
-
})
|
|
352
|
+
)
|
|
353
|
+
})
|
|
354
354
|
|
|
355
355
|
it('should detect unknown getter dependencies', async () => {
|
|
356
356
|
await assert.rejects(
|
|
@@ -365,7 +365,7 @@ describe('Field Getters', () => {
|
|
|
365
365
|
tableName: 'unknown_test'
|
|
366
366
|
}),
|
|
367
367
|
/getter dependency 'nonexistent' that does not exist/
|
|
368
|
-
)
|
|
369
|
-
})
|
|
370
|
-
})
|
|
371
|
-
})
|
|
368
|
+
)
|
|
369
|
+
})
|
|
370
|
+
})
|
|
371
|
+
})
|