test: 补充 task、copy_file、skill_search 工具测试
- task-extended.test.ts: 覆盖 Vision Agent、模型选择、后台运行 - copy_file-extended.test.ts: 覆盖递归复制、权限检查边界情况 - skill_search-extended.test.ts: 覆盖来源标记、未分类分组、参数显示 覆盖率提升: - task.ts: 97.91% - copy_file.ts: 100% - skill_search.ts: 98.61%
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
|
||||
// Mock fs/promises
|
||||
vi.mock('fs/promises', () => ({
|
||||
stat: vi.fn(),
|
||||
copyFile: vi.fn().mockResolvedValue(undefined),
|
||||
mkdir: vi.fn().mockResolvedValue(undefined),
|
||||
readdir: vi.fn().mockResolvedValue([]),
|
||||
}));
|
||||
|
||||
// 可变状态
|
||||
let mockCheckResults: Array<{
|
||||
allowed: boolean;
|
||||
action?: string;
|
||||
reason?: string;
|
||||
needsConfirmation?: boolean;
|
||||
}> = [];
|
||||
let checkCallIndex = 0;
|
||||
|
||||
// Mock permission manager
|
||||
vi.mock('../../../../src/permission/index.js', () => ({
|
||||
getPermissionManager: () => ({
|
||||
checkFilePermission: vi.fn(async () => {
|
||||
const result = mockCheckResults[checkCallIndex] || { allowed: true };
|
||||
checkCallIndex++;
|
||||
return result;
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
|
||||
// Mock loadDescription
|
||||
vi.mock('../../../../src/tools/load_description.js', () => ({
|
||||
loadDescription: vi.fn(() => '复制文件'),
|
||||
}));
|
||||
|
||||
import { copyFileTool } from '../../../../src/tools/filesystem/copy_file.js';
|
||||
import * as fs from 'fs/promises';
|
||||
|
||||
describe('copyFileTool - 扩展测试', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockCheckResults = [{ allowed: true }, { allowed: true }];
|
||||
checkCallIndex = 0;
|
||||
// 重置 mock 默认值
|
||||
vi.mocked(fs.stat).mockReset();
|
||||
vi.mocked(fs.copyFile).mockReset().mockResolvedValue(undefined);
|
||||
vi.mocked(fs.mkdir).mockReset().mockResolvedValue(undefined);
|
||||
vi.mocked(fs.readdir).mockReset().mockResolvedValue([]);
|
||||
});
|
||||
|
||||
describe('递归复制目录', () => {
|
||||
it('递归复制包含文件的目录', async () => {
|
||||
vi.mocked(fs.stat)
|
||||
.mockResolvedValueOnce({ isDirectory: () => true } as any)
|
||||
.mockRejectedValueOnce(new Error('ENOENT'))
|
||||
.mockResolvedValueOnce({ isDirectory: () => false } as any);
|
||||
|
||||
vi.mocked(fs.readdir).mockResolvedValueOnce(['file1.txt'] as any);
|
||||
|
||||
const result = await copyFileTool.execute({
|
||||
source: 'src_dir',
|
||||
destination: 'dest_dir',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(fs.mkdir).toHaveBeenCalled();
|
||||
expect(fs.copyFile).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('递归复制包含子目录的目录', async () => {
|
||||
// 调用顺序:
|
||||
// 1. execute: stat(source) - 检查源是否存在
|
||||
// 2. execute: stat(dest) - 检查目标(ENOENT)
|
||||
// 3. copyRecursive: stat(source) - 判断是否是目录
|
||||
// 4. copyRecursive: stat(source/subdir) - 递归判断子目录
|
||||
vi.mocked(fs.stat)
|
||||
.mockResolvedValueOnce({ isDirectory: () => true } as any) // source 存在且是目录
|
||||
.mockRejectedValueOnce(new Error('ENOENT')) // dest 不存在
|
||||
.mockResolvedValueOnce({ isDirectory: () => true } as any) // copyRecursive(source)
|
||||
.mockResolvedValueOnce({ isDirectory: () => true } as any); // copyRecursive(source/subdir)
|
||||
|
||||
vi.mocked(fs.readdir)
|
||||
.mockResolvedValueOnce(['subdir'] as any) // 第一层目录
|
||||
.mockResolvedValueOnce([] as any); // 子目录为空
|
||||
|
||||
const result = await copyFileTool.execute({
|
||||
source: 'src_dir',
|
||||
destination: 'dest_dir',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(fs.mkdir).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('目标位置权限', () => {
|
||||
it('目标位置需要确认时返回错误', async () => {
|
||||
mockCheckResults = [
|
||||
{ allowed: true },
|
||||
{ allowed: false, action: 'ask', needsConfirmation: true, reason: '首次复制到此位置' },
|
||||
];
|
||||
|
||||
vi.mocked(fs.stat).mockResolvedValueOnce({ isDirectory: () => false } as any);
|
||||
|
||||
const result = await copyFileTool.execute({
|
||||
source: 'src.txt',
|
||||
destination: '/new/location/dest.txt',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('需要用户确认');
|
||||
expect(result.error).toContain('首次复制到此位置');
|
||||
});
|
||||
});
|
||||
|
||||
describe('绝对路径处理', () => {
|
||||
it('源和目标都是绝对路径', async () => {
|
||||
vi.mocked(fs.stat)
|
||||
.mockResolvedValueOnce({ isDirectory: () => false } as any)
|
||||
.mockRejectedValueOnce(new Error('ENOENT'));
|
||||
|
||||
const result = await copyFileTool.execute({
|
||||
source: '/absolute/source/file.txt',
|
||||
destination: '/absolute/destination/file.txt',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.output).toContain('/absolute/source/file.txt');
|
||||
expect(result.output).toContain('/absolute/destination/file.txt');
|
||||
});
|
||||
});
|
||||
|
||||
describe('权限检查细节', () => {
|
||||
it('源文件权限被拒绝(无原因)', async () => {
|
||||
mockCheckResults = [
|
||||
{ allowed: false, action: 'deny' },
|
||||
];
|
||||
|
||||
const result = await copyFileTool.execute({
|
||||
source: 'src.txt',
|
||||
destination: 'dest.txt',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('不允许读取此文件');
|
||||
});
|
||||
|
||||
it('源文件需要确认(无原因)', async () => {
|
||||
mockCheckResults = [
|
||||
{ allowed: false, action: 'ask', needsConfirmation: true },
|
||||
];
|
||||
|
||||
const result = await copyFileTool.execute({
|
||||
source: 'src.txt',
|
||||
destination: 'dest.txt',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('需要权限确认');
|
||||
});
|
||||
|
||||
it('目标权限被拒绝(无原因)', async () => {
|
||||
mockCheckResults = [
|
||||
{ allowed: true },
|
||||
{ allowed: false, action: 'deny' },
|
||||
];
|
||||
|
||||
const result = await copyFileTool.execute({
|
||||
source: 'src.txt',
|
||||
destination: 'dest.txt',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('不允许复制到此位置');
|
||||
});
|
||||
|
||||
it('目标需要确认(无原因)', async () => {
|
||||
mockCheckResults = [
|
||||
{ allowed: true },
|
||||
{ allowed: false, action: 'ask', needsConfirmation: true },
|
||||
];
|
||||
|
||||
const result = await copyFileTool.execute({
|
||||
source: 'src.txt',
|
||||
destination: 'dest.txt',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('需要权限确认');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user