@prmichaelsen/remember-mcp 0.2.5 → 0.2.7

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,515 @@
1
+ /**
2
+ * Unit tests for Weaviate v3 filter builders
3
+ */
4
+
5
+ import {
6
+ buildCombinedSearchFilters,
7
+ buildMemoryOnlyFilters,
8
+ buildRelationshipOnlyFilters,
9
+ hasFilters,
10
+ } from './weaviate-filters.js';
11
+ import type { SearchFilters } from '../types/memory.js';
12
+
13
+ /**
14
+ * Mock Weaviate collection with filter builder
15
+ */
16
+ function createMockCollection() {
17
+ const mockFilter = {
18
+ byProperty: (property: string) => ({
19
+ equal: (value: any) => ({ property, operator: 'equal', value }),
20
+ greaterThanOrEqual: (value: any) => ({ property, operator: 'gte', value }),
21
+ lessThanOrEqual: (value: any) => ({ property, operator: 'lte', value }),
22
+ containsAny: (values: any[]) => ({ property, operator: 'containsAny', values }),
23
+ }),
24
+ };
25
+
26
+ return {
27
+ filter: mockFilter,
28
+ };
29
+ }
30
+
31
+ describe('weaviate-filters', () => {
32
+ let mockCollection: any;
33
+
34
+ beforeEach(() => {
35
+ mockCollection = createMockCollection();
36
+ });
37
+
38
+ describe('buildMemoryOnlyFilters', () => {
39
+ it('should build filter with only doc_type when no other filters provided', () => {
40
+ const result = buildMemoryOnlyFilters(mockCollection);
41
+
42
+ expect(result).toEqual({
43
+ property: 'doc_type',
44
+ operator: 'equal',
45
+ value: 'memory',
46
+ });
47
+ });
48
+
49
+ it('should build filter with doc_type and single content type', () => {
50
+ const filters: SearchFilters = {
51
+ types: ['note'],
52
+ };
53
+
54
+ const result = buildMemoryOnlyFilters(mockCollection, filters);
55
+
56
+ expect(result).toEqual({
57
+ operator: 'And',
58
+ operands: [
59
+ { property: 'doc_type', operator: 'equal', value: 'memory' },
60
+ { property: 'type', operator: 'equal', value: 'note' },
61
+ ],
62
+ });
63
+ });
64
+
65
+ it('should build filter with doc_type and multiple content types', () => {
66
+ const filters: SearchFilters = {
67
+ types: ['note', 'event', 'todo'],
68
+ };
69
+
70
+ const result = buildMemoryOnlyFilters(mockCollection, filters);
71
+
72
+ expect(result).toEqual({
73
+ operator: 'And',
74
+ operands: [
75
+ { property: 'doc_type', operator: 'equal', value: 'memory' },
76
+ { property: 'type', operator: 'containsAny', values: ['note', 'event', 'todo'] },
77
+ ],
78
+ });
79
+ });
80
+
81
+ it('should build filter with weight_min', () => {
82
+ const filters: SearchFilters = {
83
+ weight_min: 0.5,
84
+ };
85
+
86
+ const result = buildMemoryOnlyFilters(mockCollection, filters);
87
+
88
+ expect(result).toEqual({
89
+ operator: 'And',
90
+ operands: [
91
+ { property: 'doc_type', operator: 'equal', value: 'memory' },
92
+ { property: 'weight', operator: 'gte', value: 0.5 },
93
+ ],
94
+ });
95
+ });
96
+
97
+ it('should build filter with weight_max', () => {
98
+ const filters: SearchFilters = {
99
+ weight_max: 0.8,
100
+ };
101
+
102
+ const result = buildMemoryOnlyFilters(mockCollection, filters);
103
+
104
+ expect(result).toEqual({
105
+ operator: 'And',
106
+ operands: [
107
+ { property: 'doc_type', operator: 'equal', value: 'memory' },
108
+ { property: 'weight', operator: 'lte', value: 0.8 },
109
+ ],
110
+ });
111
+ });
112
+
113
+ it('should build filter with trust_min', () => {
114
+ const filters: SearchFilters = {
115
+ trust_min: 0.3,
116
+ };
117
+
118
+ const result = buildMemoryOnlyFilters(mockCollection, filters);
119
+
120
+ expect(result).toEqual({
121
+ operator: 'And',
122
+ operands: [
123
+ { property: 'doc_type', operator: 'equal', value: 'memory' },
124
+ { property: 'trust', operator: 'gte', value: 0.3 },
125
+ ],
126
+ });
127
+ });
128
+
129
+ it('should build filter with date range', () => {
130
+ const filters: SearchFilters = {
131
+ date_from: '2024-01-01',
132
+ date_to: '2024-12-31',
133
+ };
134
+
135
+ const result = buildMemoryOnlyFilters(mockCollection, filters);
136
+
137
+ expect(result).toEqual({
138
+ operator: 'And',
139
+ operands: [
140
+ { property: 'doc_type', operator: 'equal', value: 'memory' },
141
+ { property: 'created_at', operator: 'gte', value: new Date('2024-01-01') },
142
+ { property: 'created_at', operator: 'lte', value: new Date('2024-12-31') },
143
+ ],
144
+ });
145
+ });
146
+
147
+ it('should build filter with single tag', () => {
148
+ const filters: SearchFilters = {
149
+ tags: ['important'],
150
+ };
151
+
152
+ const result = buildMemoryOnlyFilters(mockCollection, filters);
153
+
154
+ expect(result).toEqual({
155
+ operator: 'And',
156
+ operands: [
157
+ { property: 'doc_type', operator: 'equal', value: 'memory' },
158
+ { property: 'tags', operator: 'containsAny', values: ['important'] },
159
+ ],
160
+ });
161
+ });
162
+
163
+ it('should build filter with multiple tags', () => {
164
+ const filters: SearchFilters = {
165
+ tags: ['work', 'urgent', 'project-x'],
166
+ };
167
+
168
+ const result = buildMemoryOnlyFilters(mockCollection, filters);
169
+
170
+ expect(result).toEqual({
171
+ operator: 'And',
172
+ operands: [
173
+ { property: 'doc_type', operator: 'equal', value: 'memory' },
174
+ { property: 'tags', operator: 'containsAny', values: ['work', 'urgent', 'project-x'] },
175
+ ],
176
+ });
177
+ });
178
+
179
+ it('should build complex filter with multiple criteria', () => {
180
+ const filters: SearchFilters = {
181
+ types: ['note', 'todo'],
182
+ weight_min: 0.5,
183
+ trust_min: 0.3,
184
+ date_from: '2024-01-01',
185
+ tags: ['work'],
186
+ };
187
+
188
+ const result = buildMemoryOnlyFilters(mockCollection, filters);
189
+
190
+ expect(result).toEqual({
191
+ operator: 'And',
192
+ operands: [
193
+ { property: 'doc_type', operator: 'equal', value: 'memory' },
194
+ { property: 'type', operator: 'containsAny', values: ['note', 'todo'] },
195
+ { property: 'weight', operator: 'gte', value: 0.5 },
196
+ { property: 'trust', operator: 'gte', value: 0.3 },
197
+ { property: 'created_at', operator: 'gte', value: new Date('2024-01-01') },
198
+ { property: 'tags', operator: 'containsAny', values: ['work'] },
199
+ ],
200
+ });
201
+ });
202
+ });
203
+
204
+ describe('buildRelationshipOnlyFilters', () => {
205
+ it('should build filter with only doc_type when no other filters provided', () => {
206
+ const result = buildRelationshipOnlyFilters(mockCollection);
207
+
208
+ expect(result).toEqual({
209
+ property: 'doc_type',
210
+ operator: 'equal',
211
+ value: 'relationship',
212
+ });
213
+ });
214
+
215
+ it('should NOT include type filter for relationships', () => {
216
+ const filters: SearchFilters = {
217
+ types: ['note'], // This should be ignored for relationships
218
+ };
219
+
220
+ const result = buildRelationshipOnlyFilters(mockCollection, filters);
221
+
222
+ // Should only have doc_type filter, not type filter
223
+ expect(result).toEqual({
224
+ property: 'doc_type',
225
+ operator: 'equal',
226
+ value: 'relationship',
227
+ });
228
+ });
229
+
230
+ it('should build filter with weight and trust for relationships', () => {
231
+ const filters: SearchFilters = {
232
+ weight_min: 0.5,
233
+ trust_min: 0.3,
234
+ };
235
+
236
+ const result = buildRelationshipOnlyFilters(mockCollection, filters);
237
+
238
+ expect(result).toEqual({
239
+ operator: 'And',
240
+ operands: [
241
+ { property: 'doc_type', operator: 'equal', value: 'relationship' },
242
+ { property: 'weight', operator: 'gte', value: 0.5 },
243
+ { property: 'trust', operator: 'gte', value: 0.3 },
244
+ ],
245
+ });
246
+ });
247
+
248
+ it('should build filter with date range and tags for relationships', () => {
249
+ const filters: SearchFilters = {
250
+ date_from: '2024-01-01',
251
+ tags: ['important'],
252
+ };
253
+
254
+ const result = buildRelationshipOnlyFilters(mockCollection, filters);
255
+
256
+ expect(result).toEqual({
257
+ operator: 'And',
258
+ operands: [
259
+ { property: 'doc_type', operator: 'equal', value: 'relationship' },
260
+ { property: 'created_at', operator: 'gte', value: new Date('2024-01-01') },
261
+ { property: 'tags', operator: 'containsAny', values: ['important'] },
262
+ ],
263
+ });
264
+ });
265
+ });
266
+
267
+ describe('buildCombinedSearchFilters', () => {
268
+ it('should combine memory and relationship filters with OR', () => {
269
+ const result = buildCombinedSearchFilters(mockCollection);
270
+
271
+ expect(result).toEqual({
272
+ operator: 'Or',
273
+ operands: [
274
+ { property: 'doc_type', operator: 'equal', value: 'memory' },
275
+ { property: 'doc_type', operator: 'equal', value: 'relationship' },
276
+ ],
277
+ });
278
+ });
279
+
280
+ it('should combine memory and relationship filters with shared criteria', () => {
281
+ const filters: SearchFilters = {
282
+ weight_min: 0.5,
283
+ trust_min: 0.3,
284
+ };
285
+
286
+ const result = buildCombinedSearchFilters(mockCollection, filters);
287
+
288
+ expect(result).toEqual({
289
+ operator: 'Or',
290
+ operands: [
291
+ {
292
+ operator: 'And',
293
+ operands: [
294
+ { property: 'doc_type', operator: 'equal', value: 'memory' },
295
+ { property: 'weight', operator: 'gte', value: 0.5 },
296
+ { property: 'trust', operator: 'gte', value: 0.3 },
297
+ ],
298
+ },
299
+ {
300
+ operator: 'And',
301
+ operands: [
302
+ { property: 'doc_type', operator: 'equal', value: 'relationship' },
303
+ { property: 'weight', operator: 'gte', value: 0.5 },
304
+ { property: 'trust', operator: 'gte', value: 0.3 },
305
+ ],
306
+ },
307
+ ],
308
+ });
309
+ });
310
+
311
+ it('should handle type filter only for memories in combined search', () => {
312
+ const filters: SearchFilters = {
313
+ types: ['note', 'todo'],
314
+ weight_min: 0.5,
315
+ };
316
+
317
+ const result = buildCombinedSearchFilters(mockCollection, filters);
318
+
319
+ expect(result).toEqual({
320
+ operator: 'Or',
321
+ operands: [
322
+ {
323
+ operator: 'And',
324
+ operands: [
325
+ { property: 'doc_type', operator: 'equal', value: 'memory' },
326
+ { property: 'type', operator: 'containsAny', values: ['note', 'todo'] },
327
+ { property: 'weight', operator: 'gte', value: 0.5 },
328
+ ],
329
+ },
330
+ {
331
+ operator: 'And',
332
+ operands: [
333
+ { property: 'doc_type', operator: 'equal', value: 'relationship' },
334
+ { property: 'weight', operator: 'gte', value: 0.5 },
335
+ ],
336
+ },
337
+ ],
338
+ });
339
+ });
340
+
341
+ it('should handle complex filters in combined search', () => {
342
+ const filters: SearchFilters = {
343
+ types: ['note'],
344
+ weight_min: 0.5,
345
+ weight_max: 0.9,
346
+ trust_min: 0.3,
347
+ date_from: '2024-01-01',
348
+ date_to: '2024-12-31',
349
+ tags: ['work', 'important'],
350
+ };
351
+
352
+ const result = buildCombinedSearchFilters(mockCollection, filters);
353
+
354
+ expect(result.operator).toBe('Or');
355
+ expect(result.operands).toHaveLength(2);
356
+
357
+ // Memory filters should include type (single type uses 'equal', not 'containsAny')
358
+ expect(result.operands[0].operands).toContainEqual({
359
+ property: 'type',
360
+ operator: 'equal',
361
+ value: 'note',
362
+ });
363
+
364
+ // Relationship filters should NOT include type
365
+ expect(result.operands[1].operands).not.toContainEqual(
366
+ expect.objectContaining({ property: 'type' })
367
+ );
368
+
369
+ // Both should have shared filters
370
+ const sharedFilters = [
371
+ { property: 'weight', operator: 'gte', value: 0.5 },
372
+ { property: 'weight', operator: 'lte', value: 0.9 },
373
+ { property: 'trust', operator: 'gte', value: 0.3 },
374
+ { property: 'created_at', operator: 'gte', value: new Date('2024-01-01') },
375
+ { property: 'created_at', operator: 'lte', value: new Date('2024-12-31') },
376
+ { property: 'tags', operator: 'containsAny', values: ['work', 'important'] },
377
+ ];
378
+
379
+ sharedFilters.forEach(filter => {
380
+ expect(result.operands[0].operands).toContainEqual(filter);
381
+ expect(result.operands[1].operands).toContainEqual(filter);
382
+ });
383
+ });
384
+ });
385
+
386
+ describe('hasFilters', () => {
387
+ it('should return true for defined filter', () => {
388
+ const filter = { property: 'doc_type', operator: 'equal', value: 'memory' };
389
+ expect(hasFilters(filter)).toBe(true);
390
+ });
391
+
392
+ it('should return false for undefined', () => {
393
+ expect(hasFilters(undefined)).toBe(false);
394
+ });
395
+
396
+ it('should return false for null', () => {
397
+ expect(hasFilters(null)).toBe(false);
398
+ });
399
+
400
+ it('should return true for empty object', () => {
401
+ expect(hasFilters({})).toBe(true);
402
+ });
403
+
404
+ it('should return true for complex filter', () => {
405
+ const filter = {
406
+ operator: 'And',
407
+ operands: [
408
+ { property: 'doc_type', operator: 'equal', value: 'memory' },
409
+ { property: 'weight', operator: 'gte', value: 0.5 },
410
+ ],
411
+ };
412
+ expect(hasFilters(filter)).toBe(true);
413
+ });
414
+ });
415
+
416
+ describe('edge cases', () => {
417
+ it('should handle empty types array', () => {
418
+ const filters: SearchFilters = {
419
+ types: [],
420
+ };
421
+
422
+ const result = buildMemoryOnlyFilters(mockCollection, filters);
423
+
424
+ // Should only have doc_type filter
425
+ expect(result).toEqual({
426
+ property: 'doc_type',
427
+ operator: 'equal',
428
+ value: 'memory',
429
+ });
430
+ });
431
+
432
+ it('should handle empty tags array', () => {
433
+ const filters: SearchFilters = {
434
+ tags: [],
435
+ };
436
+
437
+ const result = buildMemoryOnlyFilters(mockCollection, filters);
438
+
439
+ // Should only have doc_type filter
440
+ expect(result).toEqual({
441
+ property: 'doc_type',
442
+ operator: 'equal',
443
+ value: 'memory',
444
+ });
445
+ });
446
+
447
+ it('should handle weight_min of 0', () => {
448
+ const filters: SearchFilters = {
449
+ weight_min: 0,
450
+ };
451
+
452
+ const result = buildMemoryOnlyFilters(mockCollection, filters);
453
+
454
+ expect(result).toEqual({
455
+ operator: 'And',
456
+ operands: [
457
+ { property: 'doc_type', operator: 'equal', value: 'memory' },
458
+ { property: 'weight', operator: 'gte', value: 0 },
459
+ ],
460
+ });
461
+ });
462
+
463
+ it('should handle trust_min of 0', () => {
464
+ const filters: SearchFilters = {
465
+ trust_min: 0,
466
+ };
467
+
468
+ const result = buildMemoryOnlyFilters(mockCollection, filters);
469
+
470
+ expect(result).toEqual({
471
+ operator: 'And',
472
+ operands: [
473
+ { property: 'doc_type', operator: 'equal', value: 'memory' },
474
+ { property: 'trust', operator: 'gte', value: 0 },
475
+ ],
476
+ });
477
+ });
478
+
479
+ it('should handle weight range (min and max)', () => {
480
+ const filters: SearchFilters = {
481
+ weight_min: 0.3,
482
+ weight_max: 0.7,
483
+ };
484
+
485
+ const result = buildMemoryOnlyFilters(mockCollection, filters);
486
+
487
+ expect(result).toEqual({
488
+ operator: 'And',
489
+ operands: [
490
+ { property: 'doc_type', operator: 'equal', value: 'memory' },
491
+ { property: 'weight', operator: 'gte', value: 0.3 },
492
+ { property: 'weight', operator: 'lte', value: 0.7 },
493
+ ],
494
+ });
495
+ });
496
+
497
+ it('should handle trust range (min and max)', () => {
498
+ const filters: SearchFilters = {
499
+ trust_min: 0.2,
500
+ trust_max: 0.8,
501
+ };
502
+
503
+ const result = buildMemoryOnlyFilters(mockCollection, filters);
504
+
505
+ expect(result).toEqual({
506
+ operator: 'And',
507
+ operands: [
508
+ { property: 'doc_type', operator: 'equal', value: 'memory' },
509
+ { property: 'trust', operator: 'gte', value: 0.2 },
510
+ { property: 'trust', operator: 'lte', value: 0.8 },
511
+ ],
512
+ });
513
+ });
514
+ });
515
+ });