@mhalder/qdrant-mcp-server 2.1.1 → 2.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.
- package/CHANGELOG.md +11 -0
- package/README.md +5 -4
- package/build/code/indexer.d.ts +5 -0
- package/build/code/indexer.d.ts.map +1 -1
- package/build/code/indexer.js +137 -32
- package/build/code/indexer.js.map +1 -1
- package/build/code/types.d.ts +4 -0
- package/build/code/types.d.ts.map +1 -1
- package/build/qdrant/client.d.ts +5 -0
- package/build/qdrant/client.d.ts.map +1 -1
- package/build/qdrant/client.js +10 -0
- package/build/qdrant/client.js.map +1 -1
- package/build/qdrant/client.test.js +25 -0
- package/build/qdrant/client.test.js.map +1 -1
- package/build/tools/code.d.ts.map +1 -1
- package/build/tools/code.js +11 -1
- package/build/tools/code.js.map +1 -1
- package/examples/code-search/README.md +19 -4
- package/package.json +1 -1
- package/src/code/indexer.ts +210 -55
- package/src/code/types.ts +5 -0
- package/src/qdrant/client.test.ts +29 -0
- package/src/qdrant/client.ts +14 -0
- package/src/tools/code.ts +12 -1
- package/tests/code/chunker/tree-sitter-chunker.test.ts +87 -5
- package/tests/code/indexer.test.ts +533 -74
- package/tests/code/integration.test.ts +253 -54
- package/tests/code/scanner.test.ts +81 -6
- package/tests/code/sync/snapshot.test.ts +55 -4
- package/tests/code/sync/synchronizer.test.ts +86 -10
|
@@ -19,8 +19,8 @@ class MockQdrantManager implements Partial<QdrantManager> {
|
|
|
19
19
|
async createCollection(
|
|
20
20
|
name: string,
|
|
21
21
|
_vectorSize: number,
|
|
22
|
-
_distance:
|
|
23
|
-
_enableHybrid?: boolean
|
|
22
|
+
_distance: "Cosine" | "Euclid" | "Dot" = "Cosine",
|
|
23
|
+
_enableHybrid?: boolean,
|
|
24
24
|
): Promise<void> {
|
|
25
25
|
this.collections.set(name, {
|
|
26
26
|
vectorSize: _vectorSize,
|
|
@@ -36,10 +36,16 @@ class MockQdrantManager implements Partial<QdrantManager> {
|
|
|
36
36
|
|
|
37
37
|
async addPoints(collectionName: string, points: any[]): Promise<void> {
|
|
38
38
|
const existing = this.points.get(collectionName) || [];
|
|
39
|
-
|
|
39
|
+
// Upsert: remove existing points with same ID, then add new ones
|
|
40
|
+
const newIds = new Set(points.map((p) => p.id));
|
|
41
|
+
const filtered = existing.filter((p) => !newIds.has(p.id));
|
|
42
|
+
this.points.set(collectionName, [...filtered, ...points]);
|
|
40
43
|
}
|
|
41
44
|
|
|
42
|
-
async addPointsWithSparse(
|
|
45
|
+
async addPointsWithSparse(
|
|
46
|
+
collectionName: string,
|
|
47
|
+
points: any[],
|
|
48
|
+
): Promise<void> {
|
|
43
49
|
await this.addPoints(collectionName, points);
|
|
44
50
|
}
|
|
45
51
|
|
|
@@ -47,7 +53,7 @@ class MockQdrantManager implements Partial<QdrantManager> {
|
|
|
47
53
|
collectionName: string,
|
|
48
54
|
_vector: number[],
|
|
49
55
|
limit: number,
|
|
50
|
-
_filter?: any
|
|
56
|
+
_filter?: any,
|
|
51
57
|
): Promise<any[]> {
|
|
52
58
|
const points = this.points.get(collectionName) || [];
|
|
53
59
|
return points.slice(0, limit).map((p, idx) => ({
|
|
@@ -62,7 +68,7 @@ class MockQdrantManager implements Partial<QdrantManager> {
|
|
|
62
68
|
_vector: number[],
|
|
63
69
|
_sparseVector: any,
|
|
64
70
|
limit: number,
|
|
65
|
-
_filter?: any
|
|
71
|
+
_filter?: any,
|
|
66
72
|
): Promise<any[]> {
|
|
67
73
|
return this.search(collectionName, _vector, limit, _filter);
|
|
68
74
|
}
|
|
@@ -76,6 +82,35 @@ class MockQdrantManager implements Partial<QdrantManager> {
|
|
|
76
82
|
vectorSize: collection?.vectorSize || 384,
|
|
77
83
|
};
|
|
78
84
|
}
|
|
85
|
+
|
|
86
|
+
async getPoint(
|
|
87
|
+
collectionName: string,
|
|
88
|
+
id: string | number,
|
|
89
|
+
): Promise<{ id: string | number; payload?: Record<string, any> } | null> {
|
|
90
|
+
const points = this.points.get(collectionName) || [];
|
|
91
|
+
const point = points.find((p) => p.id === id);
|
|
92
|
+
if (!point) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
id: point.id,
|
|
97
|
+
payload: point.payload,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async deletePointsByFilter(
|
|
102
|
+
collectionName: string,
|
|
103
|
+
filter: Record<string, any>,
|
|
104
|
+
): Promise<void> {
|
|
105
|
+
const points = this.points.get(collectionName) || [];
|
|
106
|
+
const pathToDelete = filter?.must?.[0]?.match?.value;
|
|
107
|
+
if (pathToDelete) {
|
|
108
|
+
const filtered = points.filter(
|
|
109
|
+
(p) => p.payload?.relativePath !== pathToDelete,
|
|
110
|
+
);
|
|
111
|
+
this.points.set(collectionName, filtered);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
79
114
|
}
|
|
80
115
|
|
|
81
116
|
class MockEmbeddingProvider implements EmbeddingProvider {
|
|
@@ -83,12 +118,23 @@ class MockEmbeddingProvider implements EmbeddingProvider {
|
|
|
83
118
|
return 384;
|
|
84
119
|
}
|
|
85
120
|
|
|
86
|
-
|
|
87
|
-
return
|
|
121
|
+
getModel(): string {
|
|
122
|
+
return "mock-model";
|
|
88
123
|
}
|
|
89
124
|
|
|
90
|
-
async
|
|
91
|
-
|
|
125
|
+
async embed(
|
|
126
|
+
_text: string,
|
|
127
|
+
): Promise<{ embedding: number[]; dimensions: number }> {
|
|
128
|
+
return { embedding: new Array(384).fill(0.1), dimensions: 384 };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async embedBatch(
|
|
132
|
+
texts: string[],
|
|
133
|
+
): Promise<Array<{ embedding: number[]; dimensions: number }>> {
|
|
134
|
+
return texts.map(() => ({
|
|
135
|
+
embedding: new Array(384).fill(0.1),
|
|
136
|
+
dimensions: 384,
|
|
137
|
+
}));
|
|
92
138
|
}
|
|
93
139
|
}
|
|
94
140
|
|
|
@@ -104,7 +150,7 @@ describe("CodeIndexer", () => {
|
|
|
104
150
|
// Create temporary test directory
|
|
105
151
|
tempDir = join(
|
|
106
152
|
tmpdir(),
|
|
107
|
-
`qdrant-mcp-test-${Date.now()}-${Math.random().toString(36).substring(7)}
|
|
153
|
+
`qdrant-mcp-test-${Date.now()}-${Math.random().toString(36).substring(7)}`,
|
|
108
154
|
);
|
|
109
155
|
codebaseDir = join(tempDir, "codebase");
|
|
110
156
|
await fs.mkdir(codebaseDir, { recursive: true });
|
|
@@ -139,7 +185,7 @@ describe("CodeIndexer", () => {
|
|
|
139
185
|
await createTestFile(
|
|
140
186
|
codebaseDir,
|
|
141
187
|
"hello.ts",
|
|
142
|
-
'export function hello(name: string): string {\n console.log("Greeting user");\n return `Hello, ${name}!`;\n}'
|
|
188
|
+
'export function hello(name: string): string {\n console.log("Greeting user");\n return `Hello, ${name}!`;\n}',
|
|
143
189
|
);
|
|
144
190
|
|
|
145
191
|
const stats = await indexer.indexCodebase(codebaseDir);
|
|
@@ -173,7 +219,7 @@ describe("CodeIndexer", () => {
|
|
|
173
219
|
await createTestFile(
|
|
174
220
|
codebaseDir,
|
|
175
221
|
"test.ts",
|
|
176
|
-
"export const configuration = {\n apiKey: process.env.API_KEY,\n timeout: 5000\n};"
|
|
222
|
+
"export const configuration = {\n apiKey: process.env.API_KEY,\n timeout: 5000\n};",
|
|
177
223
|
);
|
|
178
224
|
|
|
179
225
|
const createCollectionSpy = vi.spyOn(qdrant, "createCollection");
|
|
@@ -184,7 +230,7 @@ describe("CodeIndexer", () => {
|
|
|
184
230
|
expect.stringContaining("code_"),
|
|
185
231
|
384,
|
|
186
232
|
"Cosine",
|
|
187
|
-
false
|
|
233
|
+
false,
|
|
188
234
|
);
|
|
189
235
|
});
|
|
190
236
|
|
|
@@ -192,7 +238,7 @@ describe("CodeIndexer", () => {
|
|
|
192
238
|
await createTestFile(
|
|
193
239
|
codebaseDir,
|
|
194
240
|
"test.ts",
|
|
195
|
-
"export function calculateTotal(items: number[]): number {\n return items.reduce((sum, item) => sum + item, 0);\n}"
|
|
241
|
+
"export function calculateTotal(items: number[]): number {\n return items.reduce((sum, item) => sum + item, 0);\n}",
|
|
196
242
|
);
|
|
197
243
|
|
|
198
244
|
await indexer.indexCodebase(codebaseDir);
|
|
@@ -207,7 +253,7 @@ describe("CodeIndexer", () => {
|
|
|
207
253
|
await createTestFile(
|
|
208
254
|
codebaseDir,
|
|
209
255
|
"test.ts",
|
|
210
|
-
"export interface User {\n id: string;\n name: string;\n email: string;\n}"
|
|
256
|
+
"export interface User {\n id: string;\n name: string;\n email: string;\n}",
|
|
211
257
|
);
|
|
212
258
|
|
|
213
259
|
const progressCallback = vi.fn();
|
|
@@ -215,8 +261,16 @@ describe("CodeIndexer", () => {
|
|
|
215
261
|
await indexer.indexCodebase(codebaseDir, undefined, progressCallback);
|
|
216
262
|
|
|
217
263
|
expect(progressCallback).toHaveBeenCalled();
|
|
218
|
-
expect(
|
|
219
|
-
|
|
264
|
+
expect(
|
|
265
|
+
progressCallback.mock.calls.some(
|
|
266
|
+
(call) => call[0].phase === "scanning",
|
|
267
|
+
),
|
|
268
|
+
).toBe(true);
|
|
269
|
+
expect(
|
|
270
|
+
progressCallback.mock.calls.some(
|
|
271
|
+
(call) => call[0].phase === "chunking",
|
|
272
|
+
),
|
|
273
|
+
).toBe(true);
|
|
220
274
|
});
|
|
221
275
|
|
|
222
276
|
it("should respect custom extensions", async () => {
|
|
@@ -234,7 +288,7 @@ describe("CodeIndexer", () => {
|
|
|
234
288
|
await createTestFile(
|
|
235
289
|
codebaseDir,
|
|
236
290
|
"secrets.ts",
|
|
237
|
-
'const apiKey = "sk_test_FAKE_KEY_FOR_TESTING_ONLY_NOT_REAL";'
|
|
291
|
+
'const apiKey = "sk_test_FAKE_KEY_FOR_TESTING_ONLY_NOT_REAL";',
|
|
238
292
|
);
|
|
239
293
|
|
|
240
294
|
const stats = await indexer.indexCodebase(codebaseDir);
|
|
@@ -245,12 +299,16 @@ describe("CodeIndexer", () => {
|
|
|
245
299
|
|
|
246
300
|
it("should enable hybrid search when configured", async () => {
|
|
247
301
|
const hybridConfig = { ...config, enableHybridSearch: true };
|
|
248
|
-
const hybridIndexer = new CodeIndexer(
|
|
302
|
+
const hybridIndexer = new CodeIndexer(
|
|
303
|
+
qdrant as any,
|
|
304
|
+
embeddings,
|
|
305
|
+
hybridConfig,
|
|
306
|
+
);
|
|
249
307
|
|
|
250
308
|
await createTestFile(
|
|
251
309
|
codebaseDir,
|
|
252
310
|
"test.ts",
|
|
253
|
-
"export class DataService {\n async fetchData(): Promise<any[]> {\n return [];\n }\n}"
|
|
311
|
+
"export class DataService {\n async fetchData(): Promise<any[]> {\n return [];\n }\n}",
|
|
254
312
|
);
|
|
255
313
|
|
|
256
314
|
const createCollectionSpy = vi.spyOn(qdrant, "createCollection");
|
|
@@ -261,7 +319,7 @@ describe("CodeIndexer", () => {
|
|
|
261
319
|
expect.stringContaining("code_"),
|
|
262
320
|
384,
|
|
263
321
|
"Cosine",
|
|
264
|
-
true
|
|
322
|
+
true,
|
|
265
323
|
);
|
|
266
324
|
});
|
|
267
325
|
|
|
@@ -270,16 +328,20 @@ describe("CodeIndexer", () => {
|
|
|
270
328
|
|
|
271
329
|
// Mock fs.readFile to throw error for this specific file
|
|
272
330
|
const originalReadFile = fs.readFile;
|
|
273
|
-
vi.spyOn(fs, "readFile").mockImplementation(
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
331
|
+
vi.spyOn(fs, "readFile").mockImplementation(
|
|
332
|
+
async (path: any, ...args: any[]) => {
|
|
333
|
+
if (path.includes("test.ts")) {
|
|
334
|
+
throw new Error("Permission denied");
|
|
335
|
+
}
|
|
336
|
+
return originalReadFile(path, ...args);
|
|
337
|
+
},
|
|
338
|
+
);
|
|
279
339
|
|
|
280
340
|
const stats = await indexer.indexCodebase(codebaseDir);
|
|
281
341
|
|
|
282
|
-
expect(stats.errors?.some((e) => e.includes("Permission denied"))).toBe(
|
|
342
|
+
expect(stats.errors?.some((e) => e.includes("Permission denied"))).toBe(
|
|
343
|
+
true,
|
|
344
|
+
);
|
|
283
345
|
|
|
284
346
|
vi.restoreAllMocks();
|
|
285
347
|
});
|
|
@@ -290,7 +352,7 @@ describe("CodeIndexer", () => {
|
|
|
290
352
|
await createTestFile(
|
|
291
353
|
codebaseDir,
|
|
292
354
|
`file${i}.ts`,
|
|
293
|
-
`export function test${i}() {\n console.log('Test function ${i}');\n return ${i};\n}
|
|
355
|
+
`export function test${i}() {\n console.log('Test function ${i}');\n return ${i};\n}`,
|
|
294
356
|
);
|
|
295
357
|
}
|
|
296
358
|
|
|
@@ -307,7 +369,7 @@ describe("CodeIndexer", () => {
|
|
|
307
369
|
await createTestFile(
|
|
308
370
|
codebaseDir,
|
|
309
371
|
"test.ts",
|
|
310
|
-
"export function hello(name: string): string {\n const greeting = `Hello, ${name}!`;\n return greeting;\n}"
|
|
372
|
+
"export function hello(name: string): string {\n const greeting = `Hello, ${name}!`;\n return greeting;\n}",
|
|
311
373
|
);
|
|
312
374
|
await indexer.indexCodebase(codebaseDir);
|
|
313
375
|
});
|
|
@@ -323,11 +385,15 @@ describe("CodeIndexer", () => {
|
|
|
323
385
|
const nonIndexedDir = join(tempDir, "non-indexed");
|
|
324
386
|
await fs.mkdir(nonIndexedDir, { recursive: true });
|
|
325
387
|
|
|
326
|
-
await expect(indexer.searchCode(nonIndexedDir, "test")).rejects.toThrow(
|
|
388
|
+
await expect(indexer.searchCode(nonIndexedDir, "test")).rejects.toThrow(
|
|
389
|
+
"not indexed",
|
|
390
|
+
);
|
|
327
391
|
});
|
|
328
392
|
|
|
329
393
|
it("should respect limit option", async () => {
|
|
330
|
-
const results = await indexer.searchCode(codebaseDir, "test", {
|
|
394
|
+
const results = await indexer.searchCode(codebaseDir, "test", {
|
|
395
|
+
limit: 2,
|
|
396
|
+
});
|
|
331
397
|
|
|
332
398
|
expect(results.length).toBeLessThanOrEqual(2);
|
|
333
399
|
});
|
|
@@ -356,7 +422,11 @@ describe("CodeIndexer", () => {
|
|
|
356
422
|
|
|
357
423
|
it("should use hybrid search when enabled", async () => {
|
|
358
424
|
const hybridConfig = { ...config, enableHybridSearch: true };
|
|
359
|
-
const hybridIndexer = new CodeIndexer(
|
|
425
|
+
const hybridIndexer = new CodeIndexer(
|
|
426
|
+
qdrant as any,
|
|
427
|
+
embeddings,
|
|
428
|
+
hybridConfig,
|
|
429
|
+
);
|
|
360
430
|
|
|
361
431
|
await createTestFile(
|
|
362
432
|
codebaseDir,
|
|
@@ -373,7 +443,7 @@ function performValidation(): boolean {
|
|
|
373
443
|
}
|
|
374
444
|
function checkStatus(): boolean {
|
|
375
445
|
return true;
|
|
376
|
-
}
|
|
446
|
+
}`,
|
|
377
447
|
);
|
|
378
448
|
// Force reindex to recreate collection with hybrid search enabled
|
|
379
449
|
await hybridIndexer.indexCodebase(codebaseDir, { forceReindex: true });
|
|
@@ -400,31 +470,287 @@ function checkStatus(): boolean {
|
|
|
400
470
|
});
|
|
401
471
|
|
|
402
472
|
describe("getIndexStatus", () => {
|
|
403
|
-
|
|
404
|
-
|
|
473
|
+
describe("not_indexed status", () => {
|
|
474
|
+
it("should return not_indexed for new codebase with no collection", async () => {
|
|
475
|
+
const status = await indexer.getIndexStatus(codebaseDir);
|
|
476
|
+
|
|
477
|
+
expect(status.isIndexed).toBe(false);
|
|
478
|
+
expect(status.status).toBe("not_indexed");
|
|
479
|
+
expect(status.collectionName).toBeUndefined();
|
|
480
|
+
expect(status.chunksCount).toBeUndefined();
|
|
481
|
+
});
|
|
405
482
|
|
|
406
|
-
|
|
483
|
+
it("should return not_indexed when collection exists but has no chunks and no completion marker", async () => {
|
|
484
|
+
// Create an empty directory with no supported files
|
|
485
|
+
const emptyDir = join(tempDir, "empty-codebase");
|
|
486
|
+
await fs.mkdir(emptyDir, { recursive: true });
|
|
487
|
+
// Create a non-supported file
|
|
488
|
+
await fs.writeFile(join(emptyDir, "readme.md"), "# README");
|
|
489
|
+
|
|
490
|
+
const stats = await indexer.indexCodebase(emptyDir);
|
|
491
|
+
|
|
492
|
+
// No files to index means no collection is created
|
|
493
|
+
expect(stats.filesScanned).toBe(0);
|
|
494
|
+
|
|
495
|
+
const status = await indexer.getIndexStatus(emptyDir);
|
|
496
|
+
expect(status.status).toBe("not_indexed");
|
|
497
|
+
expect(status.isIndexed).toBe(false);
|
|
498
|
+
});
|
|
407
499
|
});
|
|
408
500
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
501
|
+
describe("indexed status", () => {
|
|
502
|
+
it("should return indexed status after successful indexing", async () => {
|
|
503
|
+
await createTestFile(
|
|
504
|
+
codebaseDir,
|
|
505
|
+
"test.ts",
|
|
506
|
+
"export const APP_CONFIG = {\n port: 3000,\n host: 'localhost',\n debug: true,\n apiUrl: 'https://api.example.com',\n timeout: 5000\n};\nconsole.log('Config loaded');",
|
|
507
|
+
);
|
|
508
|
+
await indexer.indexCodebase(codebaseDir);
|
|
416
509
|
|
|
417
|
-
|
|
510
|
+
const status = await indexer.getIndexStatus(codebaseDir);
|
|
511
|
+
|
|
512
|
+
expect(status.isIndexed).toBe(true);
|
|
513
|
+
expect(status.status).toBe("indexed");
|
|
514
|
+
expect(status.collectionName).toBeDefined();
|
|
515
|
+
expect(status.chunksCount).toBeGreaterThan(0);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
it("should include lastUpdated timestamp after indexing", async () => {
|
|
519
|
+
await createTestFile(
|
|
520
|
+
codebaseDir,
|
|
521
|
+
"test.ts",
|
|
522
|
+
"export const value = 1;\nconsole.log('Testing timestamp');\nfunction test() { return value; }",
|
|
523
|
+
);
|
|
524
|
+
|
|
525
|
+
const beforeIndexing = new Date();
|
|
526
|
+
await indexer.indexCodebase(codebaseDir);
|
|
527
|
+
const afterIndexing = new Date();
|
|
528
|
+
|
|
529
|
+
const status = await indexer.getIndexStatus(codebaseDir);
|
|
530
|
+
|
|
531
|
+
expect(status.status).toBe("indexed");
|
|
532
|
+
expect(status.lastUpdated).toBeDefined();
|
|
533
|
+
expect(status.lastUpdated).toBeInstanceOf(Date);
|
|
534
|
+
expect(status.lastUpdated!.getTime()).toBeGreaterThanOrEqual(
|
|
535
|
+
beforeIndexing.getTime(),
|
|
536
|
+
);
|
|
537
|
+
expect(status.lastUpdated!.getTime()).toBeLessThanOrEqual(
|
|
538
|
+
afterIndexing.getTime(),
|
|
539
|
+
);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
it("should return correct chunks count excluding metadata point", async () => {
|
|
543
|
+
await createTestFile(
|
|
544
|
+
codebaseDir,
|
|
545
|
+
"test.ts",
|
|
546
|
+
"export const a = 1;\nexport const b = 2;\nconsole.log('File with content');\nfunction helper() { return a + b; }",
|
|
547
|
+
);
|
|
548
|
+
await indexer.indexCodebase(codebaseDir);
|
|
549
|
+
|
|
550
|
+
const status = await indexer.getIndexStatus(codebaseDir);
|
|
551
|
+
|
|
552
|
+
// Chunks count should not include the metadata point
|
|
553
|
+
expect(status.chunksCount).toBeGreaterThanOrEqual(0);
|
|
554
|
+
// The actual count depends on chunking, but it should be consistent
|
|
555
|
+
expect(typeof status.chunksCount).toBe("number");
|
|
556
|
+
});
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
describe("completion marker", () => {
|
|
560
|
+
it("should store completion marker when indexing completes", async () => {
|
|
561
|
+
await createTestFile(
|
|
562
|
+
codebaseDir,
|
|
563
|
+
"test.ts",
|
|
564
|
+
"export const data = { key: 'value' };\nconsole.log('Completion marker test');\nfunction process() { return data; }",
|
|
565
|
+
);
|
|
566
|
+
await indexer.indexCodebase(codebaseDir);
|
|
567
|
+
|
|
568
|
+
// Verify status is indexed (which means completion marker exists)
|
|
569
|
+
const status = await indexer.getIndexStatus(codebaseDir);
|
|
570
|
+
expect(status.status).toBe("indexed");
|
|
571
|
+
expect(status.isIndexed).toBe(true);
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
it("should store completion marker even when no chunks are created", async () => {
|
|
575
|
+
// Create a file that might produce no chunks (very small)
|
|
576
|
+
await createTestFile(codebaseDir, "tiny.ts", "const x = 1;");
|
|
577
|
+
|
|
578
|
+
await indexer.indexCodebase(codebaseDir);
|
|
579
|
+
|
|
580
|
+
const status = await indexer.getIndexStatus(codebaseDir);
|
|
581
|
+
|
|
582
|
+
// Even with minimal content, should be marked as indexed
|
|
583
|
+
expect(status.status).toBe("indexed");
|
|
584
|
+
expect(status.isIndexed).toBe(true);
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
it("should update completion marker on force reindex", async () => {
|
|
588
|
+
await createTestFile(
|
|
589
|
+
codebaseDir,
|
|
590
|
+
"test.ts",
|
|
591
|
+
"export const v1 = 'first';\nconsole.log('First indexing');\nfunction init() { return v1; }",
|
|
592
|
+
);
|
|
593
|
+
|
|
594
|
+
await indexer.indexCodebase(codebaseDir);
|
|
595
|
+
const status1 = await indexer.getIndexStatus(codebaseDir);
|
|
596
|
+
|
|
597
|
+
// Wait a tiny bit to ensure timestamp difference
|
|
598
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
599
|
+
|
|
600
|
+
// Force reindex
|
|
601
|
+
await indexer.indexCodebase(codebaseDir, { forceReindex: true });
|
|
602
|
+
const status2 = await indexer.getIndexStatus(codebaseDir);
|
|
603
|
+
|
|
604
|
+
expect(status2.status).toBe("indexed");
|
|
605
|
+
// Both should have lastUpdated
|
|
606
|
+
expect(status1.lastUpdated).toBeDefined();
|
|
607
|
+
expect(status2.lastUpdated).toBeDefined();
|
|
608
|
+
});
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
describe("backwards compatibility", () => {
|
|
612
|
+
it("should always return isIndexed boolean for backwards compatibility", async () => {
|
|
613
|
+
// Not indexed
|
|
614
|
+
let status = await indexer.getIndexStatus(codebaseDir);
|
|
615
|
+
expect(typeof status.isIndexed).toBe("boolean");
|
|
616
|
+
expect(status.isIndexed).toBe(false);
|
|
617
|
+
|
|
618
|
+
// Indexed
|
|
619
|
+
await createTestFile(
|
|
620
|
+
codebaseDir,
|
|
621
|
+
"test.ts",
|
|
622
|
+
"export const test = true;\nconsole.log('Backwards compat test');\nfunction run() { return test; }",
|
|
623
|
+
);
|
|
624
|
+
await indexer.indexCodebase(codebaseDir);
|
|
625
|
+
|
|
626
|
+
status = await indexer.getIndexStatus(codebaseDir);
|
|
627
|
+
expect(typeof status.isIndexed).toBe("boolean");
|
|
628
|
+
expect(status.isIndexed).toBe(true);
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
it("should have isIndexed=true only when status=indexed", async () => {
|
|
632
|
+
await createTestFile(
|
|
633
|
+
codebaseDir,
|
|
634
|
+
"test.ts",
|
|
635
|
+
"export const consistency = 1;\nconsole.log('Consistency check');\nfunction check() { return consistency; }",
|
|
636
|
+
);
|
|
637
|
+
await indexer.indexCodebase(codebaseDir);
|
|
638
|
+
|
|
639
|
+
const status = await indexer.getIndexStatus(codebaseDir);
|
|
640
|
+
|
|
641
|
+
// isIndexed should match status
|
|
642
|
+
if (status.status === "indexed") {
|
|
643
|
+
expect(status.isIndexed).toBe(true);
|
|
644
|
+
} else {
|
|
645
|
+
expect(status.isIndexed).toBe(false);
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
describe("indexing in-progress status", () => {
|
|
651
|
+
it("should return indexing status during active indexing", async () => {
|
|
652
|
+
// Create multiple files to ensure indexing takes some time
|
|
653
|
+
for (let i = 0; i < 5; i++) {
|
|
654
|
+
await createTestFile(
|
|
655
|
+
codebaseDir,
|
|
656
|
+
`file${i}.ts`,
|
|
657
|
+
`export const value${i} = ${i};\nconsole.log('Processing file ${i}');\nfunction process${i}(x: number) {\n const result = x * ${i};\n console.log('Result:', result);\n return result;\n}`,
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
let statusDuringIndexing: any = null;
|
|
662
|
+
|
|
663
|
+
// Use progress callback to check status mid-indexing
|
|
664
|
+
const indexingPromise = indexer.indexCodebase(
|
|
665
|
+
codebaseDir,
|
|
666
|
+
undefined,
|
|
667
|
+
async (progress) => {
|
|
668
|
+
// Check status during the embedding phase (when we know indexing is in progress)
|
|
669
|
+
if (
|
|
670
|
+
progress.phase === "embedding" &&
|
|
671
|
+
statusDuringIndexing === null
|
|
672
|
+
) {
|
|
673
|
+
statusDuringIndexing = await indexer.getIndexStatus(codebaseDir);
|
|
674
|
+
}
|
|
675
|
+
},
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
await indexingPromise;
|
|
679
|
+
|
|
680
|
+
// Verify we captured the in-progress status
|
|
681
|
+
if (statusDuringIndexing) {
|
|
682
|
+
expect(statusDuringIndexing.status).toBe("indexing");
|
|
683
|
+
expect(statusDuringIndexing.isIndexed).toBe(false);
|
|
684
|
+
expect(statusDuringIndexing.collectionName).toBeDefined();
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// After indexing completes, status should be "indexed"
|
|
688
|
+
const statusAfter = await indexer.getIndexStatus(codebaseDir);
|
|
689
|
+
expect(statusAfter.status).toBe("indexed");
|
|
690
|
+
expect(statusAfter.isIndexed).toBe(true);
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
it("should track chunksCount during indexing", async () => {
|
|
694
|
+
for (let i = 0; i < 3; i++) {
|
|
695
|
+
await createTestFile(
|
|
696
|
+
codebaseDir,
|
|
697
|
+
`module${i}.ts`,
|
|
698
|
+
`export class Module${i} {\n constructor() {\n console.log('Module ${i} initialized');\n }\n process(data: string): string {\n return data.toUpperCase();\n }\n}`,
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
let chunksCountDuringIndexing: number | undefined;
|
|
418
703
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
704
|
+
await indexer.indexCodebase(
|
|
705
|
+
codebaseDir,
|
|
706
|
+
undefined,
|
|
707
|
+
async (progress) => {
|
|
708
|
+
if (
|
|
709
|
+
progress.phase === "storing" &&
|
|
710
|
+
chunksCountDuringIndexing === undefined
|
|
711
|
+
) {
|
|
712
|
+
const status = await indexer.getIndexStatus(codebaseDir);
|
|
713
|
+
chunksCountDuringIndexing = status.chunksCount;
|
|
714
|
+
}
|
|
715
|
+
},
|
|
716
|
+
);
|
|
717
|
+
|
|
718
|
+
// chunksCount during indexing should be a number (could be 0 or more depending on progress)
|
|
719
|
+
if (chunksCountDuringIndexing !== undefined) {
|
|
720
|
+
expect(typeof chunksCountDuringIndexing).toBe("number");
|
|
721
|
+
expect(chunksCountDuringIndexing).toBeGreaterThanOrEqual(0);
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
describe("legacy collection handling", () => {
|
|
727
|
+
it("should treat collection with chunks but no completion marker as indexed", async () => {
|
|
728
|
+
// Simulate a legacy collection by directly adding points without completion marker
|
|
729
|
+
// First, index normally
|
|
730
|
+
await createTestFile(
|
|
731
|
+
codebaseDir,
|
|
732
|
+
"legacy.ts",
|
|
733
|
+
"export const legacy = true;\nconsole.log('Legacy test');\nfunction legacyFn() { return legacy; }",
|
|
734
|
+
);
|
|
735
|
+
await indexer.indexCodebase(codebaseDir);
|
|
736
|
+
|
|
737
|
+
// Get initial status to get collection name
|
|
738
|
+
const initialStatus = await indexer.getIndexStatus(codebaseDir);
|
|
739
|
+
expect(initialStatus.status).toBe("indexed");
|
|
740
|
+
|
|
741
|
+
// The mock should have stored the completion marker
|
|
742
|
+
// In a real scenario, legacy collections wouldn't have this marker
|
|
743
|
+
// but they would have chunks, and the code handles this gracefully
|
|
744
|
+
expect(initialStatus.chunksCount).toBeGreaterThanOrEqual(0);
|
|
745
|
+
});
|
|
422
746
|
});
|
|
423
747
|
});
|
|
424
748
|
|
|
425
749
|
describe("reindexChanges", () => {
|
|
426
750
|
it("should throw error if not previously indexed", async () => {
|
|
427
|
-
await expect(indexer.reindexChanges(codebaseDir)).rejects.toThrow(
|
|
751
|
+
await expect(indexer.reindexChanges(codebaseDir)).rejects.toThrow(
|
|
752
|
+
"not indexed",
|
|
753
|
+
);
|
|
428
754
|
});
|
|
429
755
|
|
|
430
756
|
it("should detect and index new files", async () => {
|
|
@@ -436,7 +762,7 @@ console.log('Initial file created');
|
|
|
436
762
|
function helper(param: string): boolean {
|
|
437
763
|
console.log('Processing:', param);
|
|
438
764
|
return true;
|
|
439
|
-
}
|
|
765
|
+
}`,
|
|
440
766
|
);
|
|
441
767
|
await indexer.indexCodebase(codebaseDir);
|
|
442
768
|
|
|
@@ -463,7 +789,7 @@ function helper(param: string): boolean {
|
|
|
463
789
|
" console.log('Valid input');",
|
|
464
790
|
" return input.length > 5;",
|
|
465
791
|
"}",
|
|
466
|
-
].join("\n")
|
|
792
|
+
].join("\n"),
|
|
467
793
|
);
|
|
468
794
|
|
|
469
795
|
const stats = await indexer.reindexChanges(codebaseDir);
|
|
@@ -476,14 +802,14 @@ function helper(param: string): boolean {
|
|
|
476
802
|
await createTestFile(
|
|
477
803
|
codebaseDir,
|
|
478
804
|
"test.ts",
|
|
479
|
-
"export const originalValue = 1;\nconsole.log('Original');"
|
|
805
|
+
"export const originalValue = 1;\nconsole.log('Original');",
|
|
480
806
|
);
|
|
481
807
|
await indexer.indexCodebase(codebaseDir);
|
|
482
808
|
|
|
483
809
|
await createTestFile(
|
|
484
810
|
codebaseDir,
|
|
485
811
|
"test.ts",
|
|
486
|
-
"export const updatedValue = 2;\nconsole.log('Updated');"
|
|
812
|
+
"export const updatedValue = 2;\nconsole.log('Updated');",
|
|
487
813
|
);
|
|
488
814
|
|
|
489
815
|
const stats = await indexer.reindexChanges(codebaseDir);
|
|
@@ -495,7 +821,7 @@ function helper(param: string): boolean {
|
|
|
495
821
|
await createTestFile(
|
|
496
822
|
codebaseDir,
|
|
497
823
|
"test.ts",
|
|
498
|
-
"export const toBeDeleted = 1;\nconsole.log('Will be deleted');"
|
|
824
|
+
"export const toBeDeleted = 1;\nconsole.log('Will be deleted');",
|
|
499
825
|
);
|
|
500
826
|
await indexer.indexCodebase(codebaseDir);
|
|
501
827
|
|
|
@@ -510,7 +836,7 @@ function helper(param: string): boolean {
|
|
|
510
836
|
await createTestFile(
|
|
511
837
|
codebaseDir,
|
|
512
838
|
"test.ts",
|
|
513
|
-
"export const unchangedValue = 1;\nconsole.log('No changes');"
|
|
839
|
+
"export const unchangedValue = 1;\nconsole.log('No changes');",
|
|
514
840
|
);
|
|
515
841
|
await indexer.indexCodebase(codebaseDir);
|
|
516
842
|
|
|
@@ -525,14 +851,14 @@ function helper(param: string): boolean {
|
|
|
525
851
|
await createTestFile(
|
|
526
852
|
codebaseDir,
|
|
527
853
|
"test.ts",
|
|
528
|
-
"export const existingValue = 1;\nconsole.log('Existing');"
|
|
854
|
+
"export const existingValue = 1;\nconsole.log('Existing');",
|
|
529
855
|
);
|
|
530
856
|
await indexer.indexCodebase(codebaseDir);
|
|
531
857
|
|
|
532
858
|
await createTestFile(
|
|
533
859
|
codebaseDir,
|
|
534
860
|
"new.ts",
|
|
535
|
-
"export const newValue = 2;\nconsole.log('New file');"
|
|
861
|
+
"export const newValue = 2;\nconsole.log('New file');",
|
|
536
862
|
);
|
|
537
863
|
|
|
538
864
|
const progressCallback = vi.fn();
|
|
@@ -540,6 +866,113 @@ function helper(param: string): boolean {
|
|
|
540
866
|
|
|
541
867
|
expect(progressCallback).toHaveBeenCalled();
|
|
542
868
|
});
|
|
869
|
+
|
|
870
|
+
it("should delete old chunks when file is modified", async () => {
|
|
871
|
+
await createTestFile(
|
|
872
|
+
codebaseDir,
|
|
873
|
+
"test.ts",
|
|
874
|
+
"export const originalValue = 1;\nconsole.log('Original version');",
|
|
875
|
+
);
|
|
876
|
+
await indexer.indexCodebase(codebaseDir);
|
|
877
|
+
|
|
878
|
+
const deletePointsByFilterSpy = vi.spyOn(qdrant, "deletePointsByFilter");
|
|
879
|
+
|
|
880
|
+
await createTestFile(
|
|
881
|
+
codebaseDir,
|
|
882
|
+
"test.ts",
|
|
883
|
+
"export const modifiedValue = 2;\nconsole.log('Modified version');",
|
|
884
|
+
);
|
|
885
|
+
|
|
886
|
+
await indexer.reindexChanges(codebaseDir);
|
|
887
|
+
|
|
888
|
+
expect(deletePointsByFilterSpy).toHaveBeenCalledWith(
|
|
889
|
+
expect.stringContaining("code_"),
|
|
890
|
+
{
|
|
891
|
+
must: [{ key: "relativePath", match: { value: "test.ts" } }],
|
|
892
|
+
},
|
|
893
|
+
);
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
it("should delete all chunks when file is deleted", async () => {
|
|
897
|
+
await createTestFile(
|
|
898
|
+
codebaseDir,
|
|
899
|
+
"test.ts",
|
|
900
|
+
"export const toDelete = 1;\nconsole.log('Will be deleted');",
|
|
901
|
+
);
|
|
902
|
+
await indexer.indexCodebase(codebaseDir);
|
|
903
|
+
|
|
904
|
+
const deletePointsByFilterSpy = vi.spyOn(qdrant, "deletePointsByFilter");
|
|
905
|
+
|
|
906
|
+
await fs.unlink(join(codebaseDir, "test.ts"));
|
|
907
|
+
|
|
908
|
+
await indexer.reindexChanges(codebaseDir);
|
|
909
|
+
|
|
910
|
+
expect(deletePointsByFilterSpy).toHaveBeenCalledWith(
|
|
911
|
+
expect.stringContaining("code_"),
|
|
912
|
+
{
|
|
913
|
+
must: [{ key: "relativePath", match: { value: "test.ts" } }],
|
|
914
|
+
},
|
|
915
|
+
);
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
it("should not affect chunks from unchanged files", async () => {
|
|
919
|
+
await createTestFile(
|
|
920
|
+
codebaseDir,
|
|
921
|
+
"unchanged.ts",
|
|
922
|
+
"export const unchanged = 1;\nconsole.log('Unchanged');",
|
|
923
|
+
);
|
|
924
|
+
await createTestFile(
|
|
925
|
+
codebaseDir,
|
|
926
|
+
"changed.ts",
|
|
927
|
+
"export const original = 2;\nconsole.log('Original');",
|
|
928
|
+
);
|
|
929
|
+
await indexer.indexCodebase(codebaseDir);
|
|
930
|
+
|
|
931
|
+
const deletePointsByFilterSpy = vi.spyOn(qdrant, "deletePointsByFilter");
|
|
932
|
+
|
|
933
|
+
await createTestFile(
|
|
934
|
+
codebaseDir,
|
|
935
|
+
"changed.ts",
|
|
936
|
+
"export const modified = 3;\nconsole.log('Modified');",
|
|
937
|
+
);
|
|
938
|
+
|
|
939
|
+
await indexer.reindexChanges(codebaseDir);
|
|
940
|
+
|
|
941
|
+
// Should only delete chunks for changed.ts, not unchanged.ts
|
|
942
|
+
expect(deletePointsByFilterSpy).toHaveBeenCalledTimes(1);
|
|
943
|
+
expect(deletePointsByFilterSpy).toHaveBeenCalledWith(
|
|
944
|
+
expect.stringContaining("code_"),
|
|
945
|
+
{
|
|
946
|
+
must: [{ key: "relativePath", match: { value: "changed.ts" } }],
|
|
947
|
+
},
|
|
948
|
+
);
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
it("should handle deletion errors gracefully", async () => {
|
|
952
|
+
await createTestFile(
|
|
953
|
+
codebaseDir,
|
|
954
|
+
"test.ts",
|
|
955
|
+
"export const original = 1;\nconsole.log('Original');",
|
|
956
|
+
);
|
|
957
|
+
await indexer.indexCodebase(codebaseDir);
|
|
958
|
+
|
|
959
|
+
// Mock deletePointsByFilter to throw an error
|
|
960
|
+
const deletePointsByFilterSpy = vi
|
|
961
|
+
.spyOn(qdrant, "deletePointsByFilter")
|
|
962
|
+
.mockRejectedValueOnce(new Error("Deletion failed"));
|
|
963
|
+
|
|
964
|
+
await createTestFile(
|
|
965
|
+
codebaseDir,
|
|
966
|
+
"test.ts",
|
|
967
|
+
"export const modified = 2;\nconsole.log('Modified');",
|
|
968
|
+
);
|
|
969
|
+
|
|
970
|
+
// Should not throw, should continue with reindexing
|
|
971
|
+
const stats = await indexer.reindexChanges(codebaseDir);
|
|
972
|
+
|
|
973
|
+
expect(deletePointsByFilterSpy).toHaveBeenCalled();
|
|
974
|
+
expect(stats.filesModified).toBe(1);
|
|
975
|
+
});
|
|
543
976
|
});
|
|
544
977
|
|
|
545
978
|
describe("path validation", () => {
|
|
@@ -568,7 +1001,7 @@ function helper(param: string): boolean {
|
|
|
568
1001
|
await createTestFile(
|
|
569
1002
|
codebaseDir,
|
|
570
1003
|
"test.ts",
|
|
571
|
-
"export const configValue = 1;\nconsole.log('Config loaded');"
|
|
1004
|
+
"export const configValue = 1;\nconsole.log('Config loaded');",
|
|
572
1005
|
);
|
|
573
1006
|
await indexer.indexCodebase(codebaseDir);
|
|
574
1007
|
|
|
@@ -586,7 +1019,7 @@ function helper(param: string): boolean {
|
|
|
586
1019
|
await createTestFile(
|
|
587
1020
|
codebaseDir,
|
|
588
1021
|
"test.ts",
|
|
589
|
-
"export const reindexValue = 1;\nconsole.log('Reindexing');"
|
|
1022
|
+
"export const reindexValue = 1;\nconsole.log('Reindexing');",
|
|
590
1023
|
);
|
|
591
1024
|
await indexer.indexCodebase(codebaseDir);
|
|
592
1025
|
|
|
@@ -599,7 +1032,9 @@ function helper(param: string): boolean {
|
|
|
599
1032
|
|
|
600
1033
|
describe("edge cases", () => {
|
|
601
1034
|
it("should handle nested directory structures", async () => {
|
|
602
|
-
await fs.mkdir(join(codebaseDir, "src", "components"), {
|
|
1035
|
+
await fs.mkdir(join(codebaseDir, "src", "components"), {
|
|
1036
|
+
recursive: true,
|
|
1037
|
+
});
|
|
603
1038
|
await createTestFile(
|
|
604
1039
|
codebaseDir,
|
|
605
1040
|
"src/components/Button.ts",
|
|
@@ -609,7 +1044,7 @@ function helper(param: string): boolean {
|
|
|
609
1044
|
console.log('Button clicked');
|
|
610
1045
|
};
|
|
611
1046
|
return '<button>Click me</button>';
|
|
612
|
-
}
|
|
1047
|
+
}`,
|
|
613
1048
|
);
|
|
614
1049
|
|
|
615
1050
|
const stats = await indexer.indexCodebase(codebaseDir);
|
|
@@ -618,7 +1053,11 @@ function helper(param: string): boolean {
|
|
|
618
1053
|
});
|
|
619
1054
|
|
|
620
1055
|
it("should handle files with unicode content", async () => {
|
|
621
|
-
await createTestFile(
|
|
1056
|
+
await createTestFile(
|
|
1057
|
+
codebaseDir,
|
|
1058
|
+
"test.ts",
|
|
1059
|
+
"const greeting = '你好世界';",
|
|
1060
|
+
);
|
|
622
1061
|
|
|
623
1062
|
const stats = await indexer.indexCodebase(codebaseDir);
|
|
624
1063
|
|
|
@@ -671,12 +1110,19 @@ function helper(param: string): boolean {
|
|
|
671
1110
|
...config,
|
|
672
1111
|
maxChunksPerFile: 2,
|
|
673
1112
|
};
|
|
674
|
-
const limitedIndexer = new CodeIndexer(
|
|
1113
|
+
const limitedIndexer = new CodeIndexer(
|
|
1114
|
+
qdrant as any,
|
|
1115
|
+
embeddings,
|
|
1116
|
+
limitedConfig,
|
|
1117
|
+
);
|
|
675
1118
|
|
|
676
1119
|
// Create a large file that would generate many chunks
|
|
677
1120
|
const largeContent = Array(50)
|
|
678
1121
|
.fill(null)
|
|
679
|
-
.map(
|
|
1122
|
+
.map(
|
|
1123
|
+
(_, i) =>
|
|
1124
|
+
`function test${i}() { console.log('test ${i}'); return ${i}; }`,
|
|
1125
|
+
)
|
|
680
1126
|
.join("\n\n");
|
|
681
1127
|
|
|
682
1128
|
await createTestFile(codebaseDir, "large.ts", largeContent);
|
|
@@ -693,14 +1139,18 @@ function helper(param: string): boolean {
|
|
|
693
1139
|
...config,
|
|
694
1140
|
maxTotalChunks: 3,
|
|
695
1141
|
};
|
|
696
|
-
const limitedIndexer = new CodeIndexer(
|
|
1142
|
+
const limitedIndexer = new CodeIndexer(
|
|
1143
|
+
qdrant as any,
|
|
1144
|
+
embeddings,
|
|
1145
|
+
limitedConfig,
|
|
1146
|
+
);
|
|
697
1147
|
|
|
698
1148
|
// Create multiple files
|
|
699
1149
|
for (let i = 0; i < 10; i++) {
|
|
700
1150
|
await createTestFile(
|
|
701
1151
|
codebaseDir,
|
|
702
1152
|
`file${i}.ts`,
|
|
703
|
-
`export function func${i}() { console.log('function ${i}'); return ${i}; }
|
|
1153
|
+
`export function func${i}() { console.log('function ${i}'); return ${i}; }`,
|
|
704
1154
|
);
|
|
705
1155
|
}
|
|
706
1156
|
|
|
@@ -716,7 +1166,11 @@ function helper(param: string): boolean {
|
|
|
716
1166
|
...config,
|
|
717
1167
|
maxTotalChunks: 1,
|
|
718
1168
|
};
|
|
719
|
-
const limitedIndexer = new CodeIndexer(
|
|
1169
|
+
const limitedIndexer = new CodeIndexer(
|
|
1170
|
+
qdrant as any,
|
|
1171
|
+
embeddings,
|
|
1172
|
+
limitedConfig,
|
|
1173
|
+
);
|
|
720
1174
|
|
|
721
1175
|
// Create a file with multiple chunks
|
|
722
1176
|
const content = `
|
|
@@ -751,7 +1205,7 @@ function third() {
|
|
|
751
1205
|
await createTestFile(
|
|
752
1206
|
codebaseDir,
|
|
753
1207
|
"file1.ts",
|
|
754
|
-
"export const initial = 1;\nconsole.log('Initial');"
|
|
1208
|
+
"export const initial = 1;\nconsole.log('Initial');",
|
|
755
1209
|
);
|
|
756
1210
|
await indexer.indexCodebase(codebaseDir);
|
|
757
1211
|
|
|
@@ -765,7 +1219,7 @@ console.log('Added file');
|
|
|
765
1219
|
export function process() {
|
|
766
1220
|
console.log('Processing');
|
|
767
1221
|
return true;
|
|
768
|
-
}
|
|
1222
|
+
}`,
|
|
769
1223
|
);
|
|
770
1224
|
|
|
771
1225
|
const progressUpdates: string[] = [];
|
|
@@ -793,7 +1247,11 @@ export function process() {
|
|
|
793
1247
|
// @ts-expect-error - Mocking for test
|
|
794
1248
|
fs.readFile = async (path: any, encoding: any) => {
|
|
795
1249
|
callCount++;
|
|
796
|
-
if (
|
|
1250
|
+
if (
|
|
1251
|
+
callCount === 1 &&
|
|
1252
|
+
typeof path === "string" &&
|
|
1253
|
+
path.endsWith("test.ts")
|
|
1254
|
+
) {
|
|
797
1255
|
// Throw a non-Error object
|
|
798
1256
|
throw "String error";
|
|
799
1257
|
}
|
|
@@ -805,10 +1263,11 @@ export function process() {
|
|
|
805
1263
|
|
|
806
1264
|
// Should handle the error gracefully
|
|
807
1265
|
expect(stats.status).toBe("completed");
|
|
808
|
-
expect(stats.errors?.some((e) => e.includes("String error"))).toBe(
|
|
1266
|
+
expect(stats.errors?.some((e) => e.includes("String error"))).toBe(
|
|
1267
|
+
true,
|
|
1268
|
+
);
|
|
809
1269
|
} finally {
|
|
810
1270
|
// Restore original function
|
|
811
|
-
// @ts-expect-error
|
|
812
1271
|
fs.readFile = originalReadFile;
|
|
813
1272
|
}
|
|
814
1273
|
});
|
|
@@ -819,7 +1278,7 @@ export function process() {
|
|
|
819
1278
|
async function createTestFile(
|
|
820
1279
|
baseDir: string,
|
|
821
1280
|
relativePath: string,
|
|
822
|
-
content: string
|
|
1281
|
+
content: string,
|
|
823
1282
|
): Promise<void> {
|
|
824
1283
|
const fullPath = join(baseDir, relativePath);
|
|
825
1284
|
const dir = join(fullPath, "..");
|