@mastra/qdrant 0.11.8 → 0.11.9-alpha.0

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,857 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import type { QdrantVectorFilter } from './filter';
3
- import { QdrantFilterTranslator } from './filter';
4
-
5
- describe('QdrantFilterTranslator', () => {
6
- const translator = new QdrantFilterTranslator();
7
-
8
- describe('Basic Operators', () => {
9
- it('should translate direct value match', () => {
10
- const filter: QdrantVectorFilter = { field: 'value' };
11
- const expected = { must: [{ key: 'field', match: { value: 'value' } }] };
12
- expect(translator.translate(filter)).toEqual(expected);
13
- });
14
-
15
- it('should translate primitive values', () => {
16
- expect(translator.translate({ field: 123 })).toEqual({ must: [{ key: 'field', match: { value: 123 } }] });
17
- expect(translator.translate({ field: true })).toEqual({ must: [{ key: 'field', match: { value: true } }] });
18
- expect(translator.translate({ field: null })).toEqual({ must: [{ is_null: { key: 'field' } }] });
19
- });
20
-
21
- it('should handle special numeric values', () => {
22
- expect(translator.translate({ field: 0 })).toEqual({ must: [{ key: 'field', match: { value: 0 } }] });
23
- expect(translator.translate({ field: -0 })).toEqual({ must: [{ key: 'field', match: { value: 0 } }] });
24
- expect(translator.translate({ field: Number.MAX_SAFE_INTEGER })).toEqual({
25
- must: [{ key: 'field', match: { value: Number.MAX_SAFE_INTEGER } }],
26
- });
27
- expect(translator.translate({ field: Number.MIN_SAFE_INTEGER })).toEqual({
28
- must: [{ key: 'field', match: { value: Number.MIN_SAFE_INTEGER } }],
29
- });
30
- });
31
-
32
- it('should translate comparison operators', () => {
33
- const filters = {
34
- eq: { field: { $eq: 'value' } },
35
- ne: { field: { $ne: 'value' } },
36
- gt: { field: { $gt: 100 } },
37
- gte: { field: { $gte: 100 } },
38
- lt: { field: { $lt: 100 } },
39
- lte: { field: { $lte: 100 } },
40
- };
41
-
42
- expect(translator.translate(filters.eq)).toEqual({ must: [{ key: 'field', match: { value: 'value' } }] });
43
- expect(translator.translate(filters.ne)).toEqual({ must: [{ key: 'field', match: { except: ['value'] } }] });
44
- expect(translator.translate(filters.gt)).toEqual({ must: [{ key: 'field', range: { gt: 100 } }] });
45
- expect(translator.translate(filters.gte)).toEqual({ must: [{ key: 'field', range: { gte: 100 } }] });
46
- expect(translator.translate(filters.lt)).toEqual({ must: [{ key: 'field', range: { lt: 100 } }] });
47
- expect(translator.translate(filters.lte)).toEqual({ must: [{ key: 'field', range: { lte: 100 } }] });
48
- });
49
-
50
- it('should translate array operators', () => {
51
- const filters = {
52
- in: { field: { $in: [1, 2, 3] } },
53
- nin: { field: { $nin: [1, 2, 3] } },
54
- };
55
-
56
- expect(translator.translate(filters.in)).toEqual({ must: [{ key: 'field', match: { any: [1, 2, 3] } }] });
57
- expect(translator.translate(filters.nin)).toEqual({ must: [{ key: 'field', match: { except: [1, 2, 3] } }] });
58
- });
59
-
60
- it('should handle empty arrays', () => {
61
- expect(translator.translate({ field: [] })).toEqual({ must: [{ is_empty: { key: 'field' } }] });
62
- expect(translator.translate({ field: { $in: [] } })).toEqual({ must: [{ key: 'field', match: { any: [] } }] });
63
- });
64
-
65
- it('should handle multiple comparison operators on same field', () => {
66
- const filter: QdrantVectorFilter = { field: { $gt: 10, $lt: 20 } };
67
- const expected = { must: [{ key: 'field', range: { gt: 10, lt: 20 } }] };
68
- expect(translator.translate(filter)).toEqual(expected);
69
- });
70
- });
71
-
72
- describe('Logical Operators', () => {
73
- it('should translate $and operator', () => {
74
- const filter: QdrantVectorFilter = {
75
- $and: [{ field1: 'value1' }, { field2: { $gt: 100 } }],
76
- };
77
-
78
- const expected = {
79
- must: [
80
- { key: 'field1', match: { value: 'value1' } },
81
- { key: 'field2', range: { gt: 100 } },
82
- ],
83
- };
84
-
85
- expect(translator.translate(filter)).toEqual(expected);
86
- });
87
-
88
- it('should translate $or operator', () => {
89
- const filter: QdrantVectorFilter = {
90
- $or: [{ field1: 'value1' }, { field2: { $lt: 100 } }],
91
- };
92
-
93
- const expected = {
94
- should: [
95
- { key: 'field1', match: { value: 'value1' } },
96
- { key: 'field2', range: { lt: 100 } },
97
- ],
98
- };
99
-
100
- expect(translator.translate(filter)).toEqual(expected);
101
- });
102
-
103
- it('should translate $not operator', () => {
104
- const filter: QdrantVectorFilter = {
105
- $not: { field: 'value' },
106
- };
107
-
108
- const expected = {
109
- must_not: [{ key: 'field', match: { value: 'value' } }],
110
- };
111
-
112
- expect(translator.translate(filter)).toEqual(expected);
113
- });
114
-
115
- it('should handle nested logical operators', () => {
116
- const filter: QdrantVectorFilter = {
117
- $and: [
118
- { 'user.age': { $gte: 18 } },
119
- {
120
- $or: [{ 'user.country': { $in: ['US', 'CA'] } }, { 'user.verified': true }],
121
- },
122
- {
123
- $not: { 'user.banned': true },
124
- },
125
- ],
126
- };
127
-
128
- const expected = {
129
- must: [
130
- { key: 'user.age', range: { gte: 18 } },
131
- {
132
- should: [
133
- { key: 'user.country', match: { any: ['US', 'CA'] } },
134
- { key: 'user.verified', match: { value: true } },
135
- ],
136
- },
137
- {
138
- must_not: [{ key: 'user.banned', match: { value: true } }],
139
- },
140
- ],
141
- };
142
-
143
- expect(translator.translate(filter)).toEqual(expected);
144
- });
145
-
146
- it('should handle empty logical operators', () => {
147
- expect(translator.translate({ $and: [] })).toEqual({ must: [] });
148
- expect(translator.translate({ $or: [] })).toEqual({ should: [] });
149
- });
150
-
151
- it('should handle single condition in logical operators', () => {
152
- expect(translator.translate({ $and: [{ field: 'value' }] })).toEqual({
153
- must: [{ key: 'field', match: { value: 'value' } }],
154
- });
155
- });
156
-
157
- it('should handle nested must_not operators', () => {
158
- const filter: QdrantVectorFilter = {
159
- $not: {
160
- $not: { field: 'value' },
161
- },
162
- };
163
- const expected = {
164
- must_not: [
165
- {
166
- must_not: [{ key: 'field', match: { value: 'value' } }],
167
- },
168
- ],
169
- };
170
- expect(translator.translate(filter)).toEqual(expected);
171
- });
172
-
173
- it('should handle complex logical combinations with ranges', () => {
174
- const filter: QdrantVectorFilter = {
175
- $or: [
176
- { $and: [{ price: { $gte: 100, $lt: 200 } }, { stock: { $gt: 0 } }] },
177
- { $and: [{ price: { $lt: 100 } }, { featured: true }] },
178
- ],
179
- };
180
- const expected = {
181
- should: [
182
- {
183
- must: [
184
- { key: 'price', range: { gte: 100, lt: 200 } },
185
- { key: 'stock', range: { gt: 0 } },
186
- ],
187
- },
188
- {
189
- must: [
190
- { key: 'price', range: { lt: 100 } },
191
- { key: 'featured', match: { value: true } },
192
- ],
193
- },
194
- ],
195
- };
196
- expect(translator.translate(filter)).toEqual(expected);
197
- });
198
- });
199
-
200
- describe('Custom Operators', () => {
201
- it('should translate $count operator', () => {
202
- const filter: QdrantVectorFilter = { field: { $count: { $gt: 5 } } };
203
- const expected = { must: [{ key: 'field', values_count: { gt: 5 } }] };
204
- expect(translator.translate(filter)).toEqual(expected);
205
- });
206
-
207
- it('should translate $geo operator with radius', () => {
208
- const filter: QdrantVectorFilter = {
209
- location: {
210
- $geo: {
211
- type: 'radius',
212
- center: { lat: 52.5, lon: 13.4 },
213
- radius: 1000,
214
- },
215
- },
216
- };
217
- const expected = {
218
- must: [
219
- {
220
- key: 'location',
221
- geo_radius: {
222
- center: { lat: 52.5, lon: 13.4 },
223
- radius: 1000,
224
- },
225
- },
226
- ],
227
- };
228
- expect(translator.translate(filter)).toEqual(expected);
229
- });
230
-
231
- it('should translate $geo operator with bounding box', () => {
232
- const filter: QdrantVectorFilter = {
233
- location: {
234
- $geo: {
235
- type: 'box',
236
- top_left: { lat: 52.5, lon: 13.4 },
237
- bottom_right: { lat: 52.4, lon: 13.5 },
238
- },
239
- },
240
- };
241
-
242
- const expected = {
243
- must: [
244
- {
245
- key: 'location',
246
- geo_bounding_box: {
247
- top_left: { lat: 52.5, lon: 13.4 },
248
- bottom_right: { lat: 52.4, lon: 13.5 },
249
- },
250
- },
251
- ],
252
- };
253
-
254
- expect(translator.translate(filter)).toEqual(expected);
255
- });
256
-
257
- it('should translate $geo operator with polygon', () => {
258
- const filter: QdrantVectorFilter = {
259
- location: {
260
- $geo: {
261
- type: 'polygon',
262
- exterior: { points: [{ lat: 52.5, lon: 13.4 }] },
263
- interiors: [],
264
- },
265
- },
266
- };
267
-
268
- const expected = {
269
- must: [
270
- {
271
- key: 'location',
272
- geo_polygon: {
273
- exterior: { points: [{ lat: 52.5, lon: 13.4 }] },
274
- interiors: [],
275
- },
276
- },
277
- ],
278
- };
279
-
280
- expect(translator.translate(filter)).toEqual(expected);
281
- });
282
-
283
- it('should translate $hasId operator', () => {
284
- expect(translator.translate({ $hasId: '123' })).toEqual({ must: [{ has_id: ['123'] }] });
285
- expect(translator.translate({ $hasId: ['123', '456'] })).toEqual({ must: [{ has_id: ['123', '456'] }] });
286
- });
287
-
288
- it('should translate $nested operator', () => {
289
- const filter: QdrantVectorFilter = {
290
- diet: {
291
- $nested: {
292
- food: 'meat',
293
- likes: true,
294
- },
295
- },
296
- $hasId: '123',
297
- };
298
-
299
- const expected = {
300
- must: [
301
- {
302
- nested: {
303
- key: 'diet',
304
- filter: {
305
- must: [
306
- { key: 'food', match: { value: 'meat' } },
307
- { key: 'likes', match: { value: true } },
308
- ],
309
- },
310
- },
311
- },
312
- { has_id: ['123'] },
313
- ],
314
- };
315
-
316
- expect(translator.translate(filter)).toEqual(expected);
317
- });
318
-
319
- it('should translate $hasVector operator', () => {
320
- expect(translator.translate({ $hasVector: 'vector_field' })).toEqual({ must: [{ has_vector: 'vector_field' }] });
321
- });
322
-
323
- it('should translate $datetime operator', () => {
324
- const now = new Date();
325
- const filter: QdrantVectorFilter = {
326
- timestamp: {
327
- $datetime: {
328
- range: {
329
- gt: now,
330
- lt: new Date(now.getTime() + 86400000),
331
- },
332
- },
333
- },
334
- };
335
-
336
- const expected = {
337
- must: [
338
- {
339
- key: 'timestamp',
340
- range: {
341
- gt: now.toISOString(),
342
- lt: new Date(now.getTime() + 86400000).toISOString(),
343
- },
344
- },
345
- ],
346
- };
347
-
348
- expect(translator.translate(filter)).toEqual(expected);
349
- });
350
-
351
- it('should translate $null operator', () => {
352
- expect(translator.translate({ field: { $null: true } })).toEqual({ must: [{ is_null: { key: 'field' } }] });
353
- });
354
-
355
- it('should translate $empty operator', () => {
356
- expect(translator.translate({ field: { $empty: true } })).toEqual({ must: [{ is_empty: { key: 'field' } }] });
357
- });
358
-
359
- it('should handle nested $count with multiple conditions', () => {
360
- const filter: QdrantVectorFilter = { 'array.items': { $count: { $gt: 5, $lt: 10 } } };
361
- const expected = {
362
- must: [
363
- {
364
- key: 'array.items',
365
- values_count: { gt: 5, lt: 10 },
366
- },
367
- ],
368
- };
369
- expect(translator.translate(filter)).toEqual(expected);
370
- });
371
-
372
- it('should translate $nested operator with complex conditions', () => {
373
- const filter: QdrantVectorFilter = {
374
- nested_field: {
375
- $nested: {
376
- inner_field: { $gt: 100 },
377
- 'deep.field': { $in: ['value1', 'value2'] },
378
- },
379
- },
380
- };
381
- const expected = {
382
- must: [
383
- {
384
- nested: {
385
- key: 'nested_field',
386
- filter: {
387
- must: [
388
- { key: 'inner_field', range: { gt: 100 } },
389
- { key: 'deep.field', match: { any: ['value1', 'value2'] } },
390
- ],
391
- },
392
- },
393
- },
394
- ],
395
- };
396
-
397
- expect(translator.translate(filter)).toEqual(expected);
398
- });
399
-
400
- it('should translate $datetime operator with multiple range conditions', () => {
401
- const now = new Date();
402
- const filter: QdrantVectorFilter = {
403
- timestamp: {
404
- $datetime: {
405
- range: {
406
- gt: now,
407
- lt: new Date(now.getTime() + 86400000),
408
- gte: new Date(now.getTime() - 3600000),
409
- lte: new Date(now.getTime() + 90000000),
410
- },
411
- },
412
- },
413
- };
414
-
415
- const expected = {
416
- must: [
417
- {
418
- key: 'timestamp',
419
- range: {
420
- gt: now.toISOString(),
421
- lt: new Date(now.getTime() + 86400000).toISOString(),
422
- gte: new Date(now.getTime() - 3600000).toISOString(),
423
- lte: new Date(now.getTime() + 90000000).toISOString(),
424
- },
425
- },
426
- ],
427
- };
428
-
429
- expect(translator.translate(filter)).toEqual(expected);
430
- });
431
-
432
- it('should translate $datetime operator with string dates', () => {
433
- const filter: QdrantVectorFilter = {
434
- timestamp: {
435
- $datetime: {
436
- key: 'timestamp',
437
- range: {
438
- gt: '2023-01-01T00:00:00Z',
439
- lt: '2024-01-01T00:00:00Z',
440
- },
441
- },
442
- },
443
- };
444
-
445
- const expected = {
446
- must: [
447
- {
448
- key: 'timestamp',
449
- range: {
450
- gt: '2023-01-01T00:00:00Z',
451
- lt: '2024-01-01T00:00:00Z',
452
- },
453
- },
454
- ],
455
- };
456
-
457
- expect(translator.translate(filter)).toEqual(expected);
458
- });
459
-
460
- it('should translate complex $nested operator', () => {
461
- const filter: QdrantVectorFilter = {
462
- diet: {
463
- $nested: {
464
- food: { $in: ['meat', 'fish'] },
465
- likes: true,
466
- rating: { $gt: 5 },
467
- },
468
- },
469
- };
470
-
471
- const expected = {
472
- must: [
473
- {
474
- nested: {
475
- key: 'diet',
476
- filter: {
477
- must: [
478
- { key: 'food', match: { any: ['meat', 'fish'] } },
479
- { key: 'likes', match: { value: true } },
480
- { key: 'rating', range: { gt: 5 } },
481
- ],
482
- },
483
- },
484
- },
485
- ],
486
- };
487
-
488
- expect(translator.translate(filter)).toEqual(expected);
489
- });
490
- });
491
-
492
- describe('Special Cases', () => {
493
- it('should handle nested paths', () => {
494
- const filter: QdrantVectorFilter = { 'obj.field': 'value' };
495
- const expected = { must: [{ key: 'obj.field', match: { value: 'value' } }] };
496
- expect(translator.translate(filter)).toEqual(expected);
497
- });
498
-
499
- it('should handle deep nested paths', () => {
500
- const filter: QdrantVectorFilter = { 'a.b.c.d': { $gt: 100 } };
501
- const expected = { must: [{ key: 'a.b.c.d', range: { gt: 100 } }] };
502
- expect(translator.translate(filter)).toEqual(expected);
503
- });
504
-
505
- it('should handle complex combinations', () => {
506
- const filter: QdrantVectorFilter = {
507
- $and: [
508
- { 'user.age': { $gte: 18 } },
509
- {
510
- $or: [{ 'user.country': { $in: ['US', 'CA'] } }, { 'user.verified': true }],
511
- },
512
- {
513
- $not: { 'user.banned': true },
514
- },
515
- ],
516
- };
517
-
518
- const expected = {
519
- must: [
520
- { key: 'user.age', range: { gte: 18 } },
521
- {
522
- should: [
523
- { key: 'user.country', match: { any: ['US', 'CA'] } },
524
- { key: 'user.verified', match: { value: true } },
525
- ],
526
- },
527
- {
528
- must_not: [{ key: 'user.banned', match: { value: true } }],
529
- },
530
- ],
531
- };
532
-
533
- expect(translator.translate(filter)).toEqual(expected);
534
- });
535
-
536
- it('should handle multiple nested paths with same prefix', () => {
537
- const filter: QdrantVectorFilter = {
538
- $and: [{ 'user.profile.age': { $gte: 18 } }, { 'user.profile.name': 'John' }],
539
- };
540
-
541
- const expected = {
542
- must: [
543
- { key: 'user.profile.age', range: { gte: 18 } },
544
- { key: 'user.profile.name', match: { value: 'John' } },
545
- ],
546
- };
547
-
548
- expect(translator.translate(filter)).toEqual(expected);
549
- });
550
-
551
- it('should handle mixed array and object paths', () => {
552
- const filter: QdrantVectorFilter = {
553
- 'items[].category': { $in: ['A', 'B'] },
554
- 'items[].details.price': { $gt: 100 },
555
- };
556
-
557
- const expected = {
558
- must: [
559
- { key: 'items[].category', match: { any: ['A', 'B'] } },
560
- { key: 'items[].details.price', range: { gt: 100 } },
561
- ],
562
- };
563
-
564
- expect(translator.translate(filter)).toEqual(expected);
565
- });
566
-
567
- it('should handle empty logical operators in combination', () => {
568
- const filter: QdrantVectorFilter = {
569
- $and: [{ $or: [] }, { field: 'value' }],
570
- };
571
- const expected = {
572
- must: [{ should: [] }, { key: 'field', match: { value: 'value' } }],
573
- };
574
- expect(translator.translate(filter)).toEqual(expected);
575
- });
576
-
577
- it('should handle deeply nested paths with array notation', () => {
578
- const filter: QdrantVectorFilter = {
579
- 'users[].addresses[].geo.location': {
580
- $geo: {
581
- type: 'radius',
582
- center: { lat: 52.5, lon: 13.4 },
583
- radius: 1000,
584
- },
585
- },
586
- };
587
- const expected = {
588
- must: [
589
- {
590
- key: 'users[].addresses[].geo.location',
591
- geo_radius: {
592
- center: { lat: 52.5, lon: 13.4 },
593
- radius: 1000,
594
- },
595
- },
596
- ],
597
- };
598
- expect(translator.translate(filter)).toEqual(expected);
599
- });
600
-
601
- it('should handle array paths with multiple levels', () => {
602
- const filter: QdrantVectorFilter = {
603
- 'users[].addresses[].location[].coordinates': { $gt: 100 },
604
- };
605
- const expected = {
606
- must: [
607
- {
608
- key: 'users[].addresses[].location[].coordinates',
609
- range: { gt: 100 },
610
- },
611
- ],
612
- };
613
- expect(translator.translate(filter)).toEqual(expected);
614
- });
615
-
616
- it('should handle combination of array and object paths', () => {
617
- const filter: QdrantVectorFilter = {
618
- 'users[].profile.addresses[].location.coordinates': { $gt: 100 },
619
- };
620
- const expected = {
621
- must: [
622
- {
623
- key: 'users[].profile.addresses[].location.coordinates',
624
- range: { gt: 100 },
625
- },
626
- ],
627
- };
628
- expect(translator.translate(filter)).toEqual(expected);
629
- });
630
-
631
- it('should handle multiple conditions with same array path', () => {
632
- const filter: QdrantVectorFilter = {
633
- $and: [
634
- { 'items[].price': { $gt: 100 } },
635
- { 'items[].quantity': { $gt: 0 } },
636
- { 'items[].tags': { $in: ['sale'] } },
637
- ],
638
- };
639
- const expected = {
640
- must: [
641
- { key: 'items[].price', range: { gt: 100 } },
642
- { key: 'items[].quantity', range: { gt: 0 } },
643
- { key: 'items[].tags', match: { any: ['sale'] } },
644
- ],
645
- };
646
- expect(translator.translate(filter)).toEqual(expected);
647
- });
648
- });
649
-
650
- describe('Error Cases', () => {
651
- it('should throw error for unsupported operators', () => {
652
- const filter = { field: { $invalidOp: 'value' } };
653
- expect(() => translator.translate(filter)).toThrow();
654
- });
655
-
656
- it('should throw error for invalid geo filter type', () => {
657
- const filter: QdrantVectorFilter = {
658
- location: {
659
- $geo: {
660
- type: 'invalid',
661
- center: { lat: 52.5, lon: 13.4 },
662
- radius: 1000,
663
- },
664
- },
665
- };
666
- expect(() => translator.translate(filter)).toThrow();
667
- });
668
-
669
- it('should throw error for invalid custom operator', () => {
670
- const filter: QdrantVectorFilter = { $invalidCustom: 'value' };
671
- expect(() => translator.translate(filter)).toThrow();
672
- });
673
- });
674
-
675
- describe('Validation Cases', () => {
676
- it('should validate $not operator structure', () => {
677
- const invalidFilters = [
678
- { $not: 'invalid' }, // Should be an object
679
- { $not: [] }, // Should be an object
680
- { $not: {} }, // Cannot be empty
681
- ];
682
-
683
- invalidFilters.forEach(filter => {
684
- expect(() => translator.translate(filter)).toThrow();
685
- });
686
- });
687
-
688
- it('should validate array operator values', () => {
689
- const invalidFilters: any = [
690
- { field: { $in: 'not-an-array' } }, // Should be array
691
- { field: { $nin: 123 } }, // Should be array
692
- { field: { $in: {} } }, // Should be array
693
- ];
694
-
695
- invalidFilters.forEach(filter => {
696
- expect(() => translator.translate(filter)).toThrow();
697
- });
698
- });
699
- it('throws error for non-logical operators at top level', () => {
700
- const invalidFilters: any = [{ $gt: 100 }, { $in: ['value1', 'value2'] }, { $eq: true }];
701
-
702
- invalidFilters.forEach(filter => {
703
- expect(() => translator.translate(filter)).toThrow(/Invalid top-level operator/);
704
- });
705
- });
706
- it('allows logical operators at top level', () => {
707
- const validFilters: QdrantVectorFilter[] = [{ $and: [{ field: 'value' }] }, { $or: [{ field: 'value' }] }];
708
-
709
- validFilters.forEach(filter => {
710
- expect(() => translator.translate(filter)).not.toThrow();
711
- });
712
- });
713
- });
714
-
715
- describe('Must Wrapper Cases', () => {
716
- it('should wrap single field conditions', () => {
717
- const filters = {
718
- simple: { field: 'value' },
719
- nested: { 'obj.field': 'value' },
720
- array: { 'items[].field': 'value' },
721
- };
722
-
723
- expect(translator.translate(filters.simple)).toEqual({
724
- must: [{ key: 'field', match: { value: 'value' } }],
725
- });
726
- expect(translator.translate(filters.nested)).toEqual({
727
- must: [{ key: 'obj.field', match: { value: 'value' } }],
728
- });
729
- expect(translator.translate(filters.array)).toEqual({
730
- must: [{ key: 'items[].field', match: { value: 'value' } }],
731
- });
732
- });
733
-
734
- it('should wrap multiple field conditions in single must', () => {
735
- const filter: QdrantVectorFilter = {
736
- field1: 'value1',
737
- field2: 'value2',
738
- };
739
- const expected = {
740
- must: [
741
- { key: 'field1', match: { value: 'value1' } },
742
- { key: 'field2', match: { value: 'value2' } },
743
- ],
744
- };
745
- expect(translator.translate(filter)).toEqual(expected);
746
- });
747
-
748
- it('should wrap complex single field conditions', () => {
749
- const filter: QdrantVectorFilter = {
750
- field: {
751
- $gt: 10,
752
- $lt: 20,
753
- $ne: 15,
754
- },
755
- };
756
- const expected = {
757
- must: [
758
- {
759
- key: 'field',
760
- range: { gt: 10, lt: 20 },
761
- },
762
- {
763
- key: 'field',
764
- match: { except: [15] },
765
- },
766
- ],
767
- };
768
- expect(translator.translate(filter)).toEqual(expected);
769
- });
770
- });
771
-
772
- describe('No Must Wrapper Cases', () => {
773
- it('should not wrap nested logical operators', () => {
774
- const filter: QdrantVectorFilter = {
775
- $and: [{ field1: 'value1' }, { field2: 'value2' }],
776
- };
777
- const expected = {
778
- must: [
779
- { key: 'field1', match: { value: 'value1' } },
780
- { key: 'field2', match: { value: 'value2' } },
781
- ],
782
- };
783
- expect(translator.translate(filter)).toEqual(expected);
784
- });
785
-
786
- it('should not double-wrap already wrapped conditions', () => {
787
- const filter = {
788
- must: [{ key: 'field', match: { value: 'value' } }],
789
- };
790
- expect(translator.translate(filter)).toEqual(filter);
791
- });
792
-
793
- it('should preserve existing logical structure', () => {
794
- const filter: QdrantVectorFilter = {
795
- $or: [{ field1: 'value1' }, { $and: [{ field2: 'value2' }, { field3: 'value3' }] }],
796
- };
797
- const expected = {
798
- should: [
799
- { key: 'field1', match: { value: 'value1' } },
800
- {
801
- must: [
802
- { key: 'field2', match: { value: 'value2' } },
803
- { key: 'field3', match: { value: 'value3' } },
804
- ],
805
- },
806
- ],
807
- };
808
- expect(translator.translate(filter)).toEqual(expected);
809
- });
810
- });
811
-
812
- describe('Regex Patterns', () => {
813
- it('should throw error for direct regex patterns', () => {
814
- const filter = {
815
- field: /pattern/,
816
- };
817
- expect(() => translator.translate(filter)).toThrow();
818
- });
819
-
820
- it('should translate $regex operator', () => {
821
- const filters = {
822
- field: { $regex: 'pattern' },
823
- };
824
-
825
- expect(translator.translate(filters)).toEqual({ must: [{ key: 'field', match: { text: 'pattern' } }] });
826
- });
827
-
828
- it('should handle regex in nested conditions', () => {
829
- const filter: QdrantVectorFilter = {
830
- diet: {
831
- $nested: {
832
- food: { $regex: 'meat' },
833
- description: { $regex: 'organic' },
834
- },
835
- },
836
- };
837
-
838
- const expected = {
839
- must: [
840
- {
841
- nested: {
842
- key: 'diet',
843
- filter: {
844
- must: [
845
- { key: 'food', match: { text: 'meat' } },
846
- { key: 'description', match: { text: 'organic' } },
847
- ],
848
- },
849
- },
850
- },
851
- ],
852
- };
853
-
854
- expect(translator.translate(filter)).toEqual(expected);
855
- });
856
- });
857
- });