Files
ai-terminal-assistant/tests/unit/tools/git/git_commit-extended.test.ts
T
kurihada bca19b7741 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
2025-12-11 20:37:03 +08:00

276 lines
7.5 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from 'vitest';
// 定义可控的 mock 变量
let mockExecAsyncResult: { stdout: string; stderr: string } | Error = {
stdout: '[main abc1234] Test commit\n 1 file changed, 1 insertion(+)',
stderr: '',
};
let mockPermissionResult = {
allowed: true,
action: 'allow' as const,
reason: undefined as string | undefined,
needsConfirmation: false,
};
// Mock child_process
vi.mock('child_process', () => ({
exec: vi.fn(),
}));
// Mock util - 返回函数使用外部变量
vi.mock('util', () => ({
promisify: vi.fn(() => vi.fn(async () => {
if (mockExecAsyncResult instanceof Error) {
throw mockExecAsyncResult;
}
return mockExecAsyncResult;
})),
}));
// Mock permission manager
vi.mock('../../../../src/permission/index.js', () => ({
getPermissionManager: vi.fn(() => ({
checkGitPermission: vi.fn(async () => mockPermissionResult),
})),
}));
// Mock loadDescription
vi.mock('../../../../src/tools/load_description.js', () => ({
loadDescription: vi.fn(() => '提交 Git 变更'),
}));
import { gitCommitTool } from '../../../../src/tools/git/git_commit.js';
import { getPermissionManager } from '../../../../src/permission/index.js';
describe('gitCommitTool - Git 提交工具扩展测试', () => {
beforeEach(() => {
vi.clearAllMocks();
mockExecAsyncResult = {
stdout: '[main abc1234] Test commit\n 1 file changed, 1 insertion(+)',
stderr: '',
};
mockPermissionResult = {
allowed: true,
action: 'allow',
reason: undefined,
needsConfirmation: false,
};
});
describe('基本提交', () => {
it('成功提交变更', async () => {
const result = await gitCommitTool.execute({
message: 'Test commit message',
});
expect(result.success).toBe(true);
expect(result.output).toContain('Test commit');
});
it('没有 message 返回错误', async () => {
const result = await gitCommitTool.execute({});
expect(result.success).toBe(false);
expect(result.error).toContain('提交信息是必填的');
});
it('amend 模式无 message 允许', async () => {
const result = await gitCommitTool.execute({
amend: true,
});
expect(result.success).toBe(true);
});
});
describe('提交选项', () => {
it('使用 -a 选项暂存所有变更', async () => {
const result = await gitCommitTool.execute({
message: 'Auto stage commit',
all: true,
});
expect(result.success).toBe(true);
});
it('amend 带 message', async () => {
const result = await gitCommitTool.execute({
message: 'Updated message',
amend: true,
});
expect(result.success).toBe(true);
});
it('转义 message 中的引号', async () => {
const result = await gitCommitTool.execute({
message: 'Message with "quotes"',
});
expect(result.success).toBe(true);
});
});
describe('权限检查', () => {
it('权限拒绝返回错误', async () => {
mockPermissionResult = {
allowed: false,
action: 'deny',
reason: '不允许提交到此仓库',
needsConfirmation: false,
};
const result = await gitCommitTool.execute({
message: 'Test commit',
});
expect(result.success).toBe(false);
expect(result.error).toContain('权限被拒绝');
expect(result.error).toContain('不允许提交到此仓库');
});
it('需要确认时返回提示', async () => {
mockPermissionResult = {
allowed: false,
action: 'ask',
reason: '首次提交',
needsConfirmation: true,
};
const result = await gitCommitTool.execute({
message: 'First commit',
});
expect(result.success).toBe(false);
expect(result.error).toContain('需要用户确认');
expect(result.error).toContain('首次提交');
});
it('权限检查包含正确上下文', async () => {
const mockCheck = vi.fn().mockResolvedValue({
allowed: true,
action: 'allow',
});
vi.mocked(getPermissionManager).mockReturnValue({
checkGitPermission: mockCheck,
} as any);
await gitCommitTool.execute({
message: 'Check context',
});
expect(mockCheck).toHaveBeenCalledWith({
operation: 'commit',
message: 'Check context',
});
});
});
describe('错误处理', () => {
it('没有变更可提交', async () => {
mockExecAsyncResult = Object.assign(
new Error('nothing to commit, working tree clean'),
{ stdout: '', stderr: '', message: 'nothing to commit, working tree clean' }
);
const result = await gitCommitTool.execute({
message: 'Empty commit',
});
expect(result.success).toBe(false);
expect(result.error).toContain('没有变更需要提交');
expect(result.error).toContain('git_add');
});
it('stderr 包含 nothing to commit', async () => {
mockExecAsyncResult = Object.assign(
new Error('Command failed'),
{ stdout: '', stderr: 'nothing to commit', message: 'Command failed' }
);
const result = await gitCommitTool.execute({
message: 'Empty commit',
});
expect(result.success).toBe(false);
expect(result.error).toContain('没有变更需要提交');
});
it('其他 Git 错误', async () => {
mockExecAsyncResult = Object.assign(
new Error('fatal: not a git repository'),
{ stdout: '', stderr: 'fatal: not a git repository', message: 'fatal: not a git repository' }
);
const result = await gitCommitTool.execute({
message: 'Test commit',
});
expect(result.success).toBe(false);
expect(result.error).toContain('not a git repository');
});
it('保留 stdout 在错误中', async () => {
mockExecAsyncResult = Object.assign(
new Error('error'),
{ stdout: 'some output', stderr: 'error message', message: 'error' }
);
const result = await gitCommitTool.execute({
message: 'Test',
});
expect(result.output).toBe('some output');
expect(result.error).toBe('error message');
});
});
describe('工具元数据', () => {
it('包含正确的名称', () => {
expect(gitCommitTool.name).toBe('git_commit');
});
it('包含正确的类别', () => {
expect(gitCommitTool.metadata.category).toBe('git');
});
it('包含关键词', () => {
expect(gitCommitTool.metadata.keywords).toContain('git');
expect(gitCommitTool.metadata.keywords).toContain('commit');
expect(gitCommitTool.metadata.keywords).toContain('提交');
});
it('参数定义正确', () => {
expect(gitCommitTool.parameters.message.required).toBe(true);
expect(gitCommitTool.parameters.amend.required).toBe(false);
expect(gitCommitTool.parameters.all.required).toBe(false);
});
});
describe('输出格式', () => {
it('包含 stdout', async () => {
mockExecAsyncResult = {
stdout: 'commit output',
stderr: '',
};
const result = await gitCommitTool.execute({ message: 'test' });
expect(result.output).toBe('commit output');
});
it('包含 stdout 和 stderr', async () => {
mockExecAsyncResult = {
stdout: 'commit output',
stderr: 'warning message',
};
const result = await gitCommitTool.execute({ message: 'test' });
expect(result.output).toContain('commit output');
expect(result.output).toContain('warning message');
});
});
});