import { describe, it, expect, beforeEach, vi } from 'vitest'; // Mock child_process const mockExec = vi.fn(); vi.mock('child_process', () => ({ exec: (cmd: string, opts: any, cb?: Function) => { if (typeof opts === 'function') { cb = opts; } // 使用 setImmediate 模拟异步 setImmediate(() => { const result = mockExec(cmd); if (result.error) { const err = result.error; err.stdout = result.stdout || ''; err.stderr = result.stderr || ''; cb?.(err, result.stdout || '', result.stderr || ''); } else { cb?.(null, result.stdout || '', result.stderr || ''); } }); }, })); // Mock permission manager vi.mock('../../../../src/permission/index.js', () => ({ getPermissionManager: vi.fn(() => ({ checkGitPermission: vi.fn().mockResolvedValue({ allowed: true, action: 'allow', }), })), })); // Mock loadDescription vi.mock('../../../../src/tools/load_description.js', () => ({ loadDescription: vi.fn(() => '管理 Git 分支'), })); import { gitBranchTool } from '../../../../src/tools/git/git_branch.js'; import { getPermissionManager } from '../../../../src/permission/index.js'; describe('gitBranchTool - Git 分支工具', () => { beforeEach(() => { vi.clearAllMocks(); mockExec.mockReturnValue({ stdout: '* main\n develop', stderr: '' }); }); describe('工具定义', () => { it('有正确的名称', () => { expect(gitBranchTool.name).toBe('git_branch'); }); it('有正确的元数据', () => { expect(gitBranchTool.metadata.category).toBe('git'); expect(gitBranchTool.metadata.keywords).toContain('branch'); expect(gitBranchTool.metadata.keywords).toContain('create'); expect(gitBranchTool.metadata.keywords).toContain('delete'); }); it('定义了可选参数', () => { expect(gitBranchTool.parameters.action.required).toBe(false); expect(gitBranchTool.parameters.name.required).toBe(false); expect(gitBranchTool.parameters.new_name.required).toBe(false); }); }); describe('execute - 执行', () => { it('列出分支(默认操作)', async () => { const result = await gitBranchTool.execute({}); expect(result.success).toBe(true); // 源代码使用 -v 参数,输出可能包含 main 或在空结果时返回空字符串 expect(mockExec).toHaveBeenCalledWith(expect.stringContaining('git branch')); expect(mockExec).toHaveBeenCalledWith(expect.stringContaining('-v')); }); it('创建新分支', async () => { mockExec.mockReturnValue({ stdout: '', stderr: '' }); const result = await gitBranchTool.execute({ action: 'create', name: 'feature/new-branch', }); expect(result.success).toBe(true); expect(mockExec).toHaveBeenCalledWith(expect.stringContaining('git branch feature/new-branch')); }); it('删除分支', async () => { mockExec.mockReturnValue({ stdout: '', stderr: '' }); const result = await gitBranchTool.execute({ action: 'delete', name: 'old-branch', }); expect(result.success).toBe(true); expect(mockExec).toHaveBeenCalledWith(expect.stringContaining('git branch -d old-branch')); }); it('强制删除分支', async () => { mockExec.mockReturnValue({ stdout: '', stderr: '' }); const result = await gitBranchTool.execute({ action: 'delete', name: 'unmerged-branch', force: true, }); expect(result.success).toBe(true); expect(mockExec).toHaveBeenCalledWith(expect.stringContaining('git branch -D unmerged-branch')); }); it('重命名分支', async () => { mockExec.mockReturnValue({ stdout: '', stderr: '' }); const result = await gitBranchTool.execute({ action: 'rename', name: 'old-name', new_name: 'new-name', }); expect(result.success).toBe(true); expect(mockExec).toHaveBeenCalledWith(expect.stringContaining('git branch -m old-name new-name')); }); it('显示远程分支', async () => { mockExec.mockReturnValue({ stdout: 'origin/main\norigin/develop', stderr: '' }); const result = await gitBranchTool.execute({ action: 'list', remote: true, }); expect(result.success).toBe(true); expect(mockExec).toHaveBeenCalledWith(expect.stringContaining('git branch -r')); }); it('显示所有分支', async () => { const result = await gitBranchTool.execute({ action: 'list', all: true, }); expect(result.success).toBe(true); expect(mockExec).toHaveBeenCalledWith(expect.stringContaining('git branch -a')); }); it('创建分支缺少名称返回错误', async () => { const result = await gitBranchTool.execute({ action: 'create', }); expect(result.success).toBe(false); expect(result.error).toContain('需要提供分支名称'); }); it('删除分支缺少名称返回错误', async () => { const result = await gitBranchTool.execute({ action: 'delete', }); expect(result.success).toBe(false); expect(result.error).toContain('需要提供分支名称'); }); it('重命名缺少参数返回错误', async () => { const result = await gitBranchTool.execute({ action: 'rename', name: 'old-name', }); expect(result.success).toBe(false); expect(result.error).toContain('新名称'); }); it('未知操作返回错误', async () => { const result = await gitBranchTool.execute({ action: 'unknown', }); expect(result.success).toBe(false); expect(result.error).toContain('未知操作'); }); it('权限被拒绝时返回错误', async () => { vi.mocked(getPermissionManager).mockReturnValue({ checkGitPermission: vi.fn().mockResolvedValue({ allowed: false, action: 'deny', reason: '不允许操作分支', }), } as any); const result = await gitBranchTool.execute({ action: 'create', name: 'test', }); expect(result.success).toBe(false); expect(result.error).toContain('权限被拒绝'); }); it('需要确认时返回提示', async () => { vi.mocked(getPermissionManager).mockReturnValue({ checkGitPermission: vi.fn().mockResolvedValue({ allowed: false, action: 'ask', needsConfirmation: true, }), } as any); const result = await gitBranchTool.execute({ action: 'delete', name: 'branch', }); expect(result.success).toBe(false); expect(result.error).toContain('需要用户确认'); }); it('Git 命令失败返回错误', async () => { // 恢复权限检查 vi.mocked(getPermissionManager).mockReturnValue({ checkGitPermission: vi.fn().mockResolvedValue({ allowed: true }), } as any); mockExec.mockReturnValue({ error: new Error('Command failed'), stdout: '', stderr: 'fatal: not a git repository', }); const result = await gitBranchTool.execute({}); expect(result.success).toBe(false); expect(result.error).toContain('not a git repository'); }); }); });