dynamo-query-engine 1.0.1 → 1.0.2

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.
@@ -0,0 +1,350 @@
1
+ /**
2
+ * @fileoverview Tests for validation utilities
3
+ */
4
+
5
+ import { describe, it, expect } from "vitest";
6
+ import {
7
+ validateSingleSort,
8
+ validateFilterField,
9
+ validateSortField,
10
+ validateFilterOperator,
11
+ validateExpandField,
12
+ validatePaginationModel,
13
+ } from "../../src/utils/validation.js";
14
+
15
+ describe("Validation Utils", () => {
16
+ describe("validateSingleSort", () => {
17
+ it("should allow empty sort model", () => {
18
+ expect(() => {
19
+ validateSingleSort([]);
20
+ }).not.toThrow();
21
+ });
22
+
23
+ it("should allow null sort model", () => {
24
+ expect(() => {
25
+ validateSingleSort(null);
26
+ }).not.toThrow();
27
+ });
28
+
29
+ it("should allow undefined sort model", () => {
30
+ expect(() => {
31
+ validateSingleSort(undefined);
32
+ }).not.toThrow();
33
+ });
34
+
35
+ it("should allow single sort field", () => {
36
+ const sortModel = [{ field: "createdAt", sort: "desc" }];
37
+
38
+ expect(() => {
39
+ validateSingleSort(sortModel);
40
+ }).not.toThrow();
41
+ });
42
+
43
+ it("should throw error for multiple sort fields", () => {
44
+ const sortModel = [
45
+ { field: "createdAt", sort: "desc" },
46
+ { field: "status", sort: "asc" },
47
+ ];
48
+
49
+ expect(() => {
50
+ validateSingleSort(sortModel);
51
+ }).toThrow("Multiple sort fields are not allowed");
52
+ });
53
+
54
+ it("should mention DynamoDB limitation in error", () => {
55
+ const sortModel = [
56
+ { field: "field1", sort: "asc" },
57
+ { field: "field2", sort: "desc" },
58
+ ];
59
+
60
+ expect(() => {
61
+ validateSingleSort(sortModel);
62
+ }).toThrow(/DynamoDB/);
63
+ });
64
+ });
65
+
66
+ describe("validateFilterField", () => {
67
+ const gridConfig = {
68
+ status: {
69
+ filter: {
70
+ type: "key",
71
+ operators: ["eq"],
72
+ },
73
+ },
74
+ createdAt: {
75
+ filter: {
76
+ type: "key",
77
+ operators: ["gte", "lte"],
78
+ },
79
+ },
80
+ };
81
+
82
+ it("should allow filtering on configured field", () => {
83
+ expect(() => {
84
+ validateFilterField("status", gridConfig);
85
+ }).not.toThrow();
86
+ });
87
+
88
+ it("should throw error for non-configured field", () => {
89
+ expect(() => {
90
+ validateFilterField("name", gridConfig);
91
+ }).toThrow("Filtering not allowed on field 'name'");
92
+ });
93
+
94
+ it("should throw error for field without filter config", () => {
95
+ const configWithoutFilter = {
96
+ name: {
97
+ sort: { type: "key" },
98
+ },
99
+ };
100
+
101
+ expect(() => {
102
+ validateFilterField("name", configWithoutFilter);
103
+ }).toThrow("Filtering not allowed");
104
+ });
105
+
106
+ it("should provide helpful error message", () => {
107
+ expect(() => {
108
+ validateFilterField("invalidField", gridConfig);
109
+ }).toThrow(/grid\.filter config/);
110
+ });
111
+ });
112
+
113
+ describe("validateSortField", () => {
114
+ const gridConfig = {
115
+ createdAt: {
116
+ sort: { type: "key" },
117
+ },
118
+ updatedAt: {
119
+ sort: { type: "key" },
120
+ },
121
+ };
122
+
123
+ it("should allow sorting on configured field", () => {
124
+ expect(() => {
125
+ validateSortField("createdAt", gridConfig);
126
+ }).not.toThrow();
127
+ });
128
+
129
+ it("should throw error for non-configured field", () => {
130
+ expect(() => {
131
+ validateSortField("name", gridConfig);
132
+ }).toThrow("Sorting not allowed on field 'name'");
133
+ });
134
+
135
+ it("should throw error for field without sort config", () => {
136
+ const configWithoutSort = {
137
+ name: {
138
+ filter: { type: "key", operators: ["eq"] },
139
+ },
140
+ };
141
+
142
+ expect(() => {
143
+ validateSortField("name", configWithoutSort);
144
+ }).toThrow("Sorting not allowed");
145
+ });
146
+
147
+ it("should provide helpful error message", () => {
148
+ expect(() => {
149
+ validateSortField("invalidField", gridConfig);
150
+ }).toThrow(/grid\.sort config/);
151
+ });
152
+ });
153
+
154
+ describe("validateFilterOperator", () => {
155
+ const gridConfig = {
156
+ status: {
157
+ filter: {
158
+ type: "key",
159
+ operators: ["eq"],
160
+ },
161
+ },
162
+ createdAt: {
163
+ filter: {
164
+ type: "key",
165
+ operators: ["gte", "lte", "between"],
166
+ },
167
+ },
168
+ };
169
+
170
+ it("should allow configured operator", () => {
171
+ expect(() => {
172
+ validateFilterOperator("status", "eq", gridConfig);
173
+ }).not.toThrow();
174
+ });
175
+
176
+ it("should allow multiple configured operators", () => {
177
+ expect(() => {
178
+ validateFilterOperator("createdAt", "gte", gridConfig);
179
+ }).not.toThrow();
180
+
181
+ expect(() => {
182
+ validateFilterOperator("createdAt", "lte", gridConfig);
183
+ }).not.toThrow();
184
+
185
+ expect(() => {
186
+ validateFilterOperator("createdAt", "between", gridConfig);
187
+ }).not.toThrow();
188
+ });
189
+
190
+ it("should throw error for non-allowed operator", () => {
191
+ expect(() => {
192
+ validateFilterOperator("status", "gte", gridConfig);
193
+ }).toThrow("Operator 'gte' not allowed for field 'status'");
194
+ });
195
+
196
+ it("should throw error for field without filter config", () => {
197
+ expect(() => {
198
+ validateFilterOperator("nonexistent", "eq", gridConfig);
199
+ }).toThrow("does not have filter configuration");
200
+ });
201
+
202
+ it("should list allowed operators in error message", () => {
203
+ expect(() => {
204
+ validateFilterOperator("status", "contains", gridConfig);
205
+ }).toThrow(/Allowed operators: eq/);
206
+ });
207
+ });
208
+
209
+ describe("validateExpandField", () => {
210
+ const gridConfig = {
211
+ teamIds: {
212
+ expand: {
213
+ type: "Team",
214
+ relation: "MANY",
215
+ defaultLimit: 5,
216
+ maxLimit: 20,
217
+ },
218
+ },
219
+ ownerId: {
220
+ expand: {
221
+ type: "User",
222
+ relation: "ONE",
223
+ defaultLimit: 1,
224
+ maxLimit: 1,
225
+ },
226
+ },
227
+ };
228
+
229
+ it("should allow expanding configured field", () => {
230
+ expect(() => {
231
+ validateExpandField("teamIds", gridConfig);
232
+ }).not.toThrow();
233
+ });
234
+
235
+ it("should throw error for non-configured field", () => {
236
+ expect(() => {
237
+ validateExpandField("status", gridConfig);
238
+ }).toThrow("Expand not allowed on field 'status'");
239
+ });
240
+
241
+ it("should throw error for field without expand config", () => {
242
+ const configWithoutExpand = {
243
+ name: {
244
+ filter: { type: "key", operators: ["eq"] },
245
+ },
246
+ };
247
+
248
+ expect(() => {
249
+ validateExpandField("name", configWithoutExpand);
250
+ }).toThrow("Expand not allowed");
251
+ });
252
+
253
+ it("should provide helpful error message", () => {
254
+ expect(() => {
255
+ validateExpandField("invalidField", gridConfig);
256
+ }).toThrow(/grid\.expand config/);
257
+ });
258
+ });
259
+
260
+ describe("validatePaginationModel", () => {
261
+ it("should allow valid pagination model", () => {
262
+ expect(() => {
263
+ validatePaginationModel({ pageSize: 20 });
264
+ }).not.toThrow();
265
+ });
266
+
267
+ it("should allow pageSize of 1", () => {
268
+ expect(() => {
269
+ validatePaginationModel({ pageSize: 1 });
270
+ }).not.toThrow();
271
+ });
272
+
273
+ it("should allow pageSize up to 1000", () => {
274
+ expect(() => {
275
+ validatePaginationModel({ pageSize: 1000 });
276
+ }).not.toThrow();
277
+ });
278
+
279
+ it("should throw error when paginationModel is missing", () => {
280
+ expect(() => {
281
+ validatePaginationModel(null);
282
+ }).toThrow("paginationModel is required");
283
+ });
284
+
285
+ it("should throw error when pageSize is missing", () => {
286
+ expect(() => {
287
+ validatePaginationModel({});
288
+ }).toThrow("pageSize must be a positive number");
289
+ });
290
+
291
+ it("should throw error when pageSize is zero", () => {
292
+ expect(() => {
293
+ validatePaginationModel({ pageSize: 0 });
294
+ }).toThrow("pageSize must be a positive number");
295
+ });
296
+
297
+ it("should throw error when pageSize is negative", () => {
298
+ expect(() => {
299
+ validatePaginationModel({ pageSize: -5 });
300
+ }).toThrow("pageSize must be a positive number");
301
+ });
302
+
303
+ it("should throw error when pageSize exceeds 1000", () => {
304
+ expect(() => {
305
+ validatePaginationModel({ pageSize: 1001 });
306
+ }).toThrow("pageSize cannot exceed 1000");
307
+ });
308
+
309
+ it("should mention DynamoDB limit in error", () => {
310
+ expect(() => {
311
+ validatePaginationModel({ pageSize: 2000 });
312
+ }).toThrow(/DynamoDB limit/);
313
+ });
314
+ });
315
+
316
+ describe("Edge Cases", () => {
317
+ it("should handle empty grid config", () => {
318
+ const emptyConfig = {};
319
+
320
+ expect(() => {
321
+ validateFilterField("anyField", emptyConfig);
322
+ }).toThrow();
323
+
324
+ expect(() => {
325
+ validateSortField("anyField", emptyConfig);
326
+ }).toThrow();
327
+ });
328
+
329
+ it("should handle config with mixed field types", () => {
330
+ const mixedConfig = {
331
+ field1: {
332
+ filter: { type: "key", operators: ["eq"] },
333
+ },
334
+ field2: {
335
+ sort: { type: "key" },
336
+ },
337
+ field3: {
338
+ expand: { type: "Model", relation: "ONE", defaultLimit: 1, maxLimit: 1 },
339
+ },
340
+ };
341
+
342
+ expect(() => validateFilterField("field1", mixedConfig)).not.toThrow();
343
+ expect(() => validateSortField("field2", mixedConfig)).not.toThrow();
344
+ expect(() => validateExpandField("field3", mixedConfig)).not.toThrow();
345
+
346
+ expect(() => validateFilterField("field2", mixedConfig)).toThrow();
347
+ expect(() => validateSortField("field1", mixedConfig)).toThrow();
348
+ });
349
+ });
350
+ });
@@ -0,0 +1,20 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: "node",
7
+ coverage: {
8
+ provider: "v8",
9
+ reporter: ["text", "json", "html"],
10
+ include: ["src/**/*.js"],
11
+ exclude: ["src/types/**", "examples/**", "tests/**"],
12
+ thresholds: {
13
+ lines: 50,
14
+ functions: 50,
15
+ branches: 50,
16
+ statements: 50,
17
+ },
18
+ },
19
+ },
20
+ });