@mhalder/qdrant-mcp-server 1.4.0 → 1.6.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.
Files changed (92) hide show
  1. package/.codecov.yml +16 -0
  2. package/.github/workflows/claude-code-review.yml +6 -5
  3. package/.releaserc.json +8 -1
  4. package/CHANGELOG.md +34 -0
  5. package/README.md +259 -9
  6. package/build/code/chunker/base.d.ts +19 -0
  7. package/build/code/chunker/base.d.ts.map +1 -0
  8. package/build/code/chunker/base.js +5 -0
  9. package/build/code/chunker/base.js.map +1 -0
  10. package/build/code/chunker/character-chunker.d.ts +22 -0
  11. package/build/code/chunker/character-chunker.d.ts.map +1 -0
  12. package/build/code/chunker/character-chunker.js +111 -0
  13. package/build/code/chunker/character-chunker.js.map +1 -0
  14. package/build/code/chunker/tree-sitter-chunker.d.ts +29 -0
  15. package/build/code/chunker/tree-sitter-chunker.d.ts.map +1 -0
  16. package/build/code/chunker/tree-sitter-chunker.js +213 -0
  17. package/build/code/chunker/tree-sitter-chunker.js.map +1 -0
  18. package/build/code/config.d.ts +11 -0
  19. package/build/code/config.d.ts.map +1 -0
  20. package/build/code/config.js +145 -0
  21. package/build/code/config.js.map +1 -0
  22. package/build/code/indexer.d.ts +42 -0
  23. package/build/code/indexer.d.ts.map +1 -0
  24. package/build/code/indexer.js +508 -0
  25. package/build/code/indexer.js.map +1 -0
  26. package/build/code/metadata.d.ts +32 -0
  27. package/build/code/metadata.d.ts.map +1 -0
  28. package/build/code/metadata.js +128 -0
  29. package/build/code/metadata.js.map +1 -0
  30. package/build/code/scanner.d.ts +35 -0
  31. package/build/code/scanner.d.ts.map +1 -0
  32. package/build/code/scanner.js +108 -0
  33. package/build/code/scanner.js.map +1 -0
  34. package/build/code/sync/merkle.d.ts +45 -0
  35. package/build/code/sync/merkle.d.ts.map +1 -0
  36. package/build/code/sync/merkle.js +116 -0
  37. package/build/code/sync/merkle.js.map +1 -0
  38. package/build/code/sync/snapshot.d.ts +41 -0
  39. package/build/code/sync/snapshot.d.ts.map +1 -0
  40. package/build/code/sync/snapshot.js +91 -0
  41. package/build/code/sync/snapshot.js.map +1 -0
  42. package/build/code/sync/synchronizer.d.ts +53 -0
  43. package/build/code/sync/synchronizer.d.ts.map +1 -0
  44. package/build/code/sync/synchronizer.js +132 -0
  45. package/build/code/sync/synchronizer.js.map +1 -0
  46. package/build/code/types.d.ts +98 -0
  47. package/build/code/types.d.ts.map +1 -0
  48. package/build/code/types.js +5 -0
  49. package/build/code/types.js.map +1 -0
  50. package/build/index.js +252 -1
  51. package/build/index.js.map +1 -1
  52. package/build/qdrant/client.d.ts +1 -1
  53. package/build/qdrant/client.d.ts.map +1 -1
  54. package/build/qdrant/client.js +2 -2
  55. package/build/qdrant/client.js.map +1 -1
  56. package/build/qdrant/client.test.js +16 -0
  57. package/build/qdrant/client.test.js.map +1 -1
  58. package/examples/code-search/README.md +271 -0
  59. package/package.json +15 -2
  60. package/src/code/chunker/base.ts +22 -0
  61. package/src/code/chunker/character-chunker.ts +131 -0
  62. package/src/code/chunker/tree-sitter-chunker.ts +250 -0
  63. package/src/code/config.ts +156 -0
  64. package/src/code/indexer.ts +613 -0
  65. package/src/code/metadata.ts +153 -0
  66. package/src/code/scanner.ts +124 -0
  67. package/src/code/sync/merkle.ts +136 -0
  68. package/src/code/sync/snapshot.ts +110 -0
  69. package/src/code/sync/synchronizer.ts +154 -0
  70. package/src/code/types.ts +117 -0
  71. package/src/index.ts +298 -1
  72. package/src/qdrant/client.test.ts +20 -0
  73. package/src/qdrant/client.ts +2 -2
  74. package/tests/code/chunker/character-chunker.test.ts +141 -0
  75. package/tests/code/chunker/tree-sitter-chunker.test.ts +275 -0
  76. package/tests/code/fixtures/sample-py/calculator.py +32 -0
  77. package/tests/code/fixtures/sample-ts/async-operations.ts +120 -0
  78. package/tests/code/fixtures/sample-ts/auth.ts +31 -0
  79. package/tests/code/fixtures/sample-ts/config.ts +52 -0
  80. package/tests/code/fixtures/sample-ts/database.ts +50 -0
  81. package/tests/code/fixtures/sample-ts/index.ts +39 -0
  82. package/tests/code/fixtures/sample-ts/types-advanced.ts +132 -0
  83. package/tests/code/fixtures/sample-ts/utils.ts +105 -0
  84. package/tests/code/fixtures/sample-ts/validator.ts +169 -0
  85. package/tests/code/indexer.test.ts +828 -0
  86. package/tests/code/integration.test.ts +708 -0
  87. package/tests/code/metadata.test.ts +457 -0
  88. package/tests/code/scanner.test.ts +131 -0
  89. package/tests/code/sync/merkle.test.ts +406 -0
  90. package/tests/code/sync/snapshot.test.ts +360 -0
  91. package/tests/code/sync/synchronizer.test.ts +501 -0
  92. package/vitest.config.ts +1 -0
