@mhalder/qdrant-mcp-server 1.1.1 → 1.2.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,6 +1,6 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
2
- import { QdrantManager } from "./client.js";
3
1
  import { QdrantClient } from "@qdrant/js-client-rest";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { QdrantManager } from "./client.js";
4
4
 
5
5
  vi.mock("@qdrant/js-client-rest", () => ({
6
6
  QdrantClient: vi.fn(),
@@ -31,29 +31,41 @@ describe("QdrantManager", () => {
31
31
  it("should create a collection with default distance metric", async () => {
32
32
  await manager.createCollection("test-collection", 1536);
33
33
 
34
- expect(mockClient.createCollection).toHaveBeenCalledWith(
35
- "test-collection",
36
- {
37
- vectors: {
38
- size: 1536,
39
- distance: "Cosine",
40
- },
34
+ expect(mockClient.createCollection).toHaveBeenCalledWith("test-collection", {
35
+ vectors: {
36
+ size: 1536,
37
+ distance: "Cosine",
41
38
  },
42
- );
39
+ });
43
40
  });
44
41
 
45
42
  it("should create a collection with custom distance metric", async () => {
46
43
  await manager.createCollection("test-collection", 1536, "Euclid");
47
44
 
48
- expect(mockClient.createCollection).toHaveBeenCalledWith(
49
- "test-collection",
50
- {
51
- vectors: {
45
+ expect(mockClient.createCollection).toHaveBeenCalledWith("test-collection", {
46
+ vectors: {
47
+ size: 1536,
48
+ distance: "Euclid",
49
+ },
50
+ });
51
+ });
52
+
53
+ it("should create a hybrid collection with sparse vectors enabled", async () => {
54
+ await manager.createCollection("test-collection", 1536, "Cosine", true);
55
+
56
+ expect(mockClient.createCollection).toHaveBeenCalledWith("test-collection", {
57
+ vectors: {
58
+ dense: {
52
59
  size: 1536,
53
- distance: "Euclid",
60
+ distance: "Cosine",
54
61
  },
55
62
  },
56
- );
63
+ sparse_vectors: {
64
+ text: {
65
+ modifier: "idf",
66
+ },
67
+ },
68
+ });
57
69
  });
58
70
  });
59
71
 
@@ -79,20 +91,12 @@ describe("QdrantManager", () => {
79
91
  describe("listCollections", () => {
80
92
  it("should return list of collection names", async () => {
81
93
  mockClient.getCollections.mockResolvedValue({
82
- collections: [
83
- { name: "collection1" },
84
- { name: "collection2" },
85
- { name: "collection3" },
86
- ],
94
+ collections: [{ name: "collection1" }, { name: "collection2" }, { name: "collection3" }],
87
95
  });
88
96
 
89
97
  const collections = await manager.listCollections();
90
98
 
91
- expect(collections).toEqual([
92
- "collection1",
93
- "collection2",
94
- "collection3",
95
- ]);
99
+ expect(collections).toEqual(["collection1", "collection2", "collection3"]);
96
100
  });
97
101
 
98
102
  it("should return empty array when no collections exist", async () => {
@@ -128,6 +132,7 @@ describe("QdrantManager", () => {
128
132
  vectorSize: 1536,
129
133
  pointsCount: 100,
130
134
  distance: "Cosine",
135
+ hybridEnabled: false,
131
136
  });
132
137
  });
133
138
 
@@ -148,15 +153,45 @@ describe("QdrantManager", () => {
148
153
 
149
154
  expect(info.pointsCount).toBe(0);
150
155
  });
156
+
157
+ it("should return hybrid collection info with named vectors", async () => {
158
+ mockClient.getCollection.mockResolvedValue({
159
+ collection_name: "hybrid-collection",
160
+ points_count: 50,
161
+ config: {
162
+ params: {
163
+ vectors: {
164
+ dense: {
165
+ size: 768,
166
+ distance: "Cosine",
167
+ },
168
+ },
169
+ sparse_vectors: {
170
+ text: {
171
+ modifier: "idf",
172
+ },
173
+ },
174
+ },
175
+ },
176
+ });
177
+
178
+ const info = await manager.getCollectionInfo("hybrid-collection");
179
+
180
+ expect(info).toEqual({
181
+ name: "hybrid-collection",
182
+ vectorSize: 768,
183
+ pointsCount: 50,
184
+ distance: "Cosine",
185
+ hybridEnabled: true,
186
+ });
187
+ });
151
188
  });
152
189
 
153
190
  describe("deleteCollection", () => {
154
191
  it("should delete a collection", async () => {
155
192
  await manager.deleteCollection("test-collection");
156
193
 
157
- expect(mockClient.deleteCollection).toHaveBeenCalledWith(
158
- "test-collection",
159
- );
194
+ expect(mockClient.deleteCollection).toHaveBeenCalledWith("test-collection");
160
195
  });
161
196
  });
162
197
 
@@ -205,7 +240,7 @@ describe("QdrantManager", () => {
205
240
  const normalizedId = calls[0][1].points[0].id;
206
241
  // Check that it's a valid UUID format
207
242
  expect(normalizedId).toMatch(
208
- /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
243
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
209
244
  );
210
245
  // Ensure it's not the original ID
211
246
  expect(normalizedId).not.toBe("my-custom-id");
@@ -213,9 +248,7 @@ describe("QdrantManager", () => {
213
248
 
214
249
  it("should preserve UUID format IDs without modification", async () => {
215
250
  const uuidId = "123e4567-e89b-12d3-a456-426614174000";
216
- const points = [
217
- { id: uuidId, vector: [0.1, 0.2, 0.3], payload: { text: "test" } },
218
- ];
251
+ const points = [{ id: uuidId, vector: [0.1, 0.2, 0.3], payload: { text: "test" } }];
219
252
 
220
253
  await manager.addPoints("test-collection", points);
221
254
 
@@ -243,10 +276,8 @@ describe("QdrantManager", () => {
243
276
  },
244
277
  });
245
278
 
246
- await expect(
247
- manager.addPoints("test-collection", points),
248
- ).rejects.toThrow(
249
- 'Failed to add points to collection "test-collection": Vector dimension mismatch',
279
+ await expect(manager.addPoints("test-collection", points)).rejects.toThrow(
280
+ 'Failed to add points to collection "test-collection": Vector dimension mismatch'
250
281
  );
251
282
  });
252
283
 
@@ -255,10 +286,8 @@ describe("QdrantManager", () => {
255
286
 
256
287
  mockClient.upsert.mockRejectedValue(new Error("Network error"));
257
288
 
258
- await expect(
259
- manager.addPoints("test-collection", points),
260
- ).rejects.toThrow(
261
- 'Failed to add points to collection "test-collection": Network error',
289
+ await expect(manager.addPoints("test-collection", points)).rejects.toThrow(
290
+ 'Failed to add points to collection "test-collection": Network error'
262
291
  );
263
292
  });
264
293
 
@@ -267,26 +296,36 @@ describe("QdrantManager", () => {
267
296
 
268
297
  mockClient.upsert.mockRejectedValue("Unknown error");
269
298
 
270
- await expect(
271
- manager.addPoints("test-collection", points),
272
- ).rejects.toThrow(
273
- 'Failed to add points to collection "test-collection": Unknown error',
299
+ await expect(manager.addPoints("test-collection", points)).rejects.toThrow(
300
+ 'Failed to add points to collection "test-collection": Unknown error'
274
301
  );
275
302
  });
276
303
  });
277
304
 
278
305
  describe("search", () => {
306
+ beforeEach(() => {
307
+ // Mock getCollection for standard collection by default
308
+ mockClient.getCollection.mockResolvedValue({
309
+ collection_name: "test-collection",
310
+ points_count: 100,
311
+ config: {
312
+ params: {
313
+ vectors: {
314
+ size: 768,
315
+ distance: "Cosine",
316
+ },
317
+ },
318
+ },
319
+ });
320
+ });
321
+
279
322
  it("should search for similar vectors", async () => {
280
323
  mockClient.search.mockResolvedValue([
281
324
  { id: 1, score: 0.95, payload: { text: "result1" } },
282
325
  { id: 2, score: 0.85, payload: { text: "result2" } },
283
326
  ]);
284
327
 
285
- const results = await manager.search(
286
- "test-collection",
287
- [0.1, 0.2, 0.3],
288
- 5,
289
- );
328
+ const results = await manager.search("test-collection", [0.1, 0.2, 0.3], 5);
290
329
 
291
330
  expect(results).toEqual([
292
331
  { id: 1, score: 0.95, payload: { text: "result1" } },
@@ -385,21 +424,78 @@ describe("QdrantManager", () => {
385
424
  });
386
425
 
387
426
  it("should handle null payload in results", async () => {
388
- mockClient.search.mockResolvedValue([
389
- { id: 1, score: 0.95, payload: null },
390
- ]);
427
+ mockClient.search.mockResolvedValue([{ id: 1, score: 0.95, payload: null }]);
391
428
 
392
429
  const results = await manager.search("test-collection", [0.1, 0.2, 0.3]);
393
430
 
394
431
  expect(results).toEqual([{ id: 1, score: 0.95, payload: undefined }]);
395
432
  });
433
+
434
+ it("should use named vector for hybrid-enabled collections", async () => {
435
+ // Mock getCollectionInfo to return hybrid enabled collection
436
+ mockClient.getCollection.mockResolvedValue({
437
+ collection_name: "hybrid-collection",
438
+ points_count: 10,
439
+ config: {
440
+ params: {
441
+ vectors: {
442
+ dense: {
443
+ size: 768,
444
+ distance: "Cosine",
445
+ },
446
+ },
447
+ sparse_vectors: {
448
+ text: {
449
+ modifier: "idf",
450
+ },
451
+ },
452
+ },
453
+ },
454
+ });
455
+
456
+ mockClient.search.mockResolvedValue([{ id: 1, score: 0.95, payload: { text: "result1" } }]);
457
+
458
+ const results = await manager.search("hybrid-collection", [0.1, 0.2, 0.3], 5);
459
+
460
+ expect(results).toEqual([{ id: 1, score: 0.95, payload: { text: "result1" } }]);
461
+ expect(mockClient.search).toHaveBeenCalledWith("hybrid-collection", {
462
+ vector: { name: "dense", vector: [0.1, 0.2, 0.3] },
463
+ limit: 5,
464
+ filter: undefined,
465
+ });
466
+ });
467
+
468
+ it("should use unnamed vector for standard collections", async () => {
469
+ // Mock getCollectionInfo to return standard collection (no sparse vectors)
470
+ mockClient.getCollection.mockResolvedValue({
471
+ collection_name: "standard-collection",
472
+ points_count: 10,
473
+ config: {
474
+ params: {
475
+ vectors: {
476
+ size: 768,
477
+ distance: "Cosine",
478
+ },
479
+ },
480
+ },
481
+ });
482
+
483
+ mockClient.search.mockResolvedValue([{ id: 1, score: 0.95, payload: { text: "result1" } }]);
484
+
485
+ const results = await manager.search("standard-collection", [0.1, 0.2, 0.3], 5);
486
+
487
+ expect(results).toEqual([{ id: 1, score: 0.95, payload: { text: "result1" } }]);
488
+ expect(mockClient.search).toHaveBeenCalledWith("standard-collection", {
489
+ vector: [0.1, 0.2, 0.3],
490
+ limit: 5,
491
+ filter: undefined,
492
+ });
493
+ });
396
494
  });
397
495
 
398
496
  describe("getPoint", () => {
399
497
  it("should retrieve a point by id", async () => {
400
- mockClient.retrieve.mockResolvedValue([
401
- { id: 1, payload: { text: "test" } },
402
- ]);
498
+ mockClient.retrieve.mockResolvedValue([{ id: 1, payload: { text: "test" } }]);
403
499
 
404
500
  const point = await manager.getPoint("test-collection", 1);
405
501
 
@@ -453,4 +549,396 @@ describe("QdrantManager", () => {
453
549
  });
454
550
  });
455
551
  });
552
+
553
+ describe("hybridSearch", () => {
554
+ beforeEach(() => {
555
+ mockClient.query = vi.fn();
556
+ });
557
+
558
+ it("should perform hybrid search with dense and sparse vectors", async () => {
559
+ const denseVector = [0.1, 0.2, 0.3];
560
+ const sparseVector = { indices: [1, 5, 10], values: [0.5, 0.3, 0.2] };
561
+
562
+ mockClient.query.mockResolvedValue({
563
+ points: [
564
+ { id: 1, score: 0.95, payload: { text: "result1" } },
565
+ { id: 2, score: 0.85, payload: { text: "result2" } },
566
+ ],
567
+ });
568
+
569
+ const results = await manager.hybridSearch("test-collection", denseVector, sparseVector);
570
+
571
+ expect(results).toEqual([
572
+ { id: 1, score: 0.95, payload: { text: "result1" } },
573
+ { id: 2, score: 0.85, payload: { text: "result2" } },
574
+ ]);
575
+
576
+ expect(mockClient.query).toHaveBeenCalledWith("test-collection", {
577
+ prefetch: [
578
+ {
579
+ query: denseVector,
580
+ using: "dense",
581
+ limit: 20,
582
+ filter: undefined,
583
+ },
584
+ {
585
+ query: sparseVector,
586
+ using: "text",
587
+ limit: 20,
588
+ filter: undefined,
589
+ },
590
+ ],
591
+ query: {
592
+ fusion: "rrf",
593
+ },
594
+ limit: 5,
595
+ with_payload: true,
596
+ });
597
+ });
598
+
599
+ it("should use custom limit with appropriate prefetch limit", async () => {
600
+ const denseVector = [0.1, 0.2, 0.3];
601
+ const sparseVector = { indices: [1, 5], values: [0.5, 0.3] };
602
+
603
+ mockClient.query.mockResolvedValue({ points: [] });
604
+
605
+ await manager.hybridSearch("test-collection", denseVector, sparseVector, 10);
606
+
607
+ expect(mockClient.query).toHaveBeenCalledWith(
608
+ "test-collection",
609
+ expect.objectContaining({
610
+ prefetch: expect.arrayContaining([
611
+ expect.objectContaining({ limit: 40 }), // 10 * 4
612
+ ]),
613
+ limit: 10,
614
+ })
615
+ );
616
+ });
617
+
618
+ it("should convert simple filter to Qdrant format", async () => {
619
+ const denseVector = [0.1, 0.2, 0.3];
620
+ const sparseVector = { indices: [1], values: [0.5] };
621
+ const filter = { category: "test", type: "doc" };
622
+
623
+ mockClient.query.mockResolvedValue({ points: [] });
624
+
625
+ await manager.hybridSearch("test-collection", denseVector, sparseVector, 5, filter);
626
+
627
+ expect(mockClient.query).toHaveBeenCalledWith("test-collection", {
628
+ prefetch: [
629
+ {
630
+ query: denseVector,
631
+ using: "dense",
632
+ limit: 20,
633
+ filter: {
634
+ must: [
635
+ { key: "category", match: { value: "test" } },
636
+ { key: "type", match: { value: "doc" } },
637
+ ],
638
+ },
639
+ },
640
+ {
641
+ query: sparseVector,
642
+ using: "text",
643
+ limit: 20,
644
+ filter: {
645
+ must: [
646
+ { key: "category", match: { value: "test" } },
647
+ { key: "type", match: { value: "doc" } },
648
+ ],
649
+ },
650
+ },
651
+ ],
652
+ query: {
653
+ fusion: "rrf",
654
+ },
655
+ limit: 5,
656
+ with_payload: true,
657
+ });
658
+ });
659
+
660
+ it("should handle Qdrant format filter (must)", async () => {
661
+ const denseVector = [0.1, 0.2, 0.3];
662
+ const sparseVector = { indices: [1], values: [0.5] };
663
+ const filter = { must: [{ key: "status", match: { value: "active" } }] };
664
+
665
+ mockClient.query.mockResolvedValue({ points: [] });
666
+
667
+ await manager.hybridSearch("test-collection", denseVector, sparseVector, 5, filter);
668
+
669
+ const call = mockClient.query.mock.calls[0][1];
670
+ expect(call.prefetch[0].filter).toEqual(filter);
671
+ expect(call.prefetch[1].filter).toEqual(filter);
672
+ });
673
+
674
+ it("should handle Qdrant format filter (should)", async () => {
675
+ const denseVector = [0.1, 0.2, 0.3];
676
+ const sparseVector = { indices: [1], values: [0.5] };
677
+ const filter = { should: [{ key: "tag", match: { value: "important" } }] };
678
+
679
+ mockClient.query.mockResolvedValue({ points: [] });
680
+
681
+ await manager.hybridSearch("test-collection", denseVector, sparseVector, 5, filter);
682
+
683
+ const call = mockClient.query.mock.calls[0][1];
684
+ expect(call.prefetch[0].filter).toEqual(filter);
685
+ expect(call.prefetch[1].filter).toEqual(filter);
686
+ });
687
+
688
+ it("should handle Qdrant format filter (must_not)", async () => {
689
+ const denseVector = [0.1, 0.2, 0.3];
690
+ const sparseVector = { indices: [1], values: [0.5] };
691
+ const filter = { must_not: [{ key: "status", match: { value: "deleted" } }] };
692
+
693
+ mockClient.query.mockResolvedValue({ points: [] });
694
+
695
+ await manager.hybridSearch("test-collection", denseVector, sparseVector, 5, filter);
696
+
697
+ const call = mockClient.query.mock.calls[0][1];
698
+ expect(call.prefetch[0].filter).toEqual(filter);
699
+ expect(call.prefetch[1].filter).toEqual(filter);
700
+ });
701
+
702
+ it("should handle empty filter object", async () => {
703
+ const denseVector = [0.1, 0.2, 0.3];
704
+ const sparseVector = { indices: [1], values: [0.5] };
705
+
706
+ mockClient.query.mockResolvedValue({ points: [] });
707
+
708
+ await manager.hybridSearch("test-collection", denseVector, sparseVector, 5, {});
709
+
710
+ const call = mockClient.query.mock.calls[0][1];
711
+ expect(call.prefetch[0].filter).toBeUndefined();
712
+ expect(call.prefetch[1].filter).toBeUndefined();
713
+ });
714
+
715
+ it("should handle null payload in results", async () => {
716
+ const denseVector = [0.1, 0.2, 0.3];
717
+ const sparseVector = { indices: [1], values: [0.5] };
718
+
719
+ mockClient.query.mockResolvedValue({
720
+ points: [{ id: 1, score: 0.95, payload: null }],
721
+ });
722
+
723
+ const results = await manager.hybridSearch("test-collection", denseVector, sparseVector);
724
+
725
+ expect(results).toEqual([{ id: 1, score: 0.95, payload: undefined }]);
726
+ });
727
+
728
+ it("should throw error with error.data.status.error message", async () => {
729
+ const denseVector = [0.1, 0.2, 0.3];
730
+ const sparseVector = { indices: [1], values: [0.5] };
731
+
732
+ mockClient.query.mockRejectedValue({
733
+ data: {
734
+ status: {
735
+ error: "Named vector not found",
736
+ },
737
+ },
738
+ });
739
+
740
+ await expect(
741
+ manager.hybridSearch("test-collection", denseVector, sparseVector)
742
+ ).rejects.toThrow(
743
+ 'Hybrid search failed on collection "test-collection": Named vector not found'
744
+ );
745
+ });
746
+
747
+ it("should throw error with error.message fallback", async () => {
748
+ const denseVector = [0.1, 0.2, 0.3];
749
+ const sparseVector = { indices: [1], values: [0.5] };
750
+
751
+ mockClient.query.mockRejectedValue(new Error("Network timeout"));
752
+
753
+ await expect(
754
+ manager.hybridSearch("test-collection", denseVector, sparseVector)
755
+ ).rejects.toThrow('Hybrid search failed on collection "test-collection": Network timeout');
756
+ });
757
+
758
+ it("should throw error with String(error) fallback", async () => {
759
+ const denseVector = [0.1, 0.2, 0.3];
760
+ const sparseVector = { indices: [1], values: [0.5] };
761
+
762
+ mockClient.query.mockRejectedValue("Unknown error");
763
+
764
+ await expect(
765
+ manager.hybridSearch("test-collection", denseVector, sparseVector)
766
+ ).rejects.toThrow('Hybrid search failed on collection "test-collection": Unknown error');
767
+ });
768
+ });
769
+
770
+ describe("addPointsWithSparse", () => {
771
+ it("should add points with dense and sparse vectors", async () => {
772
+ const points = [
773
+ {
774
+ id: 1,
775
+ vector: [0.1, 0.2, 0.3],
776
+ sparseVector: { indices: [1, 5], values: [0.5, 0.3] },
777
+ payload: { text: "test" },
778
+ },
779
+ {
780
+ id: 2,
781
+ vector: [0.4, 0.5, 0.6],
782
+ sparseVector: { indices: [2, 8], values: [0.4, 0.6] },
783
+ payload: { text: "test2" },
784
+ },
785
+ ];
786
+
787
+ await manager.addPointsWithSparse("test-collection", points);
788
+
789
+ expect(mockClient.upsert).toHaveBeenCalledWith("test-collection", {
790
+ wait: true,
791
+ points: [
792
+ {
793
+ id: 1,
794
+ vector: {
795
+ dense: [0.1, 0.2, 0.3],
796
+ text: { indices: [1, 5], values: [0.5, 0.3] },
797
+ },
798
+ payload: { text: "test" },
799
+ },
800
+ {
801
+ id: 2,
802
+ vector: {
803
+ dense: [0.4, 0.5, 0.6],
804
+ text: { indices: [2, 8], values: [0.4, 0.6] },
805
+ },
806
+ payload: { text: "test2" },
807
+ },
808
+ ],
809
+ });
810
+ });
811
+
812
+ it("should add points without payload", async () => {
813
+ const points = [
814
+ {
815
+ id: 1,
816
+ vector: [0.1, 0.2, 0.3],
817
+ sparseVector: { indices: [1], values: [0.5] },
818
+ },
819
+ ];
820
+
821
+ await manager.addPointsWithSparse("test-collection", points);
822
+
823
+ expect(mockClient.upsert).toHaveBeenCalledWith("test-collection", {
824
+ wait: true,
825
+ points: [
826
+ {
827
+ id: 1,
828
+ vector: {
829
+ dense: [0.1, 0.2, 0.3],
830
+ text: { indices: [1], values: [0.5] },
831
+ },
832
+ payload: undefined,
833
+ },
834
+ ],
835
+ });
836
+ });
837
+
838
+ it("should normalize string IDs to UUID format", async () => {
839
+ const points = [
840
+ {
841
+ id: "my-doc-id",
842
+ vector: [0.1, 0.2, 0.3],
843
+ sparseVector: { indices: [1], values: [0.5] },
844
+ payload: { text: "test" },
845
+ },
846
+ ];
847
+
848
+ await manager.addPointsWithSparse("test-collection", points);
849
+
850
+ const calls = mockClient.upsert.mock.calls;
851
+ expect(calls).toHaveLength(1);
852
+ expect(calls[0][0]).toBe("test-collection");
853
+
854
+ const normalizedId = calls[0][1].points[0].id;
855
+ // Check that it's a valid UUID format
856
+ expect(normalizedId).toMatch(
857
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
858
+ );
859
+ expect(normalizedId).not.toBe("my-doc-id");
860
+ });
861
+
862
+ it("should preserve UUID format IDs without modification", async () => {
863
+ const uuidId = "123e4567-e89b-12d3-a456-426614174000";
864
+ const points = [
865
+ {
866
+ id: uuidId,
867
+ vector: [0.1, 0.2, 0.3],
868
+ sparseVector: { indices: [1], values: [0.5] },
869
+ payload: { text: "test" },
870
+ },
871
+ ];
872
+
873
+ await manager.addPointsWithSparse("test-collection", points);
874
+
875
+ expect(mockClient.upsert).toHaveBeenCalledWith("test-collection", {
876
+ wait: true,
877
+ points: [
878
+ {
879
+ id: uuidId,
880
+ vector: {
881
+ dense: [0.1, 0.2, 0.3],
882
+ text: { indices: [1], values: [0.5] },
883
+ },
884
+ payload: { text: "test" },
885
+ },
886
+ ],
887
+ });
888
+ });
889
+
890
+ it("should throw error with error.data.status.error message", async () => {
891
+ const points = [
892
+ {
893
+ id: 1,
894
+ vector: [0.1, 0.2, 0.3],
895
+ sparseVector: { indices: [1], values: [0.5] },
896
+ },
897
+ ];
898
+
899
+ mockClient.upsert.mockRejectedValue({
900
+ data: {
901
+ status: {
902
+ error: "Sparse vector not configured",
903
+ },
904
+ },
905
+ });
906
+
907
+ await expect(manager.addPointsWithSparse("test-collection", points)).rejects.toThrow(
908
+ 'Failed to add points with sparse vectors to collection "test-collection": Sparse vector not configured'
909
+ );
910
+ });
911
+
912
+ it("should throw error with error.message fallback", async () => {
913
+ const points = [
914
+ {
915
+ id: 1,
916
+ vector: [0.1, 0.2, 0.3],
917
+ sparseVector: { indices: [1], values: [0.5] },
918
+ },
919
+ ];
920
+
921
+ mockClient.upsert.mockRejectedValue(new Error("Connection refused"));
922
+
923
+ await expect(manager.addPointsWithSparse("test-collection", points)).rejects.toThrow(
924
+ 'Failed to add points with sparse vectors to collection "test-collection": Connection refused'
925
+ );
926
+ });
927
+
928
+ it("should throw error with String(error) fallback", async () => {
929
+ const points = [
930
+ {
931
+ id: 1,
932
+ vector: [0.1, 0.2, 0.3],
933
+ sparseVector: { indices: [1], values: [0.5] },
934
+ },
935
+ ];
936
+
937
+ mockClient.upsert.mockRejectedValue("Unexpected error");
938
+
939
+ await expect(manager.addPointsWithSparse("test-collection", points)).rejects.toThrow(
940
+ 'Failed to add points with sparse vectors to collection "test-collection": Unexpected error'
941
+ );
942
+ });
943
+ });
456
944
  });