core-services-sdk 1.3.63 → 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.
@@ -1,17 +1,19 @@
1
+ // apply-filter.integration.test.js
1
2
  // @ts-nocheck
3
+
2
4
  import { describe, it, beforeAll, afterAll, beforeEach, expect } from 'vitest'
3
5
  import knex from 'knex'
4
6
 
5
7
  import {
6
- stopPostgres,
7
8
  startPostgres,
9
+ stopPostgres,
8
10
  buildPostgresUri,
9
11
  } from '../../src/postgresql/start-stop-postgres-docker.js'
10
12
 
11
13
  import { applyFilter } from '../../src/postgresql/apply-filter.js'
12
14
 
13
15
  const PG_OPTIONS = {
14
- port: 5444,
16
+ port: 5443,
15
17
  containerName: 'postgres-apply-filter-test',
16
18
  user: 'testuser',
17
19
  pass: 'testpass',
@@ -69,7 +71,7 @@ beforeEach(async () => {
69
71
  name: 'Asset One',
70
72
  status: 'active',
71
73
  type: 'invoice',
72
- price: 100.0,
74
+ price: 100,
73
75
  created_at: new Date('2024-01-01'),
74
76
  deleted_at: null,
75
77
  },
@@ -78,7 +80,7 @@ beforeEach(async () => {
78
80
  name: 'Asset Two',
79
81
  status: 'active',
80
82
  type: 'receipt',
81
- price: 200.0,
83
+ price: 200,
82
84
  created_at: new Date('2024-01-02'),
83
85
  deleted_at: null,
84
86
  },
@@ -87,7 +89,7 @@ beforeEach(async () => {
87
89
  name: 'Asset Three',
88
90
  status: 'pending',
89
91
  type: 'invoice',
90
- price: 150.0,
92
+ price: 150,
91
93
  created_at: new Date('2024-01-03'),
92
94
  deleted_at: null,
93
95
  },
@@ -96,7 +98,7 @@ beforeEach(async () => {
96
98
  name: 'Deleted Asset',
97
99
  status: 'deleted',
98
100
  type: 'receipt',
99
- price: 50.0,
101
+ price: 50,
100
102
  created_at: new Date('2024-01-04'),
101
103
  deleted_at: new Date('2024-01-05'),
102
104
  },
@@ -105,7 +107,7 @@ beforeEach(async () => {
105
107
  name: 'Expensive Asset',
106
108
  status: 'active',
107
109
  type: 'invoice',
108
- price: 500.0,
110
+ price: 500,
109
111
  created_at: new Date('2024-01-05'),
110
112
  deleted_at: null,
111
113
  },
@@ -115,7 +117,7 @@ beforeEach(async () => {
115
117
  {
116
118
  id: '00000000-0000-0000-0000-000000000101',
117
119
  invoice_number: 'INV-001',
118
- amount: 1000.0,
120
+ amount: 1000,
119
121
  status: 'paid',
120
122
  customer_id: '00000000-0000-0000-0000-000000000201',
121
123
  created_at: new Date('2024-01-01'),
@@ -125,7 +127,7 @@ beforeEach(async () => {
125
127
  {
126
128
  id: '00000000-0000-0000-0000-000000000102',
127
129
  invoice_number: 'INV-002',
128
- amount: 2500.0,
130
+ amount: 2500,
129
131
  status: 'pending',
130
132
  customer_id: '00000000-0000-0000-0000-000000000202',
131
133
  created_at: new Date('2024-01-02'),
@@ -135,7 +137,7 @@ beforeEach(async () => {
135
137
  {
136
138
  id: '00000000-0000-0000-0000-000000000103',
137
139
  invoice_number: 'INV-003',
138
- amount: 500.0,
140
+ amount: 500,
139
141
  status: 'paid',
140
142
  customer_id: '00000000-0000-0000-0000-000000000201',
141
143
  created_at: new Date('2024-01-03'),
@@ -145,7 +147,7 @@ beforeEach(async () => {
145
147
  {
146
148
  id: '00000000-0000-0000-0000-000000000104',
147
149
  invoice_number: 'INV-004',
148
- amount: 3000.0,
150
+ amount: 3000,
149
151
  status: 'overdue',
150
152
  customer_id: '00000000-0000-0000-0000-000000000203',
151
153
  created_at: new Date('2024-01-04'),
@@ -155,7 +157,7 @@ beforeEach(async () => {
155
157
  {
156
158
  id: '00000000-0000-0000-0000-000000000105',
157
159
  invoice_number: 'INV-005',
158
- amount: 750.0,
160
+ amount: 750,
159
161
  status: 'cancelled',
160
162
  customer_id: '00000000-0000-0000-0000-000000000201',
161
163
  created_at: new Date('2024-01-05'),
@@ -165,277 +167,70 @@ beforeEach(async () => {
165
167
  ])
166
168
  })
167
169
 
168
- describe('applyFilter integration', () => {
169
- it('applies simple equality filter (eq)', async () => {
170
- const query = db('assets').select('*')
171
- const filteredQuery = applyFilter({
172
- query,
170
+ describe('applyFilter integration (snake_case only)', () => {
171
+ it('applies equality filter', async () => {
172
+ const results = await applyFilter({
173
+ query: db('assets').select('*'),
173
174
  filter: { status: 'active' },
174
175
  tableName: 'assets',
175
176
  })
176
177
 
177
- const results = await filteredQuery
178
-
179
178
  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 = applyFilter({
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
179
  })
196
180
 
197
- it('applies not equal filter (ne)', async () => {
198
- const query = db('assets').select('*')
199
- const filteredQuery = applyFilter({
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 = applyFilter({
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 = applyFilter({
228
- query,
181
+ it('applies IN filter', async () => {
182
+ const results = await applyFilter({
183
+ query: db('assets').select('*'),
229
184
  filter: { status: ['active', 'pending'] },
230
185
  tableName: 'assets',
231
186
  })
232
187
 
233
- const results = await filteredQuery
234
-
235
188
  expect(results).toHaveLength(4)
236
- expect(
237
- results.every((r) => r.status === 'active' || r.status === 'pending'),
238
- ).toBe(true)
239
189
  })
240
190
 
241
- it('applies IN filter with operator', async () => {
242
- const query = db('assets').select('*')
243
- const filteredQuery = applyFilter({
244
- query,
245
- filter: { status: { in: ['active', 'pending'] } },
191
+ it('applies NOT IN filter', async () => {
192
+ const results = await applyFilter({
193
+ query: db('assets').select('*'),
194
+ filter: { status: { nin: ['deleted'] } },
246
195
  tableName: 'assets',
247
196
  })
248
197
 
249
- const results = await filteredQuery
250
-
251
198
  expect(results).toHaveLength(4)
252
- expect(
253
- results.every((r) => r.status === 'active' || r.status === 'pending'),
254
- ).toBe(true)
255
199
  })
256
200
 
257
- it('applies NOT IN filter (nin)', async () => {
258
- const query = db('assets').select('*')
259
- const filteredQuery = applyFilter({
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 = applyFilter({
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 = applyFilter({
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 = applyFilter({
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 = applyFilter({
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 = applyFilter({
332
- query,
201
+ it('applies numeric range filter', async () => {
202
+ const results = await applyFilter({
203
+ query: db('assets').select('*'),
333
204
  filter: { price: { gte: 100, lte: 200 } },
334
205
  tableName: 'assets',
335
206
  })
336
207
 
337
- const results = await filteredQuery
338
-
339
208
  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 = applyFilter({
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
209
  })
360
210
 
361
- it('applies case-insensitive ILIKE filter', async () => {
362
- const query = db('assets').select('*')
363
- const filteredQuery = applyFilter({
364
- query,
365
- filter: { name: { ilike: '%asset%' } },
211
+ it('applies isNull filter', async () => {
212
+ const results = await applyFilter({
213
+ query: db('assets').select('*'),
214
+ filter: { deleted_at: { isNull: true } },
366
215
  tableName: 'assets',
367
216
  })
368
217
 
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 = applyFilter({
382
- query,
383
- filter: { deletedAt: { isNull: true } },
384
- tableName: 'assets',
385
- })
386
-
387
- const results = await filteredQuery
388
-
389
218
  expect(results).toHaveLength(4)
390
- expect(results.every((r) => r.deleted_at === null)).toBe(true)
391
219
  })
392
220
 
393
- it('applies isNotNull filter when isNull value is false', async () => {
394
- const query = db('assets').select('*')
395
- const filteredQuery = applyFilter({
396
- query,
397
- filter: { deletedAt: { isNull: false } },
221
+ it('applies isNotNull filter', async () => {
222
+ const results = await applyFilter({
223
+ query: db('assets').select('*'),
224
+ filter: { deleted_at: { isNotNull: true } },
398
225
  tableName: 'assets',
399
226
  })
400
227
 
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 = applyFilter({
410
- query,
411
- filter: { deletedAt: { isNotNull: true } },
412
- tableName: 'assets',
413
- })
414
-
415
- const results = await filteredQuery
416
-
417
228
  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 = applyFilter({
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
229
  })
434
230
 
435
231
  it('applies multiple filters together', async () => {
436
- const query = db('assets').select('*')
437
- const filteredQuery = applyFilter({
438
- query,
232
+ const results = await applyFilter({
233
+ query: db('assets').select('*'),
439
234
  filter: {
440
235
  status: 'active',
441
236
  type: 'invoice',
@@ -444,106 +239,42 @@ describe('applyFilter integration', () => {
444
239
  tableName: 'assets',
445
240
  })
446
241
 
447
- const results = await filteredQuery
448
-
449
242
  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 = applyFilter({
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 = applyFilter({
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 = applyFilter({
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
243
  })
495
244
 
496
245
  it('works with invoices table', async () => {
497
- const query = db('invoices').select('*')
498
- const filteredQuery = applyFilter({
499
- query,
500
- filter: { status: 'paid', deletedAt: { isNull: true } },
246
+ const results = await applyFilter({
247
+ query: db('invoices').select('*'),
248
+ filter: {
249
+ status: 'paid',
250
+ deleted_at: { isNull: true },
251
+ },
501
252
  tableName: 'invoices',
502
253
  })
503
254
 
504
- const results = await filteredQuery
505
-
506
255
  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
256
  })
510
257
 
511
- it('works with invoices table using camelCase conversion', async () => {
512
- const query = db('invoices').select('*')
513
- const filteredQuery = applyFilter({
514
- query,
258
+ it('works with invoices using snake_case foreign key', async () => {
259
+ const results = await applyFilter({
260
+ query: db('invoices').select('*'),
515
261
  filter: {
516
- customerId: '00000000-0000-0000-0000-000000000201',
262
+ customer_id: '00000000-0000-0000-0000-000000000201',
517
263
  amount: { gte: 500 },
518
264
  },
519
265
  tableName: 'invoices',
520
266
  })
521
267
 
522
- const results = await filteredQuery
523
-
524
268
  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
269
  })
533
270
 
534
- it('works with invoices table using IN filter', async () => {
535
- const query = db('invoices').select('*')
536
- const filteredQuery = applyFilter({
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)
271
+ it('throws when using camelCase keys (no automatic conversion)', async () => {
272
+ await expect(
273
+ applyFilter({
274
+ query: db('assets').select('*'),
275
+ filter: { deletedAt: { isNull: true } },
276
+ tableName: 'assets',
277
+ }),
278
+ ).rejects.toThrow(/column .*deletedAt.* does not exist/i)
548
279
  })
549
280
  })
@@ -11,8 +11,8 @@ import {
11
11
  import { sqlPaginate } from '../../src/postgresql/paginate.js'
12
12
 
13
13
  const PG_OPTIONS = {
14
- port: 5443,
15
- containerName: 'postgres-paginate-test',
14
+ port: 5442,
15
+ containerName: 'postgres-paginate-test-5442',
16
16
  user: 'testuser',
17
17
  pass: 'testpass',
18
18
  db: 'testdb',
@@ -9,8 +9,8 @@ import { connectToPg } from '../../src/postgresql/connect-to-pg.js'
9
9
  import { validateSchema } from '../../src/postgresql/validate-schema.js'
10
10
 
11
11
  const PG_OPTIONS = {
12
- port: 5433,
13
- containerName: 'postgres-validate-schema-test',
12
+ port: 5431,
13
+ containerName: 'postgres-validate-schema-test-5431',
14
14
  user: 'testuser',
15
15
  pass: 'testpass',
16
16
  db: 'testdb',