core-services-sdk 1.3.62 → 1.3.64
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/.claude/settings.local.json +22 -0
- package/package.json +1 -1
- package/src/core/normalize-phone-number.js +144 -25
- package/src/postgresql/apply-filter.js +141 -15
- package/tests/core/normalize-phone-number.unit.test.js +45 -0
- package/tests/postgresql/apply-filter-snake-case.integration.test.js +549 -0
- package/tests/postgresql/apply-filter.integration.test.js +56 -325
- package/tests/postgresql/paginate.integration.test.js +2 -2
- package/tests/postgresql/validate-schema.integration.test.js +2 -2
- package/types/core/normalize-phone-number.d.ts +97 -27
- package/types/postgresql/apply-filter.d.ts +231 -0
- package/types/postgresql/index.d.ts +1 -0
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { describe, it, beforeAll, afterAll, beforeEach, expect } from 'vitest'
|
|
3
|
+
import knex from 'knex'
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
stopPostgres,
|
|
7
|
+
startPostgres,
|
|
8
|
+
buildPostgresUri,
|
|
9
|
+
} from '../../src/postgresql/start-stop-postgres-docker.js'
|
|
10
|
+
|
|
11
|
+
import { applyFilterSnakeCase } from '../../src/postgresql/apply-filter.js'
|
|
12
|
+
|
|
13
|
+
const PG_OPTIONS = {
|
|
14
|
+
port: 5444,
|
|
15
|
+
containerName: 'postgres-apply-filter-test-5444',
|
|
16
|
+
user: 'testuser',
|
|
17
|
+
pass: 'testpass',
|
|
18
|
+
db: 'testdb',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const DATABASE_URI = buildPostgresUri(PG_OPTIONS)
|
|
22
|
+
|
|
23
|
+
let db
|
|
24
|
+
|
|
25
|
+
beforeAll(async () => {
|
|
26
|
+
startPostgres(PG_OPTIONS)
|
|
27
|
+
|
|
28
|
+
db = knex({
|
|
29
|
+
client: 'pg',
|
|
30
|
+
connection: DATABASE_URI,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
await db.schema.createTable('assets', (table) => {
|
|
34
|
+
table.uuid('id').primary()
|
|
35
|
+
table.string('name').notNullable()
|
|
36
|
+
table.string('status')
|
|
37
|
+
table.string('type')
|
|
38
|
+
table.decimal('price', 10, 2)
|
|
39
|
+
table.timestamp('created_at').notNullable()
|
|
40
|
+
table.timestamp('deleted_at')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
await db.schema.createTable('invoices', (table) => {
|
|
44
|
+
table.uuid('id').primary()
|
|
45
|
+
table.string('invoice_number').notNullable()
|
|
46
|
+
table.decimal('amount', 10, 2).notNullable()
|
|
47
|
+
table.string('status')
|
|
48
|
+
table.uuid('customer_id')
|
|
49
|
+
table.timestamp('created_at').notNullable()
|
|
50
|
+
table.timestamp('paid_at')
|
|
51
|
+
table.timestamp('deleted_at')
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
afterAll(async () => {
|
|
56
|
+
if (db) {
|
|
57
|
+
await db.destroy()
|
|
58
|
+
}
|
|
59
|
+
stopPostgres(PG_OPTIONS.containerName)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
beforeEach(async () => {
|
|
63
|
+
await db('assets').truncate()
|
|
64
|
+
await db('invoices').truncate()
|
|
65
|
+
|
|
66
|
+
await db('assets').insert([
|
|
67
|
+
{
|
|
68
|
+
id: '00000000-0000-0000-0000-000000000001',
|
|
69
|
+
name: 'Asset One',
|
|
70
|
+
status: 'active',
|
|
71
|
+
type: 'invoice',
|
|
72
|
+
price: 100.0,
|
|
73
|
+
created_at: new Date('2024-01-01'),
|
|
74
|
+
deleted_at: null,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: '00000000-0000-0000-0000-000000000002',
|
|
78
|
+
name: 'Asset Two',
|
|
79
|
+
status: 'active',
|
|
80
|
+
type: 'receipt',
|
|
81
|
+
price: 200.0,
|
|
82
|
+
created_at: new Date('2024-01-02'),
|
|
83
|
+
deleted_at: null,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: '00000000-0000-0000-0000-000000000003',
|
|
87
|
+
name: 'Asset Three',
|
|
88
|
+
status: 'pending',
|
|
89
|
+
type: 'invoice',
|
|
90
|
+
price: 150.0,
|
|
91
|
+
created_at: new Date('2024-01-03'),
|
|
92
|
+
deleted_at: null,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: '00000000-0000-0000-0000-000000000004',
|
|
96
|
+
name: 'Deleted Asset',
|
|
97
|
+
status: 'deleted',
|
|
98
|
+
type: 'receipt',
|
|
99
|
+
price: 50.0,
|
|
100
|
+
created_at: new Date('2024-01-04'),
|
|
101
|
+
deleted_at: new Date('2024-01-05'),
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: '00000000-0000-0000-0000-000000000005',
|
|
105
|
+
name: 'Expensive Asset',
|
|
106
|
+
status: 'active',
|
|
107
|
+
type: 'invoice',
|
|
108
|
+
price: 500.0,
|
|
109
|
+
created_at: new Date('2024-01-05'),
|
|
110
|
+
deleted_at: null,
|
|
111
|
+
},
|
|
112
|
+
])
|
|
113
|
+
|
|
114
|
+
await db('invoices').insert([
|
|
115
|
+
{
|
|
116
|
+
id: '00000000-0000-0000-0000-000000000101',
|
|
117
|
+
invoice_number: 'INV-001',
|
|
118
|
+
amount: 1000.0,
|
|
119
|
+
status: 'paid',
|
|
120
|
+
customer_id: '00000000-0000-0000-0000-000000000201',
|
|
121
|
+
created_at: new Date('2024-01-01'),
|
|
122
|
+
paid_at: new Date('2024-01-02'),
|
|
123
|
+
deleted_at: null,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: '00000000-0000-0000-0000-000000000102',
|
|
127
|
+
invoice_number: 'INV-002',
|
|
128
|
+
amount: 2500.0,
|
|
129
|
+
status: 'pending',
|
|
130
|
+
customer_id: '00000000-0000-0000-0000-000000000202',
|
|
131
|
+
created_at: new Date('2024-01-02'),
|
|
132
|
+
paid_at: null,
|
|
133
|
+
deleted_at: null,
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: '00000000-0000-0000-0000-000000000103',
|
|
137
|
+
invoice_number: 'INV-003',
|
|
138
|
+
amount: 500.0,
|
|
139
|
+
status: 'paid',
|
|
140
|
+
customer_id: '00000000-0000-0000-0000-000000000201',
|
|
141
|
+
created_at: new Date('2024-01-03'),
|
|
142
|
+
paid_at: new Date('2024-01-04'),
|
|
143
|
+
deleted_at: null,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: '00000000-0000-0000-0000-000000000104',
|
|
147
|
+
invoice_number: 'INV-004',
|
|
148
|
+
amount: 3000.0,
|
|
149
|
+
status: 'overdue',
|
|
150
|
+
customer_id: '00000000-0000-0000-0000-000000000203',
|
|
151
|
+
created_at: new Date('2024-01-04'),
|
|
152
|
+
paid_at: null,
|
|
153
|
+
deleted_at: null,
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: '00000000-0000-0000-0000-000000000105',
|
|
157
|
+
invoice_number: 'INV-005',
|
|
158
|
+
amount: 750.0,
|
|
159
|
+
status: 'cancelled',
|
|
160
|
+
customer_id: '00000000-0000-0000-0000-000000000201',
|
|
161
|
+
created_at: new Date('2024-01-05'),
|
|
162
|
+
paid_at: null,
|
|
163
|
+
deleted_at: new Date('2024-01-06'),
|
|
164
|
+
},
|
|
165
|
+
])
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
describe('applyFilterSnakeCase integration', () => {
|
|
169
|
+
it('applies simple equality filter (eq)', async () => {
|
|
170
|
+
const query = db('assets').select('*')
|
|
171
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
172
|
+
query,
|
|
173
|
+
filter: { status: 'active' },
|
|
174
|
+
tableName: 'assets',
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
const results = await filteredQuery
|
|
178
|
+
|
|
179
|
+
expect(results).toHaveLength(3)
|
|
180
|
+
expect(results.every((r) => r.status === 'active')).toBe(true)
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('converts camelCase keys to snake_case', async () => {
|
|
184
|
+
const query = db('assets').select('*')
|
|
185
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
186
|
+
query,
|
|
187
|
+
filter: { deletedAt: { isNull: true }, name: 'Asset One' },
|
|
188
|
+
tableName: 'assets',
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
const results = await filteredQuery
|
|
192
|
+
|
|
193
|
+
expect(results).toHaveLength(1)
|
|
194
|
+
expect(results[0].name).toBe('Asset One')
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('applies not equal filter (ne)', async () => {
|
|
198
|
+
const query = db('assets').select('*')
|
|
199
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
200
|
+
query,
|
|
201
|
+
filter: { status: { ne: 'deleted' } },
|
|
202
|
+
tableName: 'assets',
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
const results = await filteredQuery
|
|
206
|
+
|
|
207
|
+
expect(results).toHaveLength(4)
|
|
208
|
+
expect(results.every((r) => r.status !== 'deleted')).toBe(true)
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('applies not equal filter (neq alias)', async () => {
|
|
212
|
+
const query = db('assets').select('*')
|
|
213
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
214
|
+
query,
|
|
215
|
+
filter: { status: { neq: 'deleted' } },
|
|
216
|
+
tableName: 'assets',
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
const results = await filteredQuery
|
|
220
|
+
|
|
221
|
+
expect(results).toHaveLength(4)
|
|
222
|
+
expect(results.every((r) => r.status !== 'deleted')).toBe(true)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('applies IN filter with array directly', async () => {
|
|
226
|
+
const query = db('assets').select('*')
|
|
227
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
228
|
+
query,
|
|
229
|
+
filter: { status: ['active', 'pending'] },
|
|
230
|
+
tableName: 'assets',
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
const results = await filteredQuery
|
|
234
|
+
|
|
235
|
+
expect(results).toHaveLength(4)
|
|
236
|
+
expect(
|
|
237
|
+
results.every((r) => r.status === 'active' || r.status === 'pending'),
|
|
238
|
+
).toBe(true)
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
it('applies IN filter with operator', async () => {
|
|
242
|
+
const query = db('assets').select('*')
|
|
243
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
244
|
+
query,
|
|
245
|
+
filter: { status: { in: ['active', 'pending'] } },
|
|
246
|
+
tableName: 'assets',
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
const results = await filteredQuery
|
|
250
|
+
|
|
251
|
+
expect(results).toHaveLength(4)
|
|
252
|
+
expect(
|
|
253
|
+
results.every((r) => r.status === 'active' || r.status === 'pending'),
|
|
254
|
+
).toBe(true)
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('applies NOT IN filter (nin)', async () => {
|
|
258
|
+
const query = db('assets').select('*')
|
|
259
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
260
|
+
query,
|
|
261
|
+
filter: { status: { nin: ['deleted', 'archived'] } },
|
|
262
|
+
tableName: 'assets',
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
const results = await filteredQuery
|
|
266
|
+
|
|
267
|
+
expect(results).toHaveLength(4)
|
|
268
|
+
expect(
|
|
269
|
+
results.every((r) => r.status !== 'deleted' && r.status !== 'archived'),
|
|
270
|
+
).toBe(true)
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
it('applies greater than filter (gt)', async () => {
|
|
274
|
+
const query = db('assets').select('*')
|
|
275
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
276
|
+
query,
|
|
277
|
+
filter: { price: { gt: 150 } },
|
|
278
|
+
tableName: 'assets',
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
const results = await filteredQuery
|
|
282
|
+
|
|
283
|
+
expect(results).toHaveLength(2)
|
|
284
|
+
expect(results.every((r) => parseFloat(r.price) > 150)).toBe(true)
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
it('applies greater than or equal filter (gte)', async () => {
|
|
288
|
+
const query = db('assets').select('*')
|
|
289
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
290
|
+
query,
|
|
291
|
+
filter: { price: { gte: 150 } },
|
|
292
|
+
tableName: 'assets',
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
const results = await filteredQuery
|
|
296
|
+
|
|
297
|
+
expect(results).toHaveLength(3)
|
|
298
|
+
expect(results.every((r) => parseFloat(r.price) >= 150)).toBe(true)
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
it('applies less than filter (lt)', async () => {
|
|
302
|
+
const query = db('assets').select('*')
|
|
303
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
304
|
+
query,
|
|
305
|
+
filter: { price: { lt: 150 } },
|
|
306
|
+
tableName: 'assets',
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
const results = await filteredQuery
|
|
310
|
+
|
|
311
|
+
expect(results).toHaveLength(2)
|
|
312
|
+
expect(results.every((r) => parseFloat(r.price) < 150)).toBe(true)
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
it('applies less than or equal filter (lte)', async () => {
|
|
316
|
+
const query = db('assets').select('*')
|
|
317
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
318
|
+
query,
|
|
319
|
+
filter: { price: { lte: 150 } },
|
|
320
|
+
tableName: 'assets',
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
const results = await filteredQuery
|
|
324
|
+
|
|
325
|
+
expect(results).toHaveLength(3)
|
|
326
|
+
expect(results.every((r) => parseFloat(r.price) <= 150)).toBe(true)
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
it('applies range filter with gte and lte', async () => {
|
|
330
|
+
const query = db('assets').select('*')
|
|
331
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
332
|
+
query,
|
|
333
|
+
filter: { price: { gte: 100, lte: 200 } },
|
|
334
|
+
tableName: 'assets',
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
const results = await filteredQuery
|
|
338
|
+
|
|
339
|
+
expect(results).toHaveLength(3)
|
|
340
|
+
expect(
|
|
341
|
+
results.every(
|
|
342
|
+
(r) => parseFloat(r.price) >= 100 && parseFloat(r.price) <= 200,
|
|
343
|
+
),
|
|
344
|
+
).toBe(true)
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
it('applies case-sensitive LIKE filter', async () => {
|
|
348
|
+
const query = db('assets').select('*')
|
|
349
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
350
|
+
query,
|
|
351
|
+
filter: { name: { like: '%Asset%' } },
|
|
352
|
+
tableName: 'assets',
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
const results = await filteredQuery
|
|
356
|
+
|
|
357
|
+
expect(results.length).toBeGreaterThan(0)
|
|
358
|
+
expect(results.every((r) => r.name.includes('Asset'))).toBe(true)
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
it('applies case-insensitive ILIKE filter', async () => {
|
|
362
|
+
const query = db('assets').select('*')
|
|
363
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
364
|
+
query,
|
|
365
|
+
filter: { name: { ilike: '%asset%' } },
|
|
366
|
+
tableName: 'assets',
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
const results = await filteredQuery
|
|
370
|
+
|
|
371
|
+
expect(results.length).toBeGreaterThan(0)
|
|
372
|
+
expect(
|
|
373
|
+
results.every((r) =>
|
|
374
|
+
r.name.toLowerCase().includes('asset'.toLowerCase()),
|
|
375
|
+
),
|
|
376
|
+
).toBe(true)
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
it('applies isNull filter when value is true', async () => {
|
|
380
|
+
const query = db('assets').select('*')
|
|
381
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
382
|
+
query,
|
|
383
|
+
filter: { deletedAt: { isNull: true } },
|
|
384
|
+
tableName: 'assets',
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
const results = await filteredQuery
|
|
388
|
+
|
|
389
|
+
expect(results).toHaveLength(4)
|
|
390
|
+
expect(results.every((r) => r.deleted_at === null)).toBe(true)
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
it('applies isNotNull filter when isNull value is false', async () => {
|
|
394
|
+
const query = db('assets').select('*')
|
|
395
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
396
|
+
query,
|
|
397
|
+
filter: { deletedAt: { isNull: false } },
|
|
398
|
+
tableName: 'assets',
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
const results = await filteredQuery
|
|
402
|
+
|
|
403
|
+
expect(results).toHaveLength(1)
|
|
404
|
+
expect(results[0].deleted_at).not.toBe(null)
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
it('applies isNotNull filter when value is true', async () => {
|
|
408
|
+
const query = db('assets').select('*')
|
|
409
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
410
|
+
query,
|
|
411
|
+
filter: { deletedAt: { isNotNull: true } },
|
|
412
|
+
tableName: 'assets',
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
const results = await filteredQuery
|
|
416
|
+
|
|
417
|
+
expect(results).toHaveLength(1)
|
|
418
|
+
expect(results[0].deleted_at).not.toBe(null)
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
it('applies isNull filter when isNotNull value is false', async () => {
|
|
422
|
+
const query = db('assets').select('*')
|
|
423
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
424
|
+
query,
|
|
425
|
+
filter: { deletedAt: { isNotNull: false } },
|
|
426
|
+
tableName: 'assets',
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
const results = await filteredQuery
|
|
430
|
+
|
|
431
|
+
expect(results).toHaveLength(4)
|
|
432
|
+
expect(results.every((r) => r.deleted_at === null)).toBe(true)
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
it('applies multiple filters together', async () => {
|
|
436
|
+
const query = db('assets').select('*')
|
|
437
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
438
|
+
query,
|
|
439
|
+
filter: {
|
|
440
|
+
status: 'active',
|
|
441
|
+
type: 'invoice',
|
|
442
|
+
price: { gte: 100 },
|
|
443
|
+
},
|
|
444
|
+
tableName: 'assets',
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
const results = await filteredQuery
|
|
448
|
+
|
|
449
|
+
expect(results).toHaveLength(2)
|
|
450
|
+
expect(results.every((r) => r.status === 'active')).toBe(true)
|
|
451
|
+
expect(results.every((r) => r.type === 'invoice')).toBe(true)
|
|
452
|
+
expect(results.every((r) => parseFloat(r.price) >= 100)).toBe(true)
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
it('returns empty results when filter matches nothing', async () => {
|
|
456
|
+
const query = db('assets').select('*')
|
|
457
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
458
|
+
query,
|
|
459
|
+
filter: { status: 'non-existing' },
|
|
460
|
+
tableName: 'assets',
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
const results = await filteredQuery
|
|
464
|
+
|
|
465
|
+
expect(results).toHaveLength(0)
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
it('uses qualified column names with tableName', async () => {
|
|
469
|
+
const query = db('assets').select('assets.*')
|
|
470
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
471
|
+
query,
|
|
472
|
+
filter: { status: 'active' },
|
|
473
|
+
tableName: 'assets',
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
const results = await filteredQuery
|
|
477
|
+
|
|
478
|
+
expect(results).toHaveLength(3)
|
|
479
|
+
expect(results.every((r) => r.status === 'active')).toBe(true)
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
it('ignores unknown operators', async () => {
|
|
483
|
+
const query = db('assets').select('*')
|
|
484
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
485
|
+
query,
|
|
486
|
+
filter: { status: { unknownOperator: 'value' } },
|
|
487
|
+
tableName: 'assets',
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
const results = await filteredQuery
|
|
491
|
+
|
|
492
|
+
// Should return all records since unknown operator is ignored
|
|
493
|
+
expect(results).toHaveLength(5)
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
it('works with invoices table', async () => {
|
|
497
|
+
const query = db('invoices').select('*')
|
|
498
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
499
|
+
query,
|
|
500
|
+
filter: { status: 'paid', deletedAt: { isNull: true } },
|
|
501
|
+
tableName: 'invoices',
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
const results = await filteredQuery
|
|
505
|
+
|
|
506
|
+
expect(results).toHaveLength(2)
|
|
507
|
+
expect(results.every((r) => r.status === 'paid')).toBe(true)
|
|
508
|
+
expect(results.every((r) => r.deleted_at === null)).toBe(true)
|
|
509
|
+
})
|
|
510
|
+
|
|
511
|
+
it('works with invoices table using camelCase conversion', async () => {
|
|
512
|
+
const query = db('invoices').select('*')
|
|
513
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
514
|
+
query,
|
|
515
|
+
filter: {
|
|
516
|
+
customerId: '00000000-0000-0000-0000-000000000201',
|
|
517
|
+
amount: { gte: 500 },
|
|
518
|
+
},
|
|
519
|
+
tableName: 'invoices',
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
const results = await filteredQuery
|
|
523
|
+
|
|
524
|
+
expect(results).toHaveLength(3)
|
|
525
|
+
expect(
|
|
526
|
+
results.every(
|
|
527
|
+
(r) =>
|
|
528
|
+
r.customer_id === '00000000-0000-0000-0000-000000000201' &&
|
|
529
|
+
parseFloat(r.amount) >= 500,
|
|
530
|
+
),
|
|
531
|
+
).toBe(true)
|
|
532
|
+
})
|
|
533
|
+
|
|
534
|
+
it('works with invoices table using IN filter', async () => {
|
|
535
|
+
const query = db('invoices').select('*')
|
|
536
|
+
const filteredQuery = applyFilterSnakeCase({
|
|
537
|
+
query,
|
|
538
|
+
filter: { status: ['paid', 'pending'] },
|
|
539
|
+
tableName: 'invoices',
|
|
540
|
+
})
|
|
541
|
+
|
|
542
|
+
const results = await filteredQuery
|
|
543
|
+
|
|
544
|
+
expect(results).toHaveLength(3)
|
|
545
|
+
expect(
|
|
546
|
+
results.every((r) => r.status === 'paid' || r.status === 'pending'),
|
|
547
|
+
).toBe(true)
|
|
548
|
+
})
|
|
549
|
+
})
|