@@ -0,0 +1,457 @@
1
+ import { beforeEach, describe, expect, it } from "vitest";
2
+ import { MetadataExtractor } from "../../src/code/metadata.js";
3
+ import type { CodeChunk } from "../../src/code/types.js";
4
+
5
+ describe("MetadataExtractor", () => {
6
+ let extractor: MetadataExtractor;
7
+
8
+ beforeEach(() => {
9
+ extractor = new MetadataExtractor();
10
+ });
11
+
12
+ describe("extractLanguage", () => {
13
+ it("should extract TypeScript from .ts extension", () => {
14
+ expect(extractor.extractLanguage("file.ts")).toBe("typescript");
15
+ });
16
+
17
+ it("should extract JavaScript from .js extension", () => {
18
+ expect(extractor.extractLanguage("file.js")).toBe("javascript");
19
+ });
20
+
21
+ it("should extract Python from .py extension", () => {
22
+ expect(extractor.extractLanguage("file.py")).toBe("python");
23
+ });
24
+
25
+ it("should extract Go from .go extension", () => {
26
+ expect(extractor.extractLanguage("file.go")).toBe("go");
27
+ });
28
+
29
+ it("should extract Rust from .rs extension", () => {
30
+ expect(extractor.extractLanguage("file.rs")).toBe("rust");
31
+ });
32
+
33
+ it("should extract Java from .java extension", () => {
34
+ expect(extractor.extractLanguage("file.java")).toBe("java");
35
+ });
36
+
37
+ it("should handle paths with multiple dots", () => {
38
+ expect(extractor.extractLanguage("my.file.test.ts")).toBe("typescript");
39
+ });
40
+
41
+ it("should return unknown for unrecognized extensions", () => {
42
+ expect(extractor.extractLanguage("file.xyz")).toBe("unknown");
43
+ });
44
+
45
+ it("should handle files without extensions", () => {
46
+ expect(extractor.extractLanguage("Makefile")).toBe("unknown");
47
+ });
48
+ });
49
+
50
+ describe("generateChunkId", () => {
51
+ it("should generate consistent IDs for same chunk", () => {
52
+ const chunk: CodeChunk = {
53
+ content: "function test() { return 1; }",
54
+ startLine: 1,
55
+ endLine: 3,
56
+ metadata: {
57
+ filePath: "/path/to/file.ts",
58
+ language: "typescript",
59
+ chunkIndex: 0,
60
+ chunkType: "function",
61
+ name: "test",
62
+ },
63
+ };
64
+
65
+ const id1 = extractor.generateChunkId(chunk);
66
+ const id2 = extractor.generateChunkId(chunk);
67
+
68
+ expect(id1).toBe(id2);
69
+ expect(id1).toMatch(/^chunk_[a-f0-9]{16}$/);
70
+ });
71
+
72
+ it("should generate different IDs for different content", () => {
73
+ const chunk1: CodeChunk = {
74
+ content: "function test1() {}",
75
+ startLine: 1,
76
+ endLine: 1,
77
+ metadata: {
78
+ filePath: "/path/to/file.ts",
79
+ language: "typescript",
80
+ chunkIndex: 0,
81
+ },
82
+ };
83
+
84
+ const chunk2: CodeChunk = {
85
+ content: "function test2() {}",
86
+ startLine: 1,
87
+ endLine: 1,
88
+ metadata: {
89
+ filePath: "/path/to/file.ts",
90
+ language: "typescript",
91
+ chunkIndex: 0,
92
+ },
93
+ };
94
+
95
+ expect(extractor.generateChunkId(chunk1)).not.toBe(extractor.generateChunkId(chunk2));
96
+ });
97
+
98
+ it("should generate different IDs for different file paths", () => {
99
+ const chunk1: CodeChunk = {
100
+ content: "function test() {}",
101
+ startLine: 1,
102
+ endLine: 1,
103
+ metadata: {
104
+ filePath: "/path/to/file1.ts",
105
+ language: "typescript",
106
+ chunkIndex: 0,
107
+ },
108
+ };
109
+
110
+ const chunk2: CodeChunk = {
111
+ content: "function test() {}",
112
+ startLine: 1,
113
+ endLine: 1,
114
+ metadata: {
115
+ filePath: "/path/to/file2.ts",
116
+ language: "typescript",
117
+ chunkIndex: 0,
118
+ },
119
+ };
120
+
121
+ expect(extractor.generateChunkId(chunk1)).not.toBe(extractor.generateChunkId(chunk2));
122
+ });
123
+
124
+ it("should generate different IDs for different line ranges", () => {
125
+ const chunk1: CodeChunk = {
126
+ content: "function test() {}",
127
+ startLine: 1,
128
+ endLine: 5,
129
+ metadata: {
130
+ filePath: "/path/to/file.ts",
131
+ language: "typescript",
132
+ chunkIndex: 0,
133
+ },
134
+ };
135
+
136
+ const chunk2: CodeChunk = {
137
+ content: "function test() {}",
138
+ startLine: 10,
139
+ endLine: 15,
140
+ metadata: {
141
+ filePath: "/path/to/file.ts",
142
+ language: "typescript",
143
+ chunkIndex: 0,
144
+ },
145
+ };
146
+
147
+ expect(extractor.generateChunkId(chunk1)).not.toBe(extractor.generateChunkId(chunk2));
148
+ });
149
+ });
150
+
151
+ describe("containsSecrets", () => {
152
+ it("should detect API keys", () => {
153
+ const code = 'const apiKey = "sk_live_1234567890abcdefghij";';
154
+ expect(extractor.containsSecrets(code)).toBe(true);
155
+ });
156
+
157
+ it("should detect passwords", () => {
158
+ const code = 'const password = "mySecretPass123!";';
159
+ expect(extractor.containsSecrets(code)).toBe(true);
160
+ });
161
+
162
+ it("should detect AWS access keys", () => {
163
+ const code = 'const key = "AKIAIOSFODNN7EXAMPLE";';
164
+ expect(extractor.containsSecrets(code)).toBe(true);
165
+ });
166
+
167
+ it("should detect private keys", () => {
168
+ const code = "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBg";
169
+ expect(extractor.containsSecrets(code)).toBe(true);
170
+ });
171
+
172
+ it("should detect tokens", () => {
173
+ const code = 'const token = "ghp_1234567890abcdefghijklmnopqrstuvwxyz";';
174
+ expect(extractor.containsSecrets(code)).toBe(true);
175
+ });
176
+
177
+ it("should not flag safe code", () => {
178
+ const code = `
179
+ function add(a: number, b: number): number {
180
+ return a + b;
181
+ }
182
+ `;
183
+ expect(extractor.containsSecrets(code)).toBe(false);
184
+ });
185
+
186
+ it("should not flag placeholder values", () => {
187
+ const code = 'const apiKey = "YOUR_API_KEY_HERE";';
188
+ expect(extractor.containsSecrets(code)).toBe(false);
189
+ });
190
+
191
+ it("should handle case insensitivity", () => {
192
+ const code = 'const API_KEY = "sk_live_1234567890abcdefghij";';
193
+ expect(extractor.containsSecrets(code)).toBe(true);
194
+ });
195
+ });
196
+
197
+ describe("extractImportsExports", () => {
198
+ it("should extract TypeScript imports", () => {
199
+ const code = `
200
+ import { foo, bar } from './module';
201
+ import defaultImport from './another';
202
+ import * as utils from './utils';
203
+ `;
204
+
205
+ const result = extractor.extractImportsExports(code, "typescript");
206
+ expect(result.imports).toContain("./module");
207
+ expect(result.imports).toContain("./another");
208
+ expect(result.imports).toContain("./utils");
209
+ });
210
+
211
+ it("should extract TypeScript exports", () => {
212
+ const code = `
213
+ export function foo() {}
214
+ export class Bar {}
215
+ export { baz } from './module';
216
+ export default MyClass;
217
+ `;
218
+
219
+ const result = extractor.extractImportsExports(code, "typescript");
220
+ expect(result.exports).toContain("foo");
221
+ expect(result.exports).toContain("Bar");
222
+ expect(result.exports).toContain("baz");
223
+ expect(result.exports).toContain("default");
224
+ });
225
+
226
+ it("should extract Python imports", () => {
227
+ const code = `
228
+ import os
229
+ import sys
230
+ from typing import List, Dict
231
+ from .module import MyClass
232
+ `;
233
+
234
+ const result = extractor.extractImportsExports(code, "python");
235
+ expect(result.imports).toContain("os");
236
+ expect(result.imports).toContain("sys");
237
+ expect(result.imports).toContain("typing");
238
+ expect(result.imports).toContain(".module");
239
+ });
240
+
241
+ it("should extract Python exports (defs and classes)", () => {
242
+ const code = `
243
+ def my_function():
244
+ pass
245
+
246
+ class MyClass:
247
+ pass
248
+ `;
249
+
250
+ const result = extractor.extractImportsExports(code, "python");
251
+ expect(result.exports).toContain("my_function");
252
+ expect(result.exports).toContain("MyClass");
253
+ });
254
+
255
+ it("should handle JavaScript require statements", () => {
256
+ const code = `
257
+ const fs = require('fs');
258
+ const { join } = require('path');
259
+ `;
260
+
261
+ const result = extractor.extractImportsExports(code, "javascript");
262
+ expect(result.imports).toContain("fs");
263
+ expect(result.imports).toContain("path");
264
+ });
265
+
266
+ it("should return empty arrays for unsupported languages", () => {
267
+ const code = "some random text";
268
+ const result = extractor.extractImportsExports(code, "unknown");
269
+ expect(result.imports).toEqual([]);
270
+ expect(result.exports).toEqual([]);
271
+ });
272
+
273
+ it("should handle empty code", () => {
274
+ const result = extractor.extractImportsExports("", "typescript");
275
+ expect(result.imports).toEqual([]);
276
+ expect(result.exports).toEqual([]);
277
+ });
278
+ });
279
+
280
+ describe("calculateComplexity", () => {
281
+ it("should calculate complexity for simple function", () => {
282
+ const code = `
283
+ function simple() {
284
+ return 1;
285
+ }
286
+ `;
287
+
288
+ const complexity = extractor.calculateComplexity(code);
289
+ expect(complexity).toBeGreaterThan(0);
290
+ expect(complexity).toBeLessThan(5);
291
+ });
292
+
293
+ it("should calculate higher complexity for branching", () => {
294
+ const simpleCode = "function simple() { return 1; }";
295
+ const complexCode = `
296
+ function complex(x) {
297
+ if (x > 0) {
298
+ if (x > 10) {
299
+ return 1;
300
+ } else {
301
+ return 2;
302
+ }
303
+ } else {
304
+ return 0;
305
+ }
306
+
307
+ for (let i = 0; i < x; i++) {
308
+ if (i % 2 === 0) {
309
+ continue;
310
+ }
311
+ }
312
+
313
+ while (x > 0) {
314
+ x--;
315
+ }
316
+ }
317
+ `;
318
+
319
+ const simpleComplexity = extractor.calculateComplexity(simpleCode);
320
+ const complexComplexity = extractor.calculateComplexity(complexCode);
321
+
322
+ expect(complexComplexity).toBeGreaterThan(simpleComplexity);
323
+ });
324
+
325
+ it("should count if statements", () => {
326
+ const code = `
327
+ function test() {
328
+ if (condition1) {}
329
+ if (condition2) {}
330
+ if (condition3) {}
331
+ }
332
+ `;
333
+
334
+ const complexity = extractor.calculateComplexity(code);
335
+ expect(complexity).toBeGreaterThanOrEqual(3);
336
+ });
337
+
338
+ it("should count loops", () => {
339
+ const code = `
340
+ function test() {
341
+ for (let i = 0; i < 10; i++) {}
342
+ while (true) {}
343
+ do {} while (false);
344
+ }
345
+ `;
346
+
347
+ const complexity = extractor.calculateComplexity(code);
348
+ expect(complexity).toBeGreaterThanOrEqual(3);
349
+ });
350
+
351
+ it("should count switch cases", () => {
352
+ const code = `
353
+ function test(x) {
354
+ switch (x) {
355
+ case 1:
356
+ break;
357
+ case 2:
358
+ break;
359
+ case 3:
360
+ break;
361
+ }
362
+ }
363
+ `;
364
+
365
+ const complexity = extractor.calculateComplexity(code);
366
+ expect(complexity).toBeGreaterThanOrEqual(3);
367
+ });
368
+
369
+ it("should count logical operators", () => {
370
+ const code = `
371
+ function test() {
372
+ if (a && b && c || d) {}
373
+ }
374
+ `;
375
+
376
+ const complexity = extractor.calculateComplexity(code);
377
+ expect(complexity).toBeGreaterThanOrEqual(2);
378
+ });
379
+
380
+ it("should count ternary operators", () => {
381
+ const code = `
382
+ function test() {
383
+ const x = condition ? 1 : 2;
384
+ const y = other ? 3 : 4;
385
+ }
386
+ `;
387
+
388
+ const complexity = extractor.calculateComplexity(code);
389
+ expect(complexity).toBeGreaterThanOrEqual(2);
390
+ });
391
+
392
+ it("should handle empty code", () => {
393
+ const complexity = extractor.calculateComplexity("");
394
+ expect(complexity).toBe(0);
395
+ });
396
+
397
+ it("should handle code with no control flow", () => {
398
+ const code = `
399
+ const x = 1;
400
+ const y = 2;
401
+ const z = x + y;
402
+ `;
403
+
404
+ const complexity = extractor.calculateComplexity(code);
405
+ expect(complexity).toBe(0);
406
+ });
407
+ });
408
+
409
+ describe("edge cases", () => {
410
+ it("should handle chunks with minimal metadata", () => {
411
+ const chunk: CodeChunk = {
412
+ content: "test",
413
+ startLine: 1,
414
+ endLine: 1,
415
+ metadata: {
416
+ filePath: "test.txt",
417
+ language: "unknown",
418
+ chunkIndex: 0,
419
+ },
420
+ };
421
+
422
+ const id = extractor.generateChunkId(chunk);
423
+ expect(id).toMatch(/^chunk_[a-f0-9]{16}$/);
424
+ });
425
+
426
+ it("should handle very long file paths", () => {
427
+ const longPath = `${"/very/long/path/".repeat(50)}file.ts`;
428
+ const language = extractor.extractLanguage(longPath);
429
+ expect(language).toBe("typescript");
430
+ });
431
+
432
+ it("should handle special characters in code", () => {
433
+ const code = `
434
+ const emoji = "🚀";
435
+ const unicode = "你好世界";
436
+ const special = "!@#$%^&*()";
437
+ `;
438
+
439
+ const secrets = extractor.containsSecrets(code);
440
+ expect(typeof secrets).toBe("boolean");
441
+ });
442
+
443
+ it("should handle multiline strings in secret detection", () => {
444
+ const code = `
445
+ const doc = \`
446
+ This is a multiline string
447
+ with api_key = "fake_key_value"
448
+ but it's just documentation
449
+ \`;
450
+ `;
451
+
452
+ // Should still detect the pattern
453
+ const hasSecrets = extractor.containsSecrets(code);
454
+ expect(typeof hasSecrets).toBe("boolean");
455
+ });
456
+ });
457
+ });
@@ -0,0 +1,131 @@
1
+ import { dirname, join } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { beforeEach, describe, expect, it } from "vitest";
4
+ import { FileScanner } from "../../src/code/scanner.js";
5
+ import type { ScannerConfig } from "../../src/code/types.js";
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const fixturesDir = join(__dirname, "fixtures");
9
+
10
+ describe("FileScanner", () => {
11
+ let scanner: FileScanner;
12
+ let config: ScannerConfig;
13
+
14
+ beforeEach(() => {
15
+ config = {
16
+ supportedExtensions: [".ts", ".js", ".py"],
17
+ ignorePatterns: ["node_modules/**", "dist/**"],
18
+ };
19
+ scanner = new FileScanner(config);
20
+ });
21
+
22
+ describe("scanDirectory", () => {
23
+ it("should find all supported files", async () => {
24
+ const files = await scanner.scanDirectory(join(fixturesDir, "sample-ts"));
25
+ expect(files.length).toBeGreaterThan(0);
26
+ expect(files.some((f) => f.endsWith("auth.ts"))).toBe(true);
27
+
28
+ // Verify new fixture files are found
29
+ expect(files.some((f) => f.endsWith("database.ts"))).toBe(true);
30
+ expect(files.some((f) => f.endsWith("utils.ts"))).toBe(true);
31
+ expect(files.some((f) => f.endsWith("validator.ts"))).toBe(true);
32
+ expect(files.some((f) => f.endsWith("config.ts"))).toBe(true);
33
+ expect(files.some((f) => f.endsWith("index.ts"))).toBe(true);
34
+ expect(files.some((f) => f.endsWith("async-operations.ts"))).toBe(true);
35
+ expect(files.some((f) => f.endsWith("types-advanced.ts"))).toBe(true);
36
+
37
+ // Should have at least 8 TypeScript files
38
+ expect(files.length).toBeGreaterThanOrEqual(8);
39
+ });
40
+
41
+ it("should respect supported extensions", async () => {
42
+ const files = await scanner.scanDirectory(join(fixturesDir, "sample-ts"));
43
+ files.forEach((file) => {
44
+ const hasValidExt = config.supportedExtensions.some((ext) => file.endsWith(ext));
45
+ expect(hasValidExt).toBe(true);
46
+ });
47
+ });
48
+
49
+ it("should handle empty directories", async () => {
50
+ const config: ScannerConfig = {
51
+ supportedExtensions: [".nonexistent"],
52
+ ignorePatterns: [],
53
+ };
54
+ const scanner = new FileScanner(config);
55
+ const files = await scanner.scanDirectory(join(fixturesDir, "sample-ts"));
56
+ expect(files).toEqual([]);
57
+ });
58
+ });
59
+
60
+ describe("loadIgnorePatterns", () => {
61
+ it("should load .gitignore patterns", async () => {
62
+ await scanner.loadIgnorePatterns(join(fixturesDir, "sample-ts"));
63
+ // .gitignore should be loaded, but we can't directly test internal state
64
+ // Instead, we test the effect through scanDirectory
65
+ const files = await scanner.scanDirectory(join(fixturesDir, "sample-ts"));
66
+ expect(files.some((f) => f.includes("node_modules"))).toBe(false);
67
+ });
68
+
69
+ it("should handle missing ignore files gracefully", async () => {
70
+ await expect(scanner.loadIgnorePatterns("/nonexistent/path")).resolves.not.toThrow();
71
+ });
72
+ });
73
+
74
+ describe("getSupportedExtensions", () => {
75
+ it("should return configured extensions", () => {
76
+ const extensions = scanner.getSupportedExtensions();
77
+ expect(extensions).toEqual([".ts", ".js", ".py"]);
78
+ });
79
+ });
80
+
81
+ describe("edge cases", () => {
82
+ it("should handle paths with special characters", async () => {
83
+ const files = await scanner.scanDirectory(join(fixturesDir, "sample-ts"));
84
+ expect(Array.isArray(files)).toBe(true);
85
+ });
86
+
87
+ it("should skip symbolic links", async () => {
88
+ const files = await scanner.scanDirectory(join(fixturesDir, "sample-ts"));
89
+ expect(Array.isArray(files)).toBe(true);
90
+ });
91
+
92
+ it("should handle custom ignore patterns", async () => {
93
+ const customConfig: ScannerConfig = {
94
+ supportedExtensions: [".ts", ".js"],
95
+ ignorePatterns: [],
96
+ customIgnorePatterns: ["**/*.test.ts"],
97
+ };
98
+ const customScanner = new FileScanner(customConfig);
99
+ await customScanner.loadIgnorePatterns(join(fixturesDir, "sample-ts"));
100
+ const files = await customScanner.scanDirectory(join(fixturesDir, "sample-ts"));
101
+ expect(files.some((f) => f.includes(".test.ts"))).toBe(false);
102
+ });
103
+
104
+ it("should properly ignore files matching ignore patterns", async () => {
105
+ const ignoreConfig: ScannerConfig = {
106
+ supportedExtensions: [".ts", ".js"],
107
+ ignorePatterns: ["**/auth.ts"],
108
+ };
109
+ const ignoreScanner = new FileScanner(ignoreConfig);
110
+ await ignoreScanner.loadIgnorePatterns(join(fixturesDir, "sample-ts"));
111
+ const files = await ignoreScanner.scanDirectory(join(fixturesDir, "sample-ts"));
112
+
113
+ // Should not include auth.ts due to ignore pattern
114
+ expect(files.some((f) => f.endsWith("auth.ts"))).toBe(false);
115
+ });
116
+
117
+ it("should handle directories with .gitignore", async () => {
118
+ const scannerWithGitignore = new FileScanner(config);
119
+ await scannerWithGitignore.loadIgnorePatterns(join(fixturesDir, "sample-ts"));
120
+ const files = await scannerWithGitignore.scanDirectory(join(fixturesDir, "sample-ts"));
121
+
122
+ // Files matching .gitignore patterns should be excluded
123
+ expect(Array.isArray(files)).toBe(true);
124
+ });
125
+
126
+ it("should gracefully handle non-existent directories", async () => {
127
+ const files = await scanner.scanDirectory("/nonexistent/directory/path");
128
+ expect(files).toEqual([]);
129
+ });
130
+ });
131
+ });