json-rest-api 1.0.29 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +10 -670
- package/LICENSE-GPL-3.0 +674 -0
- package/LICENSE-MIT +21 -0
- package/README.md +39 -30
- package/docs/API.md +744 -1273
- package/docs/GUIDE/01-getting-started.md +338 -0
- package/docs/GUIDE/02-resources-and-relationships.md +33 -0
- package/docs/GUIDE/03-running-example.md +87 -0
- package/docs/GUIDE/04-creating-and-querying.md +181 -0
- package/docs/GUIDE/05-belongs-to.md +177 -0
- package/docs/GUIDE/06-has-many.md +184 -0
- package/docs/GUIDE/07-polymorphic-relationships.md +191 -0
- package/docs/GUIDE/08-many-to-many.md +184 -0
- package/docs/GUIDE/09-pagination-and-sorting.md +149 -0
- package/docs/GUIDE/10-put-and-patch.md +174 -0
- package/docs/GUIDE/11-relationship-endpoints.md +174 -0
- package/docs/GUIDE/12-field-transformations.md +287 -0
- package/docs/GUIDE/13-hooks-and-lifecycle.md +479 -0
- package/docs/GUIDE/14-custom-resource-methods.md +125 -0
- package/docs/GUIDE/15-query-projections.md +266 -0
- package/docs/GUIDE/{GUIDE_X_Autofiltering.md → 16-autofiltering.md} +77 -15
- package/docs/GUIDE/{GUIDE_X_Row_Policies.md → 17-row-policies.md} +137 -44
- package/docs/GUIDE/18-labels.md +86 -0
- package/docs/GUIDE/19-managed-transactions.md +179 -0
- package/docs/GUIDE/20-transaction-outcomes.md +207 -0
- package/docs/GUIDE/21-schema-and-migrations.md +634 -0
- package/docs/GUIDE/22-version-field-migrations.md +208 -0
- package/docs/GUIDE/23-fastify.md +179 -0
- package/docs/GUIDE/24-cors.md +172 -0
- package/docs/GUIDE/{GUIDE_X_URL_Management.md → 25-url-management.md} +7 -1
- package/docs/GUIDE/26-file-uploads.md +278 -0
- package/docs/GUIDE/27-bulk-operations.md +277 -0
- package/docs/GUIDE/28-socketio.md +325 -0
- package/docs/GUIDE/29-writing-plugins.md +278 -0
- package/docs/GUIDE/30-backend-capabilities.md +100 -0
- package/docs/GUIDE/31-positioning.md +181 -0
- package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +74 -30
- package/docs/GUIDE/33-migrating-to-v2.md +3827 -0
- package/docs/GUIDE/index.md +51 -59
- package/docs/QUICKSTART.md +120 -287
- package/docs/architecture.md +75 -0
- package/docs/contributing.md +178 -0
- package/index.d.ts +36 -0
- package/index.js +1 -22
- package/lib/enhanced-logger.js +91 -85
- package/lib/error-context.js +445 -5
- package/lib/error-formatter.js +219 -59
- package/lib/knex-transaction.js +64 -0
- package/lib/logger-types.d.ts +22 -0
- package/lib/missing-package.js +4 -0
- package/lib/rest-api-errors.js +103 -8
- package/lib/runtime/json-rest-api.js +172 -0
- package/lib/transaction-types.d.ts +59 -0
- package/package.json +97 -27
- package/plugins/core/bulk-operations-plugin.js +191 -184
- package/plugins/core/connectors/express-plugin.js +148 -219
- package/plugins/core/connectors/fastify-plugin.js +125 -82
- package/plugins/core/connectors/lib/busboy-detector.js +74 -156
- package/plugins/core/connectors/lib/connector-core.js +144 -20
- package/plugins/core/connectors/lib/formidable-detector.js +63 -120
- package/plugins/core/connectors/lib/http-validators.js +77 -0
- package/plugins/core/connectors/lib/multipart-helpers.js +18 -0
- package/plugins/core/connectors/lib/request-helpers.js +10 -1
- package/plugins/core/connectors/lib/transport-http-helpers.js +89 -8
- package/plugins/core/connectors/lib/transport-route-schemas.js +7 -4
- package/plugins/core/file-handling-plugin.js +87 -177
- package/plugins/core/lib/anyapi/anyapi-registry.js +213 -164
- package/plugins/core/lib/anyapi/canonical-link-store.js +507 -0
- package/plugins/core/lib/anyapi/canonical-relationship-reader.js +598 -0
- package/plugins/core/lib/anyapi/schema-utils.js +25 -16
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
- package/plugins/core/lib/dbIntrospection.js +228 -21
- package/plugins/core/lib/dbTablesOperations.js +409 -92
- package/plugins/core/lib/fractional-positioning.js +12 -48
- package/plugins/core/lib/querying/identifier-query.js +19 -0
- package/plugins/core/lib/querying/include-query-helpers.js +364 -0
- package/plugins/core/lib/querying/include-to-many.js +428 -0
- package/plugins/core/lib/querying/include-to-one.js +579 -0
- package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
- package/plugins/core/lib/querying/knex-pagination-helpers.js +48 -317
- package/plugins/core/lib/querying/knex-process-includes.js +5 -110
- package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
- package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
- package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
- package/plugins/core/lib/querying/knex-window-queries.js +62 -253
- package/plugins/core/lib/querying/query-builder-utils.js +44 -4
- package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
- package/plugins/core/lib/querying/query-constraint.js +27 -0
- package/plugins/core/lib/querying/query-field-sort-helpers.js +134 -37
- package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
- package/plugins/core/lib/querying/sort-helpers.js +13 -1
- package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
- package/plugins/core/lib/querying/url-helpers.js +11 -4
- package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
- package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +179 -194
- package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
- package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
- package/plugins/core/lib/querying-writing/field-utils.js +113 -92
- package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
- package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +100 -290
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
- package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
- package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
- package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
- package/plugins/core/lib/querying-writing/request-contracts.js +93 -38
- package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
- package/plugins/core/lib/querying-writing/response-options.js +35 -0
- package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
- package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
- package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
- package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
- package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
- package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
- package/plugins/core/lib/storage/storage-adapter.js +128 -62
- package/plugins/core/lib/storage/storage-mapping.js +78 -28
- package/plugins/core/lib/storage/storage-types.d.ts +502 -0
- package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
- package/plugins/core/lib/writing/relationship-processor.js +94 -158
- package/plugins/core/lib/writing/resource-version.js +172 -0
- package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
- package/plugins/core/rest-api-anyapi-knex-plugin.js +116 -2485
- package/plugins/core/rest-api-autofilter-plugin.js +17 -19
- package/plugins/core/rest-api-cors-plugin.js +52 -205
- package/plugins/core/rest-api-knex-plugin.js +45 -1077
- package/plugins/core/rest-api-label-plugin.js +25 -38
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
- package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
- package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
- package/plugins/core/rest-api-plugin-methods/common.js +291 -395
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
- package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -148
- package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
- package/plugins/core/rest-api-plugin-methods/get.js +36 -68
- package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
- package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
- package/plugins/core/rest-api-plugin-methods/post.js +51 -51
- package/plugins/core/rest-api-plugin-methods/put.js +69 -148
- package/plugins/core/rest-api-plugin-methods/query.js +37 -81
- package/plugins/core/rest-api-plugin-methods/release.js +2 -1
- package/plugins/core/rest-api-plugin.js +32 -46
- package/plugins/core/rest-api-positioning-plugin.js +183 -628
- package/plugins/core/rest-api-query-projections-plugin.js +19 -102
- package/plugins/core/rest-api-row-policy-plugin.js +3 -3
- package/plugins/core/socketio-plugin.js +364 -344
- package/plugins/storage/local-storage.js +40 -36
- package/types/autofilter.d.ts +57 -0
- package/types/bulk-methods.d.ts +63 -0
- package/types/errors.d.ts +118 -0
- package/types/fastify-options.d.ts +19 -0
- package/types/file-detectors.d.ts +33 -0
- package/types/file-storage.d.ts +48 -0
- package/types/hook-context.d.ts +180 -0
- package/types/plugin-options.d.ts +53 -0
- package/types/relationship-methods.d.ts +74 -0
- package/types/representations.d.ts +50 -0
- package/types/resource-methods.d.ts +95 -0
- package/types/resource-schema.d.ts +157 -0
- package/types/row-policy.d.ts +31 -0
- package/types/runtime.d.ts +132 -0
- package/types/transactions.d.ts +21 -0
- package/.claude/agents/library-guide-expert.md +0 -30
- package/.claude/agents/rest-api-internals-expert.md +0 -54
- package/.claude/settings.local.json +0 -70
- package/AGENTS.md +0 -34
- package/CLAUDE.md +0 -160
- package/CRUSH.md +0 -55
- package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
- package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
- package/TODO.todo +0 -13
- package/connectors_ideas/fastify.js +0 -217
- package/connectors_ideas/koa-plugin.js +0 -112
- package/docs/.bundle/config +0 -2
- package/docs/COMPARISON.md +0 -433
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
- package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
- package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
- package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
- package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
- package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1247
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
- package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
- package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
- package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
- package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
- package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
- package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
- package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
- package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
- package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
- package/docs/Gemfile +0 -6
- package/docs/Gemfile.lock +0 -308
- package/docs/ONBOARDING.md +0 -315
- package/docs/_config.yml +0 -36
- package/docs/_layouts/default.html +0 -123
- package/docs/apple-touch-icon.svg +0 -4
- package/docs/assets/css/style.scss +0 -392
- package/docs/favicon.svg +0 -4
- package/docs/index.md +0 -164
- package/docs/mini-guides/file-uploads-complete.md +0 -161
- package/eslint.config.js +0 -13
- package/examples/url-override-example.js +0 -137
- package/fixing_plan.md +0 -112
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
- package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
- package/plugins/storage/s3-storage.js +0 -93
- package/quickTest.js +0 -136
- package/scripts/debug-anyapi-review-filter.mjs +0 -63
- package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
- package/scripts/inspect-anyapi-descriptor.mjs +0 -19
- package/scripts/run-docs.js +0 -133
- package/test-debug.js +0 -59
- package/tests/README.md +0 -202
- package/tests/TEST_TEMPLATE.test.js +0 -178
- package/tests/anyapi-basic.test.js +0 -105
- package/tests/anyapi-cursor-pagination.test.js +0 -148
- package/tests/anyapi-custom-idproperty.test.js +0 -163
- package/tests/autofilter.test.js +0 -463
- package/tests/bulk-operations.test.js +0 -657
- package/tests/computed-fields.test.js +0 -586
- package/tests/cors.test.js +0 -834
- package/tests/curl.test.js +0 -467
- package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
- package/tests/custom-id-simplified-post.test.js +0 -75
- package/tests/custom-idproperty-relationships.test.js +0 -932
- package/tests/db-introspection.test.js +0 -521
- package/tests/db-table-operations.test.js +0 -798
- package/tests/fastify-plugin.test.js +0 -184
- package/tests/field-getters.test.js +0 -370
- package/tests/field-setters.test.js +0 -306
- package/tests/file-handling.test.js +0 -155
- package/tests/fixtures/api-configs.js +0 -2566
- package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
- package/tests/helpers/fake-fastify.js +0 -99
- package/tests/helpers/storage-mode.js +0 -65
- package/tests/helpers/test-utils.js +0 -203
- package/tests/http-connectors-parity.test.js +0 -395
- package/tests/id-normalization.test.js +0 -444
- package/tests/include-depth-validation.test.js +0 -331
- package/tests/includes.test.js +0 -427
- package/tests/local-storage.test.js +0 -96
- package/tests/logical-id-write-validation.test.js +0 -151
- package/tests/nested-includes.test.js +0 -617
- package/tests/pagination-boundaries.test.js +0 -154
- package/tests/pagination-contract.test.js +0 -122
- package/tests/pagination-cursor-multifield.test.js +0 -641
- package/tests/pagination-enhanced.test.js +0 -563
- package/tests/polymorphic-relationships.test.js +0 -347
- package/tests/polymorphic-simple.test.js +0 -148
- package/tests/positioning-advanced.test.js +0 -692
- package/tests/positioning-extended.test.js +0 -915
- package/tests/positioning-multifilter.test.js +0 -699
- package/tests/positioning.test.js +0 -560
- package/tests/post-commit-hooks.test.js +0 -120
- package/tests/projected-fields.test.js +0 -319
- package/tests/public-exports.test.js +0 -15
- package/tests/put-create-parity.test.js +0 -216
- package/tests/queries.test.js +0 -521
- package/tests/query-limits.test.js +0 -584
- package/tests/relationship-endpoints.test.js +0 -620
- package/tests/relationship-include-storage-adapter.test.js +0 -56
- package/tests/relationships.test.js +0 -681
- package/tests/rest-api.test.js +0 -337
- package/tests/return-record-settings.test.js +0 -184
- package/tests/row-policy.test.js +0 -489
- package/tests/s3-storage.test.js +0 -38
- package/tests/searchschema-knex-behavior.test.js +0 -187
- package/tests/searchschema-merge.test.js +0 -151
- package/tests/socketio.test.js +0 -860
- package/tests/storage-mapping.test.js +0 -362
- package/tests/temporal-boundaries.test.js +0 -208
- package/tests/test-development-guide.md +0 -370
- package/tests/virtual-fields.test.js +0 -404
package/tests/README.md
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
# JSON-REST-API Test Suite
|
|
2
|
-
|
|
3
|
-
This directory contains comprehensive tests for the JSON-REST-API library. All tests follow strict patterns to ensure proper JSON:API compliance and database integrity.
|
|
4
|
-
|
|
5
|
-
## Test Files
|
|
6
|
-
|
|
7
|
-
- **rest-api.test.js** - Basic CRUD operations (POST, GET, PATCH, DELETE)
|
|
8
|
-
- **relationships.test.js** - One-to-one and many-to-many relationship operations
|
|
9
|
-
- **queries.test.js** - Filtering, sorting, and pagination tests
|
|
10
|
-
- **fixtures/api-configs.js** - API configuration factories
|
|
11
|
-
- **helpers/test-utils.js** - Utility functions for JSON:API validation
|
|
12
|
-
|
|
13
|
-
## Key Testing Principles
|
|
14
|
-
|
|
15
|
-
### 1. Strict Mode
|
|
16
|
-
|
|
17
|
-
All tests MUST use `simplified: false` to ensure JSON:API compliance:
|
|
18
|
-
|
|
19
|
-
```javascript
|
|
20
|
-
const result = await api.resources.resourceName.method({
|
|
21
|
-
inputRecord: jsonApiDocument,
|
|
22
|
-
simplified: false,
|
|
23
|
-
returnFullRecord: 'full' // Use 'no', 'minimal', or 'full' based on needs
|
|
24
|
-
});
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### 2. Relationship Handling
|
|
28
|
-
|
|
29
|
-
Foreign keys must be set via relationships, NOT attributes:
|
|
30
|
-
|
|
31
|
-
```javascript
|
|
32
|
-
// ❌ WRONG - Will fail in strict mode
|
|
33
|
-
const bookDoc = createJsonApiDocument('books', {
|
|
34
|
-
title: 'My Book',
|
|
35
|
-
country_id: countryId // Foreign key in attributes
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// ✅ CORRECT - Use relationships
|
|
39
|
-
const bookDoc = createJsonApiDocument('books',
|
|
40
|
-
{ title: 'My Book' },
|
|
41
|
-
{
|
|
42
|
-
country: createRelationship(resourceIdentifier('countries', countryId))
|
|
43
|
-
}
|
|
44
|
-
);
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
### 3. Database Operations
|
|
48
|
-
|
|
49
|
-
Only these Knex operations are allowed directly:
|
|
50
|
-
|
|
51
|
-
```javascript
|
|
52
|
-
// Counting records
|
|
53
|
-
const count = await countRecords(knex, 'table_name');
|
|
54
|
-
|
|
55
|
-
// Cleaning tables between tests
|
|
56
|
-
await cleanTables(knex, ['table1', 'table2']);
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
All other operations MUST go through the API.
|
|
60
|
-
|
|
61
|
-
### 4. API Configuration
|
|
62
|
-
|
|
63
|
-
Always use the same schema pattern (Countries, Publishers, Authors, Books):
|
|
64
|
-
|
|
65
|
-
```javascript
|
|
66
|
-
// Each API config must use unique table prefixes
|
|
67
|
-
tableName: 'basic_countries' // For basicApi
|
|
68
|
-
tableName: 'ext_countries' // For extendedApi
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### 5. Search & Sort Configuration
|
|
72
|
-
|
|
73
|
-
Enable search on fields that need filtering:
|
|
74
|
-
|
|
75
|
-
```javascript
|
|
76
|
-
schema: {
|
|
77
|
-
title: { type: 'string', search: true },
|
|
78
|
-
country_id: { type: 'number', belongsTo: 'countries', as: 'country', search: true }
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
Configure sortable fields in plugin options:
|
|
83
|
-
|
|
84
|
-
```javascript
|
|
85
|
-
await api.use(RestApiPlugin, {
|
|
86
|
-
sortableFields: ['id', 'title', 'country_id', 'price']
|
|
87
|
-
});
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## Common Test Patterns
|
|
91
|
-
|
|
92
|
-
### Creating Resources with Relationships
|
|
93
|
-
|
|
94
|
-
```javascript
|
|
95
|
-
// One-to-one (belongsTo)
|
|
96
|
-
const publisherDoc = createJsonApiDocument('publishers',
|
|
97
|
-
{ name: 'Publisher Name' },
|
|
98
|
-
{
|
|
99
|
-
country: createRelationship(resourceIdentifier('countries', countryId))
|
|
100
|
-
}
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
// Many-to-many
|
|
104
|
-
const bookDoc = createJsonApiDocument('books',
|
|
105
|
-
{ title: 'Book Title' },
|
|
106
|
-
{
|
|
107
|
-
authors: createToManyRelationship([
|
|
108
|
-
resourceIdentifier('authors', author1Id),
|
|
109
|
-
resourceIdentifier('authors', author2Id)
|
|
110
|
-
])
|
|
111
|
-
}
|
|
112
|
-
);
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### Validating Responses
|
|
116
|
-
|
|
117
|
-
```javascript
|
|
118
|
-
// Validate JSON:API structure
|
|
119
|
-
validateJsonApiStructure(result, false); // false = single resource
|
|
120
|
-
|
|
121
|
-
// Validate attributes
|
|
122
|
-
assertResourceAttributes(result.data, {
|
|
123
|
-
name: 'Expected Name',
|
|
124
|
-
code: 'EX'
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
// Validate relationships
|
|
128
|
-
assertResourceRelationship(result.data, 'country',
|
|
129
|
-
resourceIdentifier('countries', countryId));
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
### Query Operations
|
|
133
|
-
|
|
134
|
-
```javascript
|
|
135
|
-
// Filtering
|
|
136
|
-
const result = await api.resources.books.query({
|
|
137
|
-
queryParams: {
|
|
138
|
-
filters: { language: 'en', price: '9.99' }
|
|
139
|
-
},
|
|
140
|
-
simplified: false
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// Sorting
|
|
144
|
-
const result = await api.resources.books.query({
|
|
145
|
-
queryParams: {
|
|
146
|
-
sort: ['title', '-price'] // title ASC, price DESC
|
|
147
|
-
},
|
|
148
|
-
simplified: false
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
// Pagination
|
|
152
|
-
const result = await api.resources.books.query({
|
|
153
|
-
queryParams: {
|
|
154
|
-
page: { number: 2, size: 10 }
|
|
155
|
-
},
|
|
156
|
-
simplified: false
|
|
157
|
-
});
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
## Running Tests
|
|
161
|
-
|
|
162
|
-
```bash
|
|
163
|
-
# Run all tests
|
|
164
|
-
npm test
|
|
165
|
-
|
|
166
|
-
# Run the focused normalizeId verification path plus docs rebuild
|
|
167
|
-
npm run verify
|
|
168
|
-
|
|
169
|
-
# Run all tests against the AnyAPI storage backend
|
|
170
|
-
npm run test:anyapi
|
|
171
|
-
|
|
172
|
-
# Run only the normalizeId contract suites
|
|
173
|
-
npm run test:id-contracts
|
|
174
|
-
npm run test:id-contracts:anyapi
|
|
175
|
-
|
|
176
|
-
# Windows PowerShell (AnyAPI run)
|
|
177
|
-
$Env:JSON_REST_API_STORAGE = 'anyapi'; npm test
|
|
178
|
-
|
|
179
|
-
# Run specific test file
|
|
180
|
-
npm test -- tests/relationships.test.js
|
|
181
|
-
|
|
182
|
-
# Run with verbose output
|
|
183
|
-
DEBUG=* npm test
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
## Adding New Tests
|
|
187
|
-
|
|
188
|
-
1. Create test file following naming convention: `feature-name.test.js`
|
|
189
|
-
2. Import necessary utilities from `helpers/test-utils.js`
|
|
190
|
-
3. Use API configurations from `fixtures/api-configs.js`
|
|
191
|
-
4. Follow the established patterns for:
|
|
192
|
-
- Setting up test data
|
|
193
|
-
- Making API calls with `simplified: false`
|
|
194
|
-
- Validating JSON:API responses
|
|
195
|
-
- Cleaning data between tests
|
|
196
|
-
|
|
197
|
-
## Important Notes
|
|
198
|
-
|
|
199
|
-
- Tests intentionally generate ERROR logs for validation testing
|
|
200
|
-
- All tests should pass with exit code 0
|
|
201
|
-
- Database connections are properly closed in `after` hooks
|
|
202
|
-
- Pivot table operations use `simplified: true` internally for foreign keys
|
|
@@ -1,178 +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 {
|
|
5
|
-
validateJsonApiStructure,
|
|
6
|
-
cleanTables,
|
|
7
|
-
createJsonApiDocument,
|
|
8
|
-
assertResourceAttributes
|
|
9
|
-
} from './helpers/test-utils.js'
|
|
10
|
-
import { createBasicApi } from './fixtures/api-configs.js'
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* TEST TEMPLATE - BEST PRACTICES LEARNED
|
|
14
|
-
*
|
|
15
|
-
* CRITICAL RULES:
|
|
16
|
-
* 1. Create API instance ONLY ONCE in before() - NOT in beforeEach()
|
|
17
|
-
* 2. Use cleanTables() in beforeEach() to reset data between tests
|
|
18
|
-
* 3. Always pass context as SECOND parameter to resource methods
|
|
19
|
-
* 4. Clean up any intervals/timers in after() hook
|
|
20
|
-
* 5. SQLite returns numeric IDs - JSON:API expects string IDs
|
|
21
|
-
* 6. For auth tests, context.auth must be set via JWT token or passed manually
|
|
22
|
-
*
|
|
23
|
-
* NOTICE TO CLAUDE/AI ASSISTANTS:
|
|
24
|
-
*
|
|
25
|
-
* API CONFIGURATION RULES - EXTREMELY IMPORTANT:
|
|
26
|
-
* 1. NEVER add resources with api.addResource() in test files!
|
|
27
|
-
* 2. ALL resources MUST be defined in api-configs.js
|
|
28
|
-
* 3. NEVER create resources in test files - this is a CRITICAL RULE!
|
|
29
|
-
* 4. If createBasicApi doesn't have the resources you need:
|
|
30
|
-
* - DO NOT modify createBasicApi
|
|
31
|
-
* - Create a NEW function in api-configs.js like:
|
|
32
|
-
* export async function createExtendedApi(knex, pluginOptions) {
|
|
33
|
-
* const api = await createApi();
|
|
34
|
-
* // Install plugins
|
|
35
|
-
* // Add YOUR resources here with api.addResource()
|
|
36
|
-
* // Create tables
|
|
37
|
-
* return api;
|
|
38
|
-
* }
|
|
39
|
-
* 5. Example structure in api-configs.js:
|
|
40
|
-
* - createBasicApi() - has countries, publishers, authors, books
|
|
41
|
-
* - createAuthApi() - might have users, roles, permissions
|
|
42
|
-
* - createYourCustomApi() - whatever resources you need
|
|
43
|
-
*
|
|
44
|
-
* RESOURCE CREATION RULE (REPEAT FOR EMPHASIS):
|
|
45
|
-
* Resources are ONLY created in api-configs.js, NEVER in test files!
|
|
46
|
-
* If you need new resources, create a new function in api-configs.js!
|
|
47
|
-
*
|
|
48
|
-
* DATA ACCESS RULES - EXTREMELY IMPORTANT:
|
|
49
|
-
* 1. NEVER query the database directly with knex('table').select()
|
|
50
|
-
* 2. ALWAYS use API methods to check data:
|
|
51
|
-
* - Use api.resources.[resource].get() to fetch a single item
|
|
52
|
-
* - Use api.resources.[resource].query() to fetch multiple items
|
|
53
|
-
* - Use api.resources.[resource].post() to create items
|
|
54
|
-
* 3. Why? The API methods:
|
|
55
|
-
* - Apply proper schema transformations
|
|
56
|
-
* - Handle relationships correctly
|
|
57
|
-
* - Return data in the expected JSON:API format
|
|
58
|
-
* - Include computed fields and enriched attributes
|
|
59
|
-
* 4. Direct DB queries will give you raw data that doesn't match what the API returns!
|
|
60
|
-
*
|
|
61
|
-
* BAD EXAMPLE:
|
|
62
|
-
* const record = await knex('basic_countries').where('id', 1).first(); // NO!
|
|
63
|
-
*
|
|
64
|
-
* GOOD EXAMPLE:
|
|
65
|
-
* const result = await api.resources.countries.get({ id: 1 });
|
|
66
|
-
* const record = result.data; // YES!
|
|
67
|
-
*/
|
|
68
|
-
|
|
69
|
-
// Create Knex instance for tests - always use SQLite in-memory
|
|
70
|
-
const knex = knexLib({
|
|
71
|
-
client: 'better-sqlite3',
|
|
72
|
-
connection: {
|
|
73
|
-
filename: ':memory:'
|
|
74
|
-
},
|
|
75
|
-
useNullAsDefault: true
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
// API instance that persists across ALL tests
|
|
79
|
-
let api
|
|
80
|
-
|
|
81
|
-
describe('Your Feature Name (Template Example)', () => {
|
|
82
|
-
// IMPORTANT: before() runs ONCE for the entire test suite
|
|
83
|
-
before(async () => {
|
|
84
|
-
// Create API instance ONCE - this is reused for all tests
|
|
85
|
-
// NOTE: If you need different resources than what createBasicApi provides,
|
|
86
|
-
// create a new function in api-configs.js!
|
|
87
|
-
api = await createBasicApi(knex)
|
|
88
|
-
|
|
89
|
-
// Install your plugin if testing a specific plugin
|
|
90
|
-
// await api.use(YourPlugin, {
|
|
91
|
-
// option1: 'value1',
|
|
92
|
-
// option2: 'value2'
|
|
93
|
-
// });
|
|
94
|
-
|
|
95
|
-
// NO api.addResource() HERE! Resources are defined in api-configs.js!
|
|
96
|
-
// createBasicApi already created tables for: countries, publishers, authors, books
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
// IMPORTANT: after() cleans up resources
|
|
100
|
-
after(async () => {
|
|
101
|
-
// Clean up any intervals/timers your plugin might have created
|
|
102
|
-
// Example from JWT plugin:
|
|
103
|
-
// if (api.vars.yourPluginCleanupJob) {
|
|
104
|
-
// clearInterval(api.vars.yourPluginCleanupJob);
|
|
105
|
-
// }
|
|
106
|
-
|
|
107
|
-
// Always destroy knex connection to allow tests to exit
|
|
108
|
-
await knex.destroy()
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
// IMPORTANT: beforeEach() cleans data but does NOT recreate API
|
|
112
|
-
beforeEach(async () => {
|
|
113
|
-
// Clean all tables - list ALL tables your tests use
|
|
114
|
-
await cleanTables(knex, [
|
|
115
|
-
'basic_countries', // From createBasicApi
|
|
116
|
-
'basic_publishers', // From createBasicApi
|
|
117
|
-
'basic_authors', // From createBasicApi
|
|
118
|
-
'basic_books', // From createBasicApi
|
|
119
|
-
'basic_book_authors' // From createBasicApi (junction table)
|
|
120
|
-
// Add any additional tables used by your tests here
|
|
121
|
-
])
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
// ONE SIMPLE EXAMPLE TEST
|
|
125
|
-
it('should create and retrieve a country', async () => {
|
|
126
|
-
// Create a country using the API
|
|
127
|
-
const doc = createJsonApiDocument('countries', {
|
|
128
|
-
name: 'Test Country',
|
|
129
|
-
code: 'TC'
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
// Create the country
|
|
133
|
-
// IMPORTANT: Context is SECOND parameter, not inside first parameter!
|
|
134
|
-
const createResult = await api.resources.countries.post({
|
|
135
|
-
inputRecord: doc,
|
|
136
|
-
simplified: false
|
|
137
|
-
}, { /* optional context goes here */ })
|
|
138
|
-
|
|
139
|
-
// Validate the creation response
|
|
140
|
-
validateJsonApiStructure(createResult)
|
|
141
|
-
assert.equal(createResult.data.type, 'countries')
|
|
142
|
-
assertResourceAttributes(createResult.data, {
|
|
143
|
-
name: 'Test Country',
|
|
144
|
-
code: 'TC'
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
// Retrieve the country we just created
|
|
148
|
-
const getResult = await api.resources.countries.get({
|
|
149
|
-
id: createResult.data.id,
|
|
150
|
-
simplified: false
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
// Validate the retrieved country
|
|
154
|
-
validateJsonApiStructure(getResult)
|
|
155
|
-
assert.equal(getResult.data.id, createResult.data.id)
|
|
156
|
-
assert.equal(getResult.data.attributes.name, 'Test Country')
|
|
157
|
-
assert.equal(getResult.data.attributes.code, 'TC')
|
|
158
|
-
})
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* COMMON PITFALLS TO AVOID:
|
|
163
|
-
*
|
|
164
|
-
* 1. DON'T create API in beforeEach - it's expensive and unnecessary
|
|
165
|
-
* 2. DON'T forget to clean tables between tests
|
|
166
|
-
* 3. DON'T pass context inside params object - it's a separate parameter
|
|
167
|
-
* 4. DON'T assume IDs are strings - SQLite returns numbers
|
|
168
|
-
* 5. DON'T forget to clean up intervals/timers
|
|
169
|
-
* 6. DON'T skip error cases - test both success and failure paths
|
|
170
|
-
*
|
|
171
|
-
* DEBUGGING TIPS:
|
|
172
|
-
*
|
|
173
|
-
* 1. Use console.log liberally when debugging, but remove before committing
|
|
174
|
-
* 2. Check if your auth context is properly structured
|
|
175
|
-
* 3. Validate that required fields are present in your test data
|
|
176
|
-
* 4. Remember that HTTP routes have /api/ prefix by default
|
|
177
|
-
* 5. Use debugger statements to pause execution and inspect state
|
|
178
|
-
*/
|
|
@@ -1,105 +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 { Api } from 'hooked-api'
|
|
5
|
-
|
|
6
|
-
import { RestApiPlugin, RestApiAnyapiKnexPlugin } from '../index.js'
|
|
7
|
-
import { ensureAnyApiSchema } from '../plugins/core/lib/anyapi/schema-utils.js'
|
|
8
|
-
|
|
9
|
-
const knex = knexLib({
|
|
10
|
-
client: 'better-sqlite3',
|
|
11
|
-
connection: { filename: ':memory:' },
|
|
12
|
-
useNullAsDefault: true,
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
let api
|
|
16
|
-
|
|
17
|
-
describe('AnyAPI Knex Plugin - Basic Attributes', () => {
|
|
18
|
-
before(async () => {
|
|
19
|
-
try {
|
|
20
|
-
await ensureAnyApiSchema(knex)
|
|
21
|
-
|
|
22
|
-
api = new Api({ name: 'anyapi-test', log: { level: 'warn' } })
|
|
23
|
-
await api.use(RestApiPlugin, { simplifiedApi: false, simplifiedTransport: false })
|
|
24
|
-
await api.use(RestApiAnyapiKnexPlugin, { knex })
|
|
25
|
-
|
|
26
|
-
await api.addResource('countries', {
|
|
27
|
-
schema: {
|
|
28
|
-
id: { type: 'id' },
|
|
29
|
-
name: { type: 'string', required: true },
|
|
30
|
-
code: { type: 'string', required: true },
|
|
31
|
-
},
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
if (api.resources.countries?.createKnexTable) {
|
|
35
|
-
await api.resources.countries.createKnexTable()
|
|
36
|
-
}
|
|
37
|
-
} catch (error) {
|
|
38
|
-
console.error('Setup failed', error)
|
|
39
|
-
throw error
|
|
40
|
-
}
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
after(async () => {
|
|
44
|
-
await api.release()
|
|
45
|
-
await knex.destroy()
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
beforeEach(async () => {
|
|
49
|
-
await knex('any_records').truncate()
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
it('creates and retrieves records', async () => {
|
|
53
|
-
const result = await api.resources.countries.post({
|
|
54
|
-
inputRecord: {
|
|
55
|
-
data: {
|
|
56
|
-
type: 'countries',
|
|
57
|
-
attributes: {
|
|
58
|
-
name: 'Australia',
|
|
59
|
-
code: 'AU',
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
simplified: false,
|
|
64
|
-
}).catch((error) => {
|
|
65
|
-
console.error('POST failed', error)
|
|
66
|
-
throw error
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
assert.ok(result.data.id, 'should return new id')
|
|
70
|
-
assert.equal(result.data.attributes.name, 'Australia')
|
|
71
|
-
|
|
72
|
-
const stored = await knex('any_records').first()
|
|
73
|
-
assert.equal(stored.logical_id, result.data.id)
|
|
74
|
-
assert.equal(stored.string_1, 'Australia')
|
|
75
|
-
assert.equal(stored.string_2, 'AU')
|
|
76
|
-
|
|
77
|
-
const fetched = await api.resources.countries.get({ id: result.data.id, simplified: false })
|
|
78
|
-
.catch((error) => {
|
|
79
|
-
console.error('GET failed', error)
|
|
80
|
-
throw error
|
|
81
|
-
})
|
|
82
|
-
assert.equal(fetched.data.attributes.name, 'Australia')
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
it('lists records', async () => {
|
|
86
|
-
await knex('any_records').insert([{
|
|
87
|
-
logical_id: '1',
|
|
88
|
-
tenant_id: 'default',
|
|
89
|
-
resource: 'countries',
|
|
90
|
-
string_1: 'Canada',
|
|
91
|
-
string_2: 'CA',
|
|
92
|
-
}, {
|
|
93
|
-
logical_id: '2',
|
|
94
|
-
tenant_id: 'default',
|
|
95
|
-
resource: 'countries',
|
|
96
|
-
string_1: 'Brazil',
|
|
97
|
-
string_2: 'BR',
|
|
98
|
-
}])
|
|
99
|
-
|
|
100
|
-
const result = await api.resources.countries.query({ simplified: false })
|
|
101
|
-
assert.equal(result.data.length, 2)
|
|
102
|
-
const names = result.data.map((entry) => entry.attributes.name).sort()
|
|
103
|
-
assert.deepEqual(names, ['Brazil', 'Canada'])
|
|
104
|
-
})
|
|
105
|
-
})
|
|
@@ -1,148 +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 { createPaginationApi } from './fixtures/api-configs.js'
|
|
5
|
-
import {
|
|
6
|
-
cleanTables,
|
|
7
|
-
createJsonApiDocument,
|
|
8
|
-
createRelationship,
|
|
9
|
-
resourceIdentifier,
|
|
10
|
-
} from './helpers/test-utils.js'
|
|
11
|
-
import { storageMode } from './helpers/storage-mode.js'
|
|
12
|
-
import { parseJsonApiQuery } from '../plugins/core/lib/querying-writing/connectors-query-parser.js'
|
|
13
|
-
|
|
14
|
-
const isAnyApi = storageMode.isAnyApi()
|
|
15
|
-
|
|
16
|
-
const maybeDescribe = isAnyApi ? describe : describe.skip
|
|
17
|
-
|
|
18
|
-
const parseLinkQuery = (link) => {
|
|
19
|
-
const url = new URL(link, 'https://example.test')
|
|
20
|
-
return parseJsonApiQuery(url.search.slice(1))
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
maybeDescribe('AnyAPI Cursor Pagination', () => {
|
|
24
|
-
const knex = knexLib({
|
|
25
|
-
client: 'better-sqlite3',
|
|
26
|
-
connection: { filename: ':memory:' },
|
|
27
|
-
useNullAsDefault: true,
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
let api
|
|
31
|
-
|
|
32
|
-
before(async () => {
|
|
33
|
-
api = await createPaginationApi(knex)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
after(async () => {
|
|
37
|
-
await knex.destroy()
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
beforeEach(async () => {
|
|
41
|
-
await cleanTables(knex, [
|
|
42
|
-
'pagination_countries',
|
|
43
|
-
'pagination_publishers',
|
|
44
|
-
'pagination_books',
|
|
45
|
-
])
|
|
46
|
-
|
|
47
|
-
const country = await api.resources.countries.post({
|
|
48
|
-
inputRecord: createJsonApiDocument('countries', { name: 'Cursorland', code: 'CL' }),
|
|
49
|
-
simplified: false,
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
const publisher = await api.resources.publishers.post({
|
|
53
|
-
inputRecord: createJsonApiDocument(
|
|
54
|
-
'publishers',
|
|
55
|
-
{ name: 'Cursor Press' },
|
|
56
|
-
{ country: createRelationship(resourceIdentifier('countries', country.data.id)) }
|
|
57
|
-
),
|
|
58
|
-
simplified: false,
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
const titles = ['Book A', 'Book B', 'Book C', 'Book D', 'Book E', 'Book F']
|
|
62
|
-
for (const title of titles) {
|
|
63
|
-
await api.resources.books.post({
|
|
64
|
-
inputRecord: createJsonApiDocument(
|
|
65
|
-
'books',
|
|
66
|
-
{ title },
|
|
67
|
-
{
|
|
68
|
-
country: createRelationship(resourceIdentifier('countries', country.data.id)),
|
|
69
|
-
publisher: createRelationship(resourceIdentifier('publishers', publisher.data.id)),
|
|
70
|
-
}
|
|
71
|
-
),
|
|
72
|
-
simplified: false,
|
|
73
|
-
})
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
it('returns cursor metadata and links when requesting the first page', async () => {
|
|
78
|
-
const result = await api.resources.books.query({
|
|
79
|
-
queryParams: {
|
|
80
|
-
page: { size: 2 },
|
|
81
|
-
},
|
|
82
|
-
simplified: false,
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
assert.equal(result.data.length, 2)
|
|
86
|
-
assert(result.meta?.pagination?.cursor?.next, 'Should include next cursor')
|
|
87
|
-
assert(parseLinkQuery(result.links?.next).page.after, 'Next link should include cursor parameter')
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
it('paginates forward using page[after]', async () => {
|
|
91
|
-
const firstPage = await api.resources.books.query({
|
|
92
|
-
queryParams: {
|
|
93
|
-
page: { size: 2 },
|
|
94
|
-
},
|
|
95
|
-
simplified: false,
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
const cursor = firstPage.meta.pagination.cursor.next
|
|
99
|
-
const secondPage = await api.resources.books.query({
|
|
100
|
-
queryParams: {
|
|
101
|
-
page: { size: 2, after: cursor },
|
|
102
|
-
},
|
|
103
|
-
simplified: false,
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
assert.equal(secondPage.data.length, 2)
|
|
107
|
-
const firstIds = firstPage.data.map((book) => book.id)
|
|
108
|
-
const secondIds = secondPage.data.map((book) => book.id)
|
|
109
|
-
assert(!secondIds.some((id) => firstIds.includes(id)), 'Second page should not repeat first page records')
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
it('paginates backward using page[before]', async () => {
|
|
113
|
-
const firstPage = await api.resources.books.query({
|
|
114
|
-
queryParams: {
|
|
115
|
-
page: { size: 2 },
|
|
116
|
-
},
|
|
117
|
-
simplified: false,
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
const secondPage = await api.resources.books.query({
|
|
121
|
-
queryParams: {
|
|
122
|
-
page: {
|
|
123
|
-
size: 2,
|
|
124
|
-
after: firstPage.meta.pagination.cursor.next,
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
simplified: false,
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
const beforeCursor = `id:${encodeURIComponent(secondPage.data[0].id)}`
|
|
131
|
-
|
|
132
|
-
const previousPage = await api.resources.books.query({
|
|
133
|
-
queryParams: {
|
|
134
|
-
page: {
|
|
135
|
-
size: 2,
|
|
136
|
-
before: beforeCursor,
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
simplified: false,
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
const secondIds = secondPage.data.map((book) => book.id)
|
|
143
|
-
const previousIds = previousPage.data.map((book) => book.id)
|
|
144
|
-
assert(!previousIds.some((id) => secondIds.includes(id)), 'Page[before] should return different records')
|
|
145
|
-
const firstIds = firstPage.data.map((book) => book.id)
|
|
146
|
-
assert(previousIds.every((id) => firstIds.includes(id)), 'Page[before] should return earlier records')
|
|
147
|
-
})
|
|
148
|
-
})
|