Skip to content

Commit 92065f3

Browse files
committed
Add tests and fix implementation
1 parent 9cc1933 commit 92065f3

2 files changed

Lines changed: 155 additions & 2 deletions

File tree

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';
2+
import * as fs from 'fs/promises';
3+
import * as path from 'path';
4+
import * as os from 'os';
5+
6+
// We need to test the buildTree function, but it's defined inside the request handler
7+
// So we'll extract the core logic into a testable function
8+
import { minimatch } from 'minimatch';
9+
10+
interface TreeEntry {
11+
name: string;
12+
type: 'file' | 'directory';
13+
children?: TreeEntry[];
14+
}
15+
16+
async function buildTreeForTesting(currentPath: string, rootPath: string, excludePatterns: string[] = []): Promise<TreeEntry[]> {
17+
const entries = await fs.readdir(currentPath, {withFileTypes: true});
18+
const result: TreeEntry[] = [];
19+
20+
for (const entry of entries) {
21+
const relativePath = path.relative(rootPath, path.join(currentPath, entry.name));
22+
const shouldExclude = excludePatterns.some(pattern => {
23+
if (pattern.includes('*')) {
24+
return minimatch(relativePath, pattern, {dot: true});
25+
}
26+
// For files: match exact name or as part of path
27+
// For directories: match as directory path
28+
return minimatch(relativePath, pattern, {dot: true}) ||
29+
minimatch(relativePath, `**/${pattern}`, {dot: true}) ||
30+
minimatch(relativePath, `**/${pattern}/**`, {dot: true});
31+
});
32+
if (shouldExclude)
33+
continue;
34+
35+
const entryData: TreeEntry = {
36+
name: entry.name,
37+
type: entry.isDirectory() ? 'directory' : 'file'
38+
};
39+
40+
if (entry.isDirectory()) {
41+
const subPath = path.join(currentPath, entry.name);
42+
entryData.children = await buildTreeForTesting(subPath, rootPath, excludePatterns);
43+
}
44+
45+
result.push(entryData);
46+
}
47+
48+
return result;
49+
}
50+
51+
describe('buildTree exclude patterns', () => {
52+
let testDir: string;
53+
54+
beforeEach(async () => {
55+
testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'filesystem-test-'));
56+
57+
// Create test directory structure
58+
await fs.mkdir(path.join(testDir, 'src'));
59+
await fs.mkdir(path.join(testDir, 'node_modules'));
60+
await fs.mkdir(path.join(testDir, '.git'));
61+
await fs.mkdir(path.join(testDir, 'nested', 'node_modules'), { recursive: true });
62+
63+
// Create test files
64+
await fs.writeFile(path.join(testDir, '.env'), 'SECRET=value');
65+
await fs.writeFile(path.join(testDir, '.env.local'), 'LOCAL_SECRET=value');
66+
await fs.writeFile(path.join(testDir, 'src', 'index.js'), 'console.log("hello");');
67+
await fs.writeFile(path.join(testDir, 'package.json'), '{}');
68+
await fs.writeFile(path.join(testDir, 'node_modules', 'module.js'), 'module.exports = {};');
69+
await fs.writeFile(path.join(testDir, 'nested', 'node_modules', 'deep.js'), 'module.exports = {};');
70+
});
71+
72+
afterEach(async () => {
73+
await fs.rm(testDir, { recursive: true, force: true });
74+
});
75+
76+
it('should exclude files matching simple patterns', async () => {
77+
// Test the current implementation - this will fail until the bug is fixed
78+
const tree = await buildTreeForTesting(testDir, testDir, ['.env']);
79+
const fileNames = tree.map(entry => entry.name);
80+
81+
expect(fileNames).not.toContain('.env');
82+
expect(fileNames).toContain('.env.local'); // Should not exclude this
83+
expect(fileNames).toContain('src');
84+
expect(fileNames).toContain('package.json');
85+
});
86+
87+
it('should exclude directories matching simple patterns', async () => {
88+
const tree = await buildTreeForTesting(testDir, testDir, ['node_modules']);
89+
const dirNames = tree.map(entry => entry.name);
90+
91+
expect(dirNames).not.toContain('node_modules');
92+
expect(dirNames).toContain('src');
93+
expect(dirNames).toContain('.git');
94+
});
95+
96+
it('should exclude nested directories with same pattern', async () => {
97+
const tree = await buildTreeForTesting(testDir, testDir, ['node_modules']);
98+
99+
// Find the nested directory
100+
const nestedDir = tree.find(entry => entry.name === 'nested');
101+
expect(nestedDir).toBeDefined();
102+
expect(nestedDir!.children).toBeDefined();
103+
104+
// The nested/node_modules should also be excluded
105+
const nestedChildren = nestedDir!.children!.map(child => child.name);
106+
expect(nestedChildren).not.toContain('node_modules');
107+
});
108+
109+
it('should handle glob patterns correctly', async () => {
110+
const tree = await buildTreeForTesting(testDir, testDir, ['*.env']);
111+
const fileNames = tree.map(entry => entry.name);
112+
113+
expect(fileNames).not.toContain('.env');
114+
expect(fileNames).toContain('.env.local'); // *.env should not match .env.local
115+
expect(fileNames).toContain('src');
116+
});
117+
118+
it('should handle dot files correctly', async () => {
119+
const tree = await buildTreeForTesting(testDir, testDir, ['.git']);
120+
const dirNames = tree.map(entry => entry.name);
121+
122+
expect(dirNames).not.toContain('.git');
123+
expect(dirNames).toContain('.env'); // Should not exclude this
124+
});
125+
126+
it('should work with multiple exclude patterns', async () => {
127+
const tree = await buildTreeForTesting(testDir, testDir, ['node_modules', '.env', '.git']);
128+
const entryNames = tree.map(entry => entry.name);
129+
130+
expect(entryNames).not.toContain('node_modules');
131+
expect(entryNames).not.toContain('.env');
132+
expect(entryNames).not.toContain('.git');
133+
expect(entryNames).toContain('src');
134+
expect(entryNames).toContain('package.json');
135+
});
136+
137+
it('should handle empty exclude patterns', async () => {
138+
const tree = await buildTreeForTesting(testDir, testDir, []);
139+
const entryNames = tree.map(entry => entry.name);
140+
141+
// All entries should be included
142+
expect(entryNames).toContain('node_modules');
143+
expect(entryNames).toContain('.env');
144+
expect(entryNames).toContain('.git');
145+
expect(entryNames).toContain('src');
146+
});
147+
});

src/filesystem/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -884,8 +884,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
884884
for (const entry of entries) {
885885
const relativePath = path.relative(rootPath, path.join(currentPath, entry.name));
886886
const shouldExclude = excludePatterns.some(pattern => {
887-
const globPattern = pattern.includes('*') ? pattern : `**/${pattern}/**`;
888-
return minimatch(relativePath, globPattern, {dot: true});
887+
if (pattern.includes('*')) {
888+
return minimatch(relativePath, pattern, {dot: true});
889+
}
890+
// For files: match exact name or as part of path
891+
// For directories: match as directory path
892+
return minimatch(relativePath, pattern, {dot: true}) ||
893+
minimatch(relativePath, `**/${pattern}`, {dot: true}) ||
894+
minimatch(relativePath, `**/${pattern}/**`, {dot: true});
889895
});
890896
if (shouldExclude)
891897
continue;

0 commit comments

Comments
 (0)