@jskit-ai/crud-core 0.1.63 → 0.1.65

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.
@@ -1,7 +1,9 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
+ import { createSchema } from "json-rest-schema";
3
4
  import {
4
- cursorPaginationQueryValidator
5
+ cursorPaginationQueryValidator,
6
+ validateSchemaPayload
5
7
  } from "@jskit-ai/kernel/shared/validators";
6
8
  import { compileRouteValidator } from "@jskit-ai/kernel/_testable";
7
9
  import {
@@ -12,10 +14,48 @@ import {
12
14
  resolveCrudParentFilterKeys
13
15
  } from "../src/server/listQueryValidators.js";
14
16
 
15
- test("listSearchQueryValidator normalizes q", () => {
16
- const normalized = listSearchQueryValidator.normalize({
17
- q: " ani "
17
+ function composeSchemaDefinition(...definitions) {
18
+ return Object.freeze({
19
+ schema: createSchema(
20
+ Object.assign({}, ...definitions.map((definition) => definition.schema.getFieldDefinitions()))
21
+ ),
22
+ mode: "patch"
18
23
  });
24
+ }
25
+
26
+ function createCrudResource({
27
+ viewFields = {},
28
+ createFields = {},
29
+ patchFields = {}
30
+ } = {}) {
31
+ return {
32
+ operations: {
33
+ view: {
34
+ output: {
35
+ schema: createSchema(viewFields),
36
+ mode: "replace"
37
+ }
38
+ },
39
+ create: {
40
+ body: {
41
+ schema: createSchema(createFields),
42
+ mode: "create"
43
+ }
44
+ },
45
+ patch: {
46
+ body: {
47
+ schema: createSchema(patchFields),
48
+ mode: "patch"
49
+ }
50
+ }
51
+ }
52
+ };
53
+ }
54
+
55
+ test("listSearchQueryValidator normalizes q", async () => {
56
+ const normalized = await validateSchemaPayload(listSearchQueryValidator, {
57
+ q: " ani "
58
+ }, { phase: "input" });
19
59
 
20
60
  assert.deepEqual(normalized, {
21
61
  q: "ani"
@@ -24,16 +64,16 @@ test("listSearchQueryValidator normalizes q", () => {
24
64
 
25
65
  test("listSearchQueryValidator keeps q optional when merged with pagination query validator", () => {
26
66
  const compiled = compileRouteValidator({
27
- queryValidator: [cursorPaginationQueryValidator, listSearchQueryValidator]
67
+ query: composeSchemaDefinition(cursorPaginationQueryValidator, listSearchQueryValidator)
28
68
  });
29
69
 
30
70
  assert.deepEqual(compiled.schema.querystring.required || [], []);
31
71
  });
32
72
 
33
- test("lookupIncludeQueryValidator normalizes include", () => {
34
- const normalized = lookupIncludeQueryValidator.normalize({
73
+ test("lookupIncludeQueryValidator normalizes include", async () => {
74
+ const normalized = await validateSchemaPayload(lookupIncludeQueryValidator, {
35
75
  include: " vetId,ownerId "
36
- });
76
+ }, { phase: "input" });
37
77
 
38
78
  assert.deepEqual(normalized, {
39
79
  include: "vetId,ownerId"
@@ -42,7 +82,7 @@ test("lookupIncludeQueryValidator normalizes include", () => {
42
82
 
43
83
  test("lookupIncludeQueryValidator keeps include optional when merged with pagination and search", () => {
44
84
  const compiled = compileRouteValidator({
45
- queryValidator: [cursorPaginationQueryValidator, listSearchQueryValidator, lookupIncludeQueryValidator]
85
+ query: composeSchemaDefinition(cursorPaginationQueryValidator, listSearchQueryValidator, lookupIncludeQueryValidator)
46
86
  });
47
87
 
48
88
  assert.deepEqual(compiled.schema.querystring.required || [], []);
@@ -56,120 +96,117 @@ test("createCrudCursorPaginationQueryValidator keeps numeric cursor validation f
56
96
 
57
97
  test("createCrudCursorPaginationQueryValidator allows opaque cursor strings for ordered lists", () => {
58
98
  const validator = createCrudCursorPaginationQueryValidator({
59
- orderBy: [
60
- {
61
- column: "created_at",
62
- direction: "desc"
63
- }
64
- ]
99
+ orderBy: ["-createdAt"]
65
100
  });
66
101
 
67
102
  assert.notEqual(validator, cursorPaginationQueryValidator);
68
- assert.deepEqual(validator.normalize({ cursor: " offset:3 ", limit: "25" }), {
103
+ const normalized = validateSchemaPayload(validator, { cursor: " offset:3 ", limit: "25" }, { phase: "input" });
104
+ assert.deepEqual(normalized, {
69
105
  cursor: "offset:3",
70
106
  limit: 25
71
107
  });
72
108
  });
73
109
 
74
110
  test("resolveCrudParentFilterKeys returns lookup keys that exist in create schema", () => {
75
- const resource = {
76
- operations: {
77
- create: {
78
- bodyValidator: {
79
- schema: {
80
- type: "object",
81
- properties: {
82
- contactId: { type: "integer" },
83
- name: { type: "string" },
84
- vetId: { type: "integer" }
85
- }
86
- }
87
- }
88
- }
89
- },
90
- fieldMeta: [
91
- {
92
- key: "contactId",
111
+ const resource = createCrudResource({
112
+ viewFields: {
113
+ contactId: {
114
+ type: "integer",
93
115
  relation: {
94
116
  kind: "lookup",
95
117
  apiPath: "/contacts",
96
118
  valueKey: "id"
97
119
  }
98
120
  },
99
- {
100
- key: "vetId",
121
+ vetId: {
122
+ type: "integer",
101
123
  relation: {
102
124
  kind: "lookup",
103
125
  apiPath: "/vets",
104
126
  valueKey: "id"
105
127
  }
106
128
  },
107
- {
108
- key: "ignoredLookup",
129
+ ignoredLookup: {
130
+ type: "integer",
109
131
  relation: {
110
132
  kind: "lookup",
111
133
  apiPath: "/ignored",
112
134
  valueKey: "id"
113
135
  }
114
136
  }
115
- ]
116
- };
137
+ },
138
+ createFields: {
139
+ contactId: {
140
+ type: "integer",
141
+ relation: {
142
+ kind: "lookup",
143
+ apiPath: "/contacts",
144
+ valueKey: "id"
145
+ }
146
+ },
147
+ name: { type: "string" },
148
+ vetId: {
149
+ type: "integer",
150
+ relation: {
151
+ kind: "lookup",
152
+ apiPath: "/vets",
153
+ valueKey: "id"
154
+ }
155
+ }
156
+ }
157
+ });
117
158
 
118
159
  assert.deepEqual(resolveCrudParentFilterKeys(resource), ["contactId", "vetId"]);
119
160
  });
120
161
 
121
- test("createCrudParentFilterQueryValidator normalizes configured parent filters", () => {
122
- const validator = createCrudParentFilterQueryValidator({
123
- operations: {
124
- create: {
125
- bodyValidator: {
126
- schema: {
127
- type: "object",
128
- properties: {
129
- contactId: { type: "integer" }
130
- }
131
- }
162
+ test("createCrudParentFilterQueryValidator normalizes configured parent filters", async () => {
163
+ const validator = createCrudParentFilterQueryValidator(createCrudResource({
164
+ viewFields: {
165
+ contactId: {
166
+ type: "integer",
167
+ relation: {
168
+ kind: "lookup",
169
+ apiPath: "/contacts",
170
+ valueKey: "id"
132
171
  }
133
172
  }
134
173
  },
135
- fieldMeta: [
136
- {
137
- key: "contactId",
174
+ createFields: {
175
+ contactId: {
176
+ type: "integer",
138
177
  relation: {
139
178
  kind: "lookup",
140
179
  apiPath: "/contacts",
141
180
  valueKey: "id"
142
181
  }
143
182
  }
144
- ]
145
- });
183
+ }
184
+ }));
146
185
 
147
- const normalized = validator.normalize({
148
- contactId: " 42 ",
149
- unknown: "x"
150
- });
186
+ const normalized = await validateSchemaPayload(validator, {
187
+ contactId: " 42 "
188
+ }, { phase: "input" });
151
189
  assert.deepEqual(normalized, {
152
190
  contactId: "42"
153
191
  });
154
192
  });
155
193
 
156
- test("createCrudParentFilterQueryValidator keeps canonical field keys when fieldMeta declares parent route aliases", () => {
157
- const validator = createCrudParentFilterQueryValidator({
158
- operations: {
159
- create: {
160
- bodyValidator: {
161
- schema: {
162
- type: "object",
163
- properties: {
164
- staffContactId: { type: "integer" }
165
- }
166
- }
194
+ test("createCrudParentFilterQueryValidator keeps canonical field keys when schema declares parent route aliases", async () => {
195
+ const validator = createCrudParentFilterQueryValidator(createCrudResource({
196
+ viewFields: {
197
+ staffContactId: {
198
+ type: "integer",
199
+ parentRouteParamKey: "contactId",
200
+ relation: {
201
+ kind: "lookup",
202
+ apiPath: "/contacts",
203
+ valueKey: "id"
167
204
  }
168
205
  }
169
206
  },
170
- fieldMeta: [
171
- {
172
- key: "staffContactId",
207
+ createFields: {
208
+ staffContactId: {
209
+ type: "integer",
173
210
  parentRouteParamKey: "contactId",
174
211
  relation: {
175
212
  kind: "lookup",
@@ -177,46 +214,43 @@ test("createCrudParentFilterQueryValidator keeps canonical field keys when field
177
214
  valueKey: "id"
178
215
  }
179
216
  }
180
- ]
181
- });
217
+ }
218
+ }));
182
219
 
183
- assert.deepEqual(Object.keys(validator.schema.properties), ["staffContactId"]);
184
- assert.deepEqual(validator.normalize({
185
- staffContactId: " 42 ",
186
- contactId: " 99 "
187
- }), {
220
+ assert.deepEqual(Object.keys(validator.schema.getFieldDefinitions()), ["staffContactId"]);
221
+ assert.deepEqual(await validateSchemaPayload(validator, {
222
+ staffContactId: " 42 "
223
+ }, { phase: "input" }), {
188
224
  staffContactId: "42"
189
225
  });
190
226
  });
191
227
 
192
228
  test("createCrudParentFilterQueryValidator keeps parent filters optional when merged", () => {
193
- const parentValidator = createCrudParentFilterQueryValidator({
194
- operations: {
195
- create: {
196
- bodyValidator: {
197
- schema: {
198
- type: "object",
199
- properties: {
200
- contactId: { type: "integer" }
201
- }
202
- }
229
+ const parentValidator = createCrudParentFilterQueryValidator(createCrudResource({
230
+ viewFields: {
231
+ contactId: {
232
+ type: "integer",
233
+ relation: {
234
+ kind: "lookup",
235
+ apiPath: "/contacts",
236
+ valueKey: "id"
203
237
  }
204
238
  }
205
239
  },
206
- fieldMeta: [
207
- {
208
- key: "contactId",
240
+ createFields: {
241
+ contactId: {
242
+ type: "integer",
209
243
  relation: {
210
244
  kind: "lookup",
211
245
  apiPath: "/contacts",
212
246
  valueKey: "id"
213
247
  }
214
248
  }
215
- ]
216
- });
249
+ }
250
+ }));
217
251
 
218
252
  const compiled = compileRouteValidator({
219
- queryValidator: [cursorPaginationQueryValidator, listSearchQueryValidator, parentValidator]
253
+ query: composeSchemaDefinition(cursorPaginationQueryValidator, listSearchQueryValidator, parentValidator)
220
254
  });
221
255
  assert.deepEqual(compiled.schema.querystring.required || [], []);
222
256
  });