test: 补充单元测试提升代码覆盖率

新增测试文件:
- agent/executor-extended.test.ts, presets/
- context/manager-extended.test.ts
- core/agent.test.ts, providers.test.ts
- lsp/cli.test.ts, client-extended.test.ts, index.test.ts
- permission/file-prompt.test.ts, prompt.test.ts
- skills/builtin/
- tools/filesystem/write_file-extended.test.ts
- tools/git/git_commit-extended.test.ts
- tools/load_description.test.ts
- tools/todo/todo-manager.test.ts
- tools/tool-search.test.ts
- types/
- utils/config-extended.test.ts, diff-extended.test.ts

修改现有测试:
- agent/manager.test.ts
- tools/skill/skill.test.ts
- utils/config.test.ts, diff.test.ts, image.test.ts
This commit is contained in:
2025-12-11 20:37:03 +08:00
parent f8b0cd4bec
commit bca19b7741
24 changed files with 6347 additions and 4 deletions
+226
View File
@@ -0,0 +1,226 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { promptPermission, showPermissionDenied, showPermissionAllowed } from '../../../src/permission/prompt.js';
import type { PermissionContext } from '../../../src/permission/types.js';
// Mock readline
vi.mock('readline', () => ({
createInterface: vi.fn(() => ({
question: vi.fn(),
close: vi.fn(),
})),
}));
// Mock chalk
vi.mock('chalk', () => ({
default: {
yellow: (s: string) => s,
cyan: (s: string) => s,
white: (s: string) => s,
gray: (s: string) => s,
red: (s: string) => s,
green: (s: string) => s,
},
}));
import * as readline from 'readline';
describe('Permission Prompt - 权限提示模块', () => {
let consoleLogSpy: ReturnType<typeof vi.spyOn>;
beforeEach(() => {
vi.clearAllMocks();
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
});
afterEach(() => {
consoleLogSpy.mockRestore();
});
describe('showPermissionDenied - 显示权限被拒绝', () => {
it('显示命令和原因', () => {
showPermissionDenied('rm -rf /', '危险命令');
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('权限被拒绝'));
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('rm -rf /'));
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('危险命令'));
});
it('输出包含空行', () => {
showPermissionDenied('test', 'reason');
// 第一个和最后一个调用是空行
expect(consoleLogSpy).toHaveBeenCalledWith('');
});
});
describe('showPermissionAllowed - 显示权限允许', () => {
it('显示执行的命令', () => {
showPermissionAllowed('npm install');
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('执行'));
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('npm install'));
});
});
describe('promptPermission - 交互式权限提示', () => {
const mockContext: PermissionContext = {
command: 'git push',
workdir: '/project',
toolName: 'bash',
};
it('用户输入 y 返回允许不记住', async () => {
const mockRl = {
question: vi.fn((_, callback) => callback('y')),
close: vi.fn(),
};
vi.mocked(readline.createInterface).mockReturnValue(mockRl as any);
const result = await promptPermission(mockContext);
expect(result).toEqual({ allow: true, remember: false });
expect(mockRl.close).toHaveBeenCalled();
});
it('用户输入 Y 返回允许并记住', async () => {
const mockRl = {
question: vi.fn((_, callback) => callback('Y')),
close: vi.fn(),
};
vi.mocked(readline.createInterface).mockReturnValue(mockRl as any);
const result = await promptPermission(mockContext);
expect(result).toEqual({ allow: true, remember: true });
});
it('用户输入 n 返回拒绝不记住', async () => {
const mockRl = {
question: vi.fn((_, callback) => callback('n')),
close: vi.fn(),
};
vi.mocked(readline.createInterface).mockReturnValue(mockRl as any);
const result = await promptPermission(mockContext);
expect(result).toEqual({ allow: false, remember: false });
});
it('用户输入 N 返回拒绝并记住', async () => {
const mockRl = {
question: vi.fn((_, callback) => callback('N')),
close: vi.fn(),
};
vi.mocked(readline.createInterface).mockReturnValue(mockRl as any);
const result = await promptPermission(mockContext);
expect(result).toEqual({ allow: false, remember: true });
});
it('无效输入默认为拒绝', async () => {
const mockRl = {
question: vi.fn((_, callback) => callback('invalid')),
close: vi.fn(),
};
vi.mocked(readline.createInterface).mockReturnValue(mockRl as any);
const result = await promptPermission(mockContext);
expect(result).toEqual({ allow: false, remember: false });
});
it('空输入默认为拒绝', async () => {
const mockRl = {
question: vi.fn((_, callback) => callback('')),
close: vi.fn(),
};
vi.mocked(readline.createInterface).mockReturnValue(mockRl as any);
const result = await promptPermission(mockContext);
expect(result).toEqual({ allow: false, remember: false });
});
it('带空格的输入会被 trim', async () => {
const mockRl = {
question: vi.fn((_, callback) => callback(' y ')),
close: vi.fn(),
};
vi.mocked(readline.createInterface).mockReturnValue(mockRl as any);
const result = await promptPermission(mockContext);
expect(result).toEqual({ allow: true, remember: false });
});
it('显示命令和工作目录', async () => {
const mockRl = {
question: vi.fn((_, callback) => callback('y')),
close: vi.fn(),
};
vi.mocked(readline.createInterface).mockReturnValue(mockRl as any);
await promptPermission(mockContext);
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('git push'));
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('/project'));
});
it('显示外部路径警告', async () => {
const contextWithExternal: PermissionContext = {
...mockContext,
externalPaths: ['/etc/passwd', '/root/.ssh'],
};
const mockRl = {
question: vi.fn((_, callback) => callback('n')),
close: vi.fn(),
};
vi.mocked(readline.createInterface).mockReturnValue(mockRl as any);
await promptPermission(contextWithExternal);
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('项目目录外的路径'));
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('/etc/passwd'));
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('/root/.ssh'));
});
it('显示匹配模式', async () => {
const contextWithPatterns: PermissionContext = {
...mockContext,
patterns: ['*.js', '*.ts'],
};
const mockRl = {
question: vi.fn((_, callback) => callback('y')),
close: vi.fn(),
};
vi.mocked(readline.createInterface).mockReturnValue(mockRl as any);
await promptPermission(contextWithPatterns);
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('*.js'));
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining('*.ts'));
});
it('不显示空的外部路径', async () => {
const contextEmptyExternal: PermissionContext = {
...mockContext,
externalPaths: [],
};
const mockRl = {
question: vi.fn((_, callback) => callback('y')),
close: vi.fn(),
};
vi.mocked(readline.createInterface).mockReturnValue(mockRl as any);
await promptPermission(contextEmptyExternal);
// 不应该显示外部路径相关的警告
const calls = consoleLogSpy.mock.calls.flat().join('\n');
expect(calls).not.toContain('项目目录外的路径');
});
});
});