corebasic 1.0.206 → 1.0.209

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,675 @@
1
+ import { describe, test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import {matchPolicy, overlaps, contains, intersects, applySuffixPolicy} from "../policy.js";
4
+
5
+
6
+ /* ============================================================
7
+ * overlaps()
8
+ * ============================================================ */
9
+ describe("overlaps()", () => {
10
+
11
+ test("overlaps: identical ranges", () => {
12
+ assert.equal(
13
+ overlaps({ from: 10, to: 20 }, { from: 10, to: 20 }),
14
+ true
15
+ );
16
+ });
17
+
18
+ test("overlaps: partial overlap", () => {
19
+ assert.equal(
20
+ overlaps({ from: 10, to: 20 }, { from: 15, to: 25 }),
21
+ true
22
+ );
23
+ });
24
+
25
+ test("overlaps: no overlap", () => {
26
+ assert.equal(
27
+ overlaps({ from: 10, to: 20 }, { from: 21, to: 30 }),
28
+ false
29
+ );
30
+ });
31
+
32
+ test("overlaps: open ended lower", () => {
33
+ assert.equal(
34
+ overlaps({ to: 10 }, { from: 5 }),
35
+ true
36
+ );
37
+ });
38
+
39
+ test("overlaps: open ended upper", () => {
40
+ assert.equal(
41
+ overlaps({ from: 20 }, { to: 10 }),
42
+ false
43
+ );
44
+ });
45
+
46
+
47
+ describe("overlaps() boundary semantics", () => {
48
+
49
+ test("touching: open/open does not overlap", () => {
50
+ assert.equal(
51
+ overlaps(
52
+ { to: 10, toOp: "$lt" },
53
+ { from: 10, fromOp: "$gt" }
54
+ ),
55
+ false
56
+ );
57
+ });
58
+
59
+ test("touching: closed/closed overlaps", () => {
60
+ assert.equal(
61
+ overlaps(
62
+ { to: 10, toOp: "$lte" },
63
+ { from: 10, fromOp: "$gte" }
64
+ ),
65
+ true
66
+ );
67
+ });
68
+
69
+ test("touching: closed/open does not overlap", () => {
70
+ assert.equal(
71
+ overlaps(
72
+ { to: 10, toOp: "$lte" },
73
+ { from: 10, fromOp: "$gt" }
74
+ ),
75
+ false
76
+ );
77
+ });
78
+
79
+ test("touching: open/closed does not overlap", () => {
80
+ assert.equal(
81
+ overlaps(
82
+ { to: 10, toOp: "$lt" },
83
+ { from: 10, fromOp: "$gte" }
84
+ ),
85
+ false
86
+ );
87
+ });
88
+
89
+ test("touching: reverse order closed/closed overlaps", () => {
90
+ assert.equal(
91
+ overlaps(
92
+ { from: 10, fromOp: "$gte" },
93
+ { to: 10, toOp: "$lte" }
94
+ ),
95
+ true
96
+ );
97
+ });
98
+
99
+ test("touching: reverse order open/open does not overlap", () => {
100
+ assert.equal(
101
+ overlaps(
102
+ { from: 10, fromOp: "$gt" },
103
+ { to: 10, toOp: "$lt" }
104
+ ),
105
+ false
106
+ );
107
+ });
108
+
109
+ test("identical single-point closed intervals overlap", () => {
110
+ assert.equal(
111
+ overlaps(
112
+ {
113
+ from: 10,
114
+ to: 10,
115
+ fromOp: "$gte",
116
+ toOp: "$lte"
117
+ },
118
+ {
119
+ from: 10,
120
+ to: 10,
121
+ fromOp: "$gte",
122
+ toOp: "$lte"
123
+ }
124
+ ),
125
+ true
126
+ );
127
+ });
128
+
129
+ test("identical single-point open intervals do not overlap", () => {
130
+ assert.equal(
131
+ overlaps(
132
+ {
133
+ from: 10,
134
+ to: 10,
135
+ fromOp: "$gt",
136
+ toOp: "$lt"
137
+ },
138
+ {
139
+ from: 10,
140
+ to: 10,
141
+ fromOp: "$gt",
142
+ toOp: "$lt"
143
+ }
144
+ ),
145
+ false
146
+ );
147
+ });
148
+
149
+ test("infinite lower bound with touching closed endpoint overlaps", () => {
150
+ assert.equal(
151
+ overlaps(
152
+ { to: 5, toOp: "$lte" },
153
+ { from: 5, fromOp: "$gte" }
154
+ ),
155
+ true
156
+ );
157
+ });
158
+
159
+ test("infinite lower bound with touching open endpoint does not overlap", () => {
160
+ assert.equal(
161
+ overlaps(
162
+ { to: 5, toOp: "$lt" },
163
+ { from: 5, fromOp: "$gt" }
164
+ ),
165
+ false
166
+ );
167
+ });
168
+
169
+ });
170
+
171
+ });
172
+
173
+ /* ============================================================
174
+ * contains()
175
+ * ============================================================ */
176
+
177
+ describe("contains()", () => {
178
+
179
+ test("contains: inside", () => {
180
+ assert.equal(
181
+ contains({ from: 10, to: 20 }, 15),
182
+ true
183
+ );
184
+ });
185
+
186
+ test("contains: below", () => {
187
+ assert.equal(
188
+ contains({ from: 10, to: 20 }, 5),
189
+ false
190
+ );
191
+ });
192
+
193
+ test("contains: above", () => {
194
+ assert.equal(
195
+ contains({ from: 10, to: 20 }, 25),
196
+ false
197
+ );
198
+ });
199
+
200
+ test("contains: lower bound", () => {
201
+ assert.equal(
202
+ contains({ from: 10, to: 20 }, 10),
203
+ true
204
+ );
205
+ });
206
+
207
+ test("contains: upper bound", () => {
208
+ assert.equal(
209
+ contains({ from: 10, to: 20 }, 20),
210
+ true
211
+ );
212
+ });
213
+
214
+
215
+ test("contains: gt excludes lower bound", () => {
216
+ assert.equal(
217
+ contains({ from: 5, fromOp: "$gt" }, 5),
218
+ false
219
+ );
220
+ });
221
+
222
+ test("contains: gte includes lower bound", () => {
223
+ assert.equal(
224
+ contains({ from: 5, fromOp: "$gte" }, 5),
225
+ true
226
+ );
227
+ });
228
+
229
+ test("contains: lt excludes upper bound", () => {
230
+ assert.equal(
231
+ contains({ to: 10, toOp: "$lt" }, 10),
232
+ false
233
+ );
234
+ });
235
+
236
+ test("contains: lte includes upper bound", () => {
237
+ assert.equal(
238
+ contains({ to: 10, toOp: "$lte" }, 10),
239
+ true
240
+ );
241
+ });
242
+
243
+ test("contains: gt/lt excludes both endpoints", () => {
244
+ const bounds = {
245
+ from: 5,
246
+ fromOp: "$gt",
247
+ to: 10,
248
+ toOp: "$lt"
249
+ };
250
+
251
+ assert.equal(contains(bounds, 5), false);
252
+ assert.equal(contains(bounds, 10), false);
253
+ assert.equal(contains(bounds, 7), true);
254
+ });
255
+
256
+ test("contains: gte/lte includes both endpoints", () => {
257
+ const bounds = {
258
+ from: 5,
259
+ fromOp: "$gte",
260
+ to: 10,
261
+ toOp: "$lte"
262
+ };
263
+
264
+ assert.equal(contains(bounds, 5), true);
265
+ assert.equal(contains(bounds, 10), true);
266
+ assert.equal(contains(bounds, 7), true);
267
+ });
268
+
269
+ test("contains: gt/lte mixed", () => {
270
+ const bounds = {
271
+ from: 5,
272
+ fromOp: "$gt",
273
+ to: 10,
274
+ toOp: "$lte"
275
+ };
276
+
277
+ assert.equal(contains(bounds, 5), false);
278
+ assert.equal(contains(bounds, 10), true);
279
+ });
280
+
281
+ test("contains: gte/lt mixed", () => {
282
+ const bounds = {
283
+ from: 5,
284
+ fromOp: "$gte",
285
+ to: 10,
286
+ toOp: "$lt"
287
+ };
288
+
289
+ assert.equal(contains(bounds, 5), true);
290
+ assert.equal(contains(bounds, 10), false);
291
+ });
292
+
293
+ test("contains: value below lower bound", () => {
294
+ assert.equal(
295
+ contains({ from: 5, fromOp: "$gte" }, 4),
296
+ false
297
+ );
298
+ });
299
+
300
+ test("contains: value above upper bound", () => {
301
+ assert.equal(
302
+ contains({ to: 10, toOp: "$lte" }, 11),
303
+ false
304
+ );
305
+ });
306
+
307
+ test("contains: open lower bound", () => {
308
+ assert.equal(
309
+ contains({ to: 10, toOp: "$lte" }, -1000),
310
+ true
311
+ );
312
+ });
313
+
314
+ test("contains: open upper bound", () => {
315
+ assert.equal(
316
+ contains({ from: 5, fromOp: "$gte" }, 1000),
317
+ true
318
+ );
319
+ });
320
+
321
+ test("contains: completely unbounded", () => {
322
+ assert.equal(
323
+ contains({}, -100),
324
+ true
325
+ );
326
+ assert.equal(
327
+ contains({}, 100),
328
+ true
329
+ );
330
+ });
331
+
332
+ });
333
+
334
+ /* ============================================================
335
+ * intersects()
336
+ * ============================================================ */
337
+
338
+ describe("intersects()", () => {
339
+
340
+ test("intersects: value ↔ value", () => {
341
+ assert.equal(
342
+ intersects(["A"], null, ["A"], null),
343
+ true
344
+ );
345
+ });
346
+
347
+ test("intersects: value ↔ value miss", () => {
348
+ assert.equal(
349
+ intersects(["A"], null, ["B"], null),
350
+ false
351
+ );
352
+ });
353
+
354
+ test("intersects: value ↔ range", () => {
355
+ assert.equal(
356
+ intersects([10], null, [], { from: 5 }),
357
+ true
358
+ );
359
+ });
360
+
361
+ test("intersects: value ↔ range miss", () => {
362
+ assert.equal(
363
+ intersects([2], null, [], { from: 5 }),
364
+ false
365
+ );
366
+ });
367
+
368
+ test("intersects: range ↔ value", () => {
369
+ assert.equal(
370
+ intersects([], { from: 5 }, [10], null),
371
+ true
372
+ );
373
+ });
374
+
375
+ test("intersects: range ↔ value miss", () => {
376
+ assert.equal(
377
+ intersects([], { from: 5 }, [2], null),
378
+ false
379
+ );
380
+ });
381
+
382
+ test("intersects: range ↔ range", () => {
383
+ assert.equal(
384
+ intersects([], { from: 10 }, [], { from: 20 }),
385
+ true
386
+ );
387
+ });
388
+
389
+ test("intersects: range ↔ range miss", () => {
390
+ assert.equal(
391
+ intersects([], { to: 5 }, [], { from: 10 }),
392
+ false
393
+ );
394
+ });
395
+
396
+ });
397
+
398
+ /* ============================================================
399
+ * matchPolicy()
400
+ * ============================================================ */
401
+ describe("matchPolicy()", () => {
402
+
403
+ test("empty when", () => {
404
+ assert.equal(
405
+ matchPolicy({}, {}),
406
+ true
407
+ );
408
+ });
409
+
410
+ test("simple equality", () => {
411
+ assert.equal(
412
+ matchPolicy(
413
+ { status: "Active" },
414
+ { status: "Active" }
415
+ ),
416
+ true
417
+ );
418
+ });
419
+
420
+ test("simple equality fail", () => {
421
+ assert.equal(
422
+ matchPolicy(
423
+ { status: "Pending" },
424
+ { status: "Active" }
425
+ ),
426
+ false
427
+ );
428
+ });
429
+
430
+ test("$in subset", () => {
431
+ assert.equal(
432
+ matchPolicy(
433
+ { status: { $in: ["Active", "Pending"] } },
434
+ { status: { $in: ["Active"] } }
435
+ ),
436
+ true
437
+ );
438
+ });
439
+
440
+ test("$in miss", () => {
441
+ assert.equal(
442
+ matchPolicy(
443
+ { status: { $in: ["Pending"] } },
444
+ { status: { $in: ["Active"] } }
445
+ ),
446
+ false
447
+ );
448
+ });
449
+
450
+ test("value ↔ range", () => {
451
+ assert.equal(
452
+ matchPolicy(
453
+ { age: 10 },
454
+ { age: { $gt: 5 } }
455
+ ),
456
+ true
457
+ );
458
+ });
459
+
460
+ test("value ↔ range miss", () => {
461
+ assert.equal(
462
+ matchPolicy(
463
+ { age: 2 },
464
+ { age: { $gt: 5 } }
465
+ ),
466
+ false
467
+ );
468
+ });
469
+
470
+ test("range ↔ value", () => {
471
+ assert.equal(
472
+ matchPolicy(
473
+ { age: { $gt: 5 } },
474
+ { age: 10 }
475
+ ),
476
+ true
477
+ );
478
+ });
479
+
480
+ test("range ↔ value miss", () => {
481
+ assert.equal(
482
+ matchPolicy(
483
+ { age: { $lt: 5 } },
484
+ { age: 10 }
485
+ ),
486
+ false
487
+ );
488
+ });
489
+
490
+ test("range ↔ range overlap", () => {
491
+ assert.equal(
492
+ matchPolicy(
493
+ { age: { $gt: 15 } },
494
+ { age: { $gt: 10 } }
495
+ ),
496
+ true
497
+ );
498
+ });
499
+
500
+ test("range ↔ range miss", () => {
501
+ assert.equal(
502
+ matchPolicy(
503
+ { age: { $lt: 5 } },
504
+ { age: { $gt: 10 } }
505
+ ),
506
+ false
507
+ );
508
+ });
509
+
510
+ test("multiple keys", () => {
511
+ assert.equal(
512
+ matchPolicy(
513
+ {
514
+ status: "Active",
515
+ age: 20
516
+ },
517
+ {
518
+ status: "Active",
519
+ age: { $gt: 10 }
520
+ }
521
+ ),
522
+ true
523
+ );
524
+ });
525
+
526
+ test("multiple keys fail", () => {
527
+ assert.equal(
528
+ matchPolicy(
529
+ {
530
+ status: "Pending",
531
+ age: 20
532
+ },
533
+ {
534
+ status: "Active",
535
+ age: { $gt: 10 }
536
+ }
537
+ ),
538
+ false
539
+ );
540
+ });
541
+
542
+ test("$and flatten", () => {
543
+ assert.equal(
544
+ matchPolicy(
545
+ {
546
+ status: "Active",
547
+ region: "IN"
548
+ },
549
+ {
550
+ $and: [
551
+ { status: "Active" },
552
+ { region: "IN" }
553
+ ]
554
+ }
555
+ ),
556
+ true
557
+ );
558
+ });
559
+
560
+ test("$and flatten fail", () => {
561
+ assert.equal(
562
+ matchPolicy(
563
+ {
564
+ status: "Active",
565
+ region: "US"
566
+ },
567
+ {
568
+ $and: [
569
+ { status: "Active" },
570
+ { region: "IN" }
571
+ ]
572
+ }
573
+ ),
574
+ false
575
+ );
576
+ });
577
+
578
+ test("$or merge semantics", () => {
579
+ assert.equal(
580
+ matchPolicy(
581
+ {
582
+ status: {
583
+ $in: ["Active", "Pending"]
584
+ }
585
+ },
586
+ {
587
+ $or: [
588
+ { status: "Active" },
589
+ { status: "Pending" }
590
+ ]
591
+ }
592
+ ),
593
+ true
594
+ );
595
+ });
596
+
597
+ test("$ne throws", () => {
598
+ assert.throws(() => {
599
+ matchPolicy(
600
+ { status: "Deleted" },
601
+ {
602
+ status: {
603
+ $ne: "Deleted"
604
+ }
605
+ }
606
+ );
607
+ });
608
+ });
609
+
610
+ test("$nin throws", () => {
611
+ assert.throws(() => {
612
+ matchPolicy(
613
+ { status: "Deleted" },
614
+ {
615
+ status: {
616
+ $nin: ["Deleted"]
617
+ }
618
+ }
619
+ );
620
+ });
621
+ });
622
+
623
+ test("$not throws", () => {
624
+ assert.throws(() => {
625
+ matchPolicy(
626
+ { age: 10 },
627
+ {
628
+ age: {
629
+ $not: {
630
+ $gt: 5
631
+ }
632
+ }
633
+ }
634
+ );
635
+ });
636
+ });
637
+
638
+ test("conservative planner: identical lt", () => {
639
+ assert.equal(
640
+ matchPolicy(
641
+ { age: { $lt: 10 } },
642
+ { age: { $lt: 10 } }
643
+ ),
644
+ true
645
+ );
646
+ });
647
+
648
+ test("conservative planner: identical gt", () => {
649
+ assert.equal(
650
+ matchPolicy(
651
+ { age: { $gt: 10 } },
652
+ { age: { $gt: 10 } }
653
+ ),
654
+ true
655
+ );
656
+ });
657
+
658
+ test("mixed value + range", () => {
659
+ assert.equal(
660
+ matchPolicy(
661
+ {
662
+ age: {
663
+ $in: [10],
664
+ $gt: 20
665
+ }
666
+ },
667
+ {
668
+ age: 10
669
+ }
670
+ ),
671
+ true
672
+ );
673
+ });
674
+
675
+ });