Files
qwen-code/packages/core/src/services/fileDiscoveryService.test.ts
2025-09-14 19:38:40 +08:00

212 lines
6.6 KiB
TypeScript

/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import * as fs from 'node:fs/promises';
import * as os from 'node:os';
import * as path from 'node:path';
import { FileDiscoveryService } from './fileDiscoveryService.js';
describe('FileDiscoveryService', () => {
let testRootDir: string;
let projectRoot: string;
async function createTestFile(filePath: string, content = '') {
const fullPath = path.join(projectRoot, filePath);
await fs.mkdir(path.dirname(fullPath), { recursive: true });
await fs.writeFile(fullPath, content);
return fullPath;
}
beforeEach(async () => {
testRootDir = await fs.mkdtemp(
path.join(os.tmpdir(), 'file-discovery-test-'),
);
projectRoot = path.join(testRootDir, 'project');
await fs.mkdir(projectRoot, { recursive: true });
});
afterEach(async () => {
await fs.rm(testRootDir, { recursive: true, force: true });
});
describe('initialization', () => {
it('should initialize git ignore parser by default in a git repo', async () => {
await fs.mkdir(path.join(projectRoot, '.git'));
await createTestFile('.gitignore', 'node_modules/');
const service = new FileDiscoveryService(projectRoot);
// Let's check the effect of the parser instead of mocking it.
expect(service.shouldGitIgnoreFile('node_modules/foo.js')).toBe(true);
expect(service.shouldGitIgnoreFile('src/foo.js')).toBe(false);
});
it('should not load git repo patterns when not in a git repo', async () => {
// No .git directory
await createTestFile('.gitignore', 'node_modules/');
const service = new FileDiscoveryService(projectRoot);
// .gitignore is not loaded in non-git repos
expect(service.shouldGitIgnoreFile('node_modules/foo.js')).toBe(false);
});
it('should load .qwenignore patterns even when not in a git repo', async () => {
await createTestFile('.qwenignore', 'secrets.txt');
const service = new FileDiscoveryService(projectRoot);
expect(service.shouldGeminiIgnoreFile('secrets.txt')).toBe(true);
expect(service.shouldGeminiIgnoreFile('src/index.js')).toBe(false);
});
});
describe('filterFiles', () => {
beforeEach(async () => {
await fs.mkdir(path.join(projectRoot, '.git'));
await createTestFile('.gitignore', 'node_modules/\n.git/\ndist');
await createTestFile('.qwenignore', 'logs/');
});
it('should filter out git-ignored and gemini-ignored files by default', () => {
const files = [
'src/index.ts',
'node_modules/package/index.js',
'README.md',
'.git/config',
'dist/bundle.js',
'logs/latest.log',
].map((f) => path.join(projectRoot, f));
const service = new FileDiscoveryService(projectRoot);
expect(service.filterFiles(files)).toEqual(
['src/index.ts', 'README.md'].map((f) => path.join(projectRoot, f)),
);
});
it('should not filter files when respectGitIgnore is false', () => {
const files = [
'src/index.ts',
'node_modules/package/index.js',
'.git/config',
'logs/latest.log',
].map((f) => path.join(projectRoot, f));
const service = new FileDiscoveryService(projectRoot);
const filtered = service.filterFiles(files, {
respectGitIgnore: false,
respectGeminiIgnore: true, // still respect this one
});
expect(filtered).toEqual(
['src/index.ts', 'node_modules/package/index.js', '.git/config'].map(
(f) => path.join(projectRoot, f),
),
);
});
it('should not filter files when respectGeminiIgnore is false', () => {
const files = [
'src/index.ts',
'node_modules/package/index.js',
'logs/latest.log',
].map((f) => path.join(projectRoot, f));
const service = new FileDiscoveryService(projectRoot);
const filtered = service.filterFiles(files, {
respectGitIgnore: true,
respectGeminiIgnore: false,
});
expect(filtered).toEqual(
['src/index.ts', 'logs/latest.log'].map((f) =>
path.join(projectRoot, f),
),
);
});
it('should handle empty file list', () => {
const service = new FileDiscoveryService(projectRoot);
expect(service.filterFiles([])).toEqual([]);
});
});
describe('shouldGitIgnoreFile & shouldGeminiIgnoreFile', () => {
beforeEach(async () => {
await fs.mkdir(path.join(projectRoot, '.git'));
await createTestFile('.gitignore', 'node_modules/');
await createTestFile('.qwenignore', '*.log');
});
it('should return true for git-ignored files', () => {
const service = new FileDiscoveryService(projectRoot);
expect(
service.shouldGitIgnoreFile(
path.join(projectRoot, 'node_modules/package/index.js'),
),
).toBe(true);
});
it('should return false for non-git-ignored files', () => {
const service = new FileDiscoveryService(projectRoot);
expect(
service.shouldGitIgnoreFile(path.join(projectRoot, 'src/index.ts')),
).toBe(false);
});
it('should return true for gemini-ignored files', () => {
const service = new FileDiscoveryService(projectRoot);
expect(
service.shouldGeminiIgnoreFile(path.join(projectRoot, 'debug.log')),
).toBe(true);
});
it('should return false for non-gemini-ignored files', () => {
const service = new FileDiscoveryService(projectRoot);
expect(
service.shouldGeminiIgnoreFile(path.join(projectRoot, 'src/index.ts')),
).toBe(false);
});
});
describe('edge cases', () => {
it('should handle relative project root paths', async () => {
await fs.mkdir(path.join(projectRoot, '.git'));
await createTestFile('.gitignore', 'ignored.txt');
const service = new FileDiscoveryService(
path.relative(process.cwd(), projectRoot),
);
expect(
service.shouldGitIgnoreFile(path.join(projectRoot, 'ignored.txt')),
).toBe(true);
expect(
service.shouldGitIgnoreFile(path.join(projectRoot, 'not-ignored.txt')),
).toBe(false);
});
it('should handle filterFiles with undefined options', async () => {
await fs.mkdir(path.join(projectRoot, '.git'));
await createTestFile('.gitignore', 'ignored.txt');
const service = new FileDiscoveryService(projectRoot);
const files = ['src/index.ts', 'ignored.txt'].map((f) =>
path.join(projectRoot, f),
);
expect(service.filterFiles(files, undefined)).toEqual([
path.join(projectRoot, 'src/index.ts'),
]);
});
});
});