项目文件夹

文件
wehub-resource-sync 2771cff92b
CI / lint (push) Failing after 1s
CI / typecheck (push) Failing after 2s
CI / test (push) Failing after 1s
CI / build (push) Has been skipped
chore: import upstream snapshot with attribution
2026-07-13 12:22:56 +08:00

187 行
6.4 KiB
TypeScript

import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { type ExternalContextFile,externalContextScanner } from '@/utils/externalContextScanner';
describe('externalContextScanner', () => {
let tempDir: string;
beforeEach(() => {
// Create a temp directory for testing
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'claudian-test-'));
// Create test file structure
fs.mkdirSync(path.join(tempDir, 'subdir'));
fs.writeFileSync(path.join(tempDir, 'file1.txt'), 'content1');
fs.writeFileSync(path.join(tempDir, 'file2.md'), 'content2');
fs.writeFileSync(path.join(tempDir, 'subdir', 'file3.ts'), 'content3');
// Create hidden file (should be skipped)
fs.writeFileSync(path.join(tempDir, '.hidden'), 'hidden');
// Clear cache before each test
externalContextScanner.invalidateCache();
});
afterEach(() => {
// Clean up temp directory
fs.rmSync(tempDir, { recursive: true, force: true });
});
describe('scanPaths', () => {
it('should scan directory and return files', () => {
const files = externalContextScanner.scanPaths([tempDir]);
expect(files.length).toBe(3);
expect(files.map((f: ExternalContextFile) => f.name).sort()).toEqual(['file1.txt', 'file2.md', 'file3.ts']);
});
it('should include file metadata', () => {
const files = externalContextScanner.scanPaths([tempDir]);
const file1 = files.find((f: ExternalContextFile) => f.name === 'file1.txt');
expect(file1).toBeDefined();
expect(file1!.path).toBe(path.join(tempDir, 'file1.txt'));
expect(file1!.relativePath).toBe('file1.txt');
expect(file1!.contextRoot).toBe(tempDir);
expect(file1!.mtime).toBeGreaterThan(0);
});
it('should include files in subdirectories', () => {
const files = externalContextScanner.scanPaths([tempDir]);
const file3 = files.find((f: ExternalContextFile) => f.name === 'file3.ts');
expect(file3).toBeDefined();
expect(file3!.relativePath).toBe(path.join('subdir', 'file3.ts'));
});
it('should skip hidden files', () => {
const files = externalContextScanner.scanPaths([tempDir]);
const hidden = files.find((f: ExternalContextFile) => f.name === '.hidden');
expect(hidden).toBeUndefined();
});
it('should skip hidden directories', () => {
// Create a hidden directory with a file
const hiddenDir = path.join(tempDir, '.hidden-dir');
fs.mkdirSync(hiddenDir);
fs.writeFileSync(path.join(hiddenDir, 'secret.txt'), 'secret');
const files = externalContextScanner.scanPaths([tempDir]);
const secret = files.find((f: ExternalContextFile) => f.name === 'secret.txt');
expect(secret).toBeUndefined();
});
it('should skip node_modules', () => {
// Create node_modules with a file
const nodeModules = path.join(tempDir, 'node_modules');
fs.mkdirSync(nodeModules);
fs.writeFileSync(path.join(nodeModules, 'package.json'), '{}');
const files = externalContextScanner.scanPaths([tempDir]);
const pkg = files.find((f: ExternalContextFile) => f.name === 'package.json');
expect(pkg).toBeUndefined();
});
it('should handle non-existent paths', () => {
const files = externalContextScanner.scanPaths(['/non/existent/path']);
expect(files).toEqual([]);
});
it('should handle multiple external context paths', () => {
// Create a second temp directory
const tempDir2 = fs.mkdtempSync(path.join(os.tmpdir(), 'claudian-test2-'));
fs.writeFileSync(path.join(tempDir2, 'file4.js'), 'content4');
try {
const files = externalContextScanner.scanPaths([tempDir, tempDir2]);
expect(files.length).toBe(4);
expect(files.map((f: ExternalContextFile) => f.name).sort()).toEqual([
'file1.txt',
'file2.md',
'file3.ts',
'file4.js',
]);
} finally {
fs.rmSync(tempDir2, { recursive: true, force: true });
}
});
});
describe('caching', () => {
it('should cache results', () => {
// First scan
const files1 = externalContextScanner.scanPaths([tempDir]);
// Add a new file
fs.writeFileSync(path.join(tempDir, 'new-file.txt'), 'new content');
// Second scan should use cache
const files2 = externalContextScanner.scanPaths([tempDir]);
expect(files1.length).toBe(files2.length);
expect(files2.find((f: ExternalContextFile) => f.name === 'new-file.txt')).toBeUndefined();
});
it('should respect cache invalidation', () => {
// First scan
externalContextScanner.scanPaths([tempDir]);
// Add a new file
fs.writeFileSync(path.join(tempDir, 'new-file.txt'), 'new content');
// Invalidate cache
externalContextScanner.invalidateCache();
// Second scan should see new file
const files = externalContextScanner.scanPaths([tempDir]);
expect(files.find((f: ExternalContextFile) => f.name === 'new-file.txt')).toBeDefined();
});
it('should invalidate specific path', () => {
// Create a second temp directory
const tempDir2 = fs.mkdtempSync(path.join(os.tmpdir(), 'claudian-test2-'));
fs.writeFileSync(path.join(tempDir2, 'file4.js'), 'content4');
try {
// First scan both paths
externalContextScanner.scanPaths([tempDir, tempDir2]);
// Add files to both
fs.writeFileSync(path.join(tempDir, 'new1.txt'), 'new1');
fs.writeFileSync(path.join(tempDir2, 'new2.txt'), 'new2');
// Invalidate only first path
externalContextScanner.invalidatePath(tempDir);
// Second scan
const files = externalContextScanner.scanPaths([tempDir, tempDir2]);
// Should see new file in first path (invalidated)
expect(files.find((f: ExternalContextFile) => f.name === 'new1.txt')).toBeDefined();
// Should NOT see new file in second path (still cached)
expect(files.find((f: ExternalContextFile) => f.name === 'new2.txt')).toBeUndefined();
} finally {
fs.rmSync(tempDir2, { recursive: true, force: true });
}
});
});
describe('home path expansion', () => {
it('should expand home paths', () => {
// This test uses a real path that should exist
const files = externalContextScanner.scanPaths(['~']);
// Should not throw and should return some files (or empty if home is empty)
expect(Array.isArray(files)).toBe(true);
});
});
});