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'); }); }); });