import { describe, it, expect, beforeEach, vi } from 'vitest'; import { GitPermissionChecker } from '../../../src/permission/checkers/git.js'; import type { GitPermissionContext, PermissionDecision } from '../../../src/permission/types.js'; describe('GitPermissionChecker - Git 权限检查器', () => { let checker: GitPermissionChecker; beforeEach(() => { checker = new GitPermissionChecker(); }); describe('读操作(默认允许)', () => { const readOperations: GitPermissionContext['operation'][] = [ 'status', 'diff', 'log', 'branch_list', 'show', ]; for (const operation of readOperations) { it(`${operation} 默认允许`, async () => { const result = await checker.checkGitPermission({ operation }); expect(result.allowed).toBe(true); expect(result.action).toBe('allow'); }); } }); describe('写操作(默认询问)', () => { const writeOperations: GitPermissionContext['operation'][] = [ 'add', 'commit', 'push', 'pull', 'checkout', 'branch_create', 'branch_delete', 'stash', 'stash_pop', 'merge', 'rebase', ]; for (const operation of writeOperations) { it(`${operation} 无回调时需要确认`, async () => { const result = await checker.checkGitPermission({ operation }); expect(result.action).toBe('ask'); expect(result.needsConfirmation).toBe(true); }); } }); describe('危险操作', () => { it('reset 总是危险操作', async () => { const result = await checker.checkGitPermission({ operation: 'reset' }); expect(result.action).toBe('ask'); expect(result.needsConfirmation).toBe(true); }); it('push --force 是危险操作', async () => { const result = await checker.checkGitPermission({ operation: 'push', force: true, }); expect(result.action).toBe('ask'); }); it('checkout --force 是危险操作', async () => { const result = await checker.checkGitPermission({ operation: 'checkout', force: true, }); expect(result.action).toBe('ask'); }); it('rebase --force 是危险操作', async () => { const result = await checker.checkGitPermission({ operation: 'rebase', force: true, }); expect(result.action).toBe('ask'); }); }); describe('回调处理', () => { it('用户允许时返回允许', async () => { const mockCallback = vi.fn().mockResolvedValue({ allow: true, remember: false, } as PermissionDecision); checker.setAskCallback(mockCallback); const result = await checker.checkGitPermission({ operation: 'commit' }); expect(result.allowed).toBe(true); expect(mockCallback).toHaveBeenCalled(); }); it('用户拒绝时返回拒绝', async () => { const mockCallback = vi.fn().mockResolvedValue({ allow: false, remember: false, } as PermissionDecision); checker.setAskCallback(mockCallback); const result = await checker.checkGitPermission({ operation: 'push' }); expect(result.allowed).toBe(false); expect(result.action).toBe('deny'); }); it('remember=true 时记住决定', async () => { const mockCallback = vi.fn().mockResolvedValue({ allow: true, remember: true, } as PermissionDecision); checker.setAskCallback(mockCallback); // 第一次调用 await checker.checkGitPermission({ operation: 'commit' }); // 第二次调用不应该再询问 const secondResult = await checker.checkGitPermission({ operation: 'push' }); expect(secondResult.allowed).toBe(true); // 第二次不需要回调(因为记住了写操作的权限) expect(mockCallback).toHaveBeenCalledTimes(1); }); }); describe('会话权限管理', () => { it('清除会话权限后重新询问', async () => { const mockCallback = vi.fn().mockResolvedValue({ allow: true, remember: true, } as PermissionDecision); checker.setAskCallback(mockCallback); // 第一次调用并记住 await checker.checkGitPermission({ operation: 'commit' }); // 清除会话权限 checker.clearSessionPermissions(); // 再次调用应该重新询问 await checker.checkGitPermission({ operation: 'commit' }); expect(mockCallback).toHaveBeenCalledTimes(2); }); it('拒绝决定也被记住', async () => { const mockCallback = vi.fn().mockResolvedValue({ allow: false, remember: true, } as PermissionDecision); checker.setAskCallback(mockCallback); // 第一次拒绝并记住 await checker.checkGitPermission({ operation: 'push' }); // 第二次直接拒绝 const result = await checker.checkGitPermission({ operation: 'commit' }); expect(result.allowed).toBe(false); expect(mockCallback).toHaveBeenCalledTimes(1); }); }); describe('配置管理', () => { it('获取默认配置', () => { const config = checker.getConfig(); expect(config.readOperations).toBe('allow'); expect(config.writeOperations).toBe('ask'); expect(config.dangerousOperations).toBe('ask'); }); it('更新配置', () => { checker.setConfig({ writeOperations: 'allow' }); const config = checker.getConfig(); expect(config.writeOperations).toBe('allow'); }); it('配置更改后影响权限检查', async () => { checker.setConfig({ writeOperations: 'allow' }); const result = await checker.checkGitPermission({ operation: 'commit' }); expect(result.allowed).toBe(true); }); it('配置危险操作为拒绝', async () => { checker.setConfig({ dangerousOperations: 'deny' }); const result = await checker.checkGitPermission({ operation: 'reset' }); expect(result.allowed).toBe(false); expect(result.action).toBe('deny'); }); }); describe('操作描述生成', () => { it('带 target 的操作描述', async () => { checker.setAskCallback(vi.fn().mockResolvedValue({ allow: true, remember: false })); await checker.checkGitPermission({ operation: 'checkout', target: 'feature-branch', }); // 验证回调收到正确的描述 const callArg = vi.mocked(checker['askCallback']!).mock.calls[0][0]; expect(callArg.command).toContain('feature-branch'); }); it('带 remote 的操作描述', async () => { checker.setAskCallback(vi.fn().mockResolvedValue({ allow: true, remember: false })); await checker.checkGitPermission({ operation: 'push', remote: 'origin', }); const callArg = vi.mocked(checker['askCallback']!).mock.calls[0][0]; expect(callArg.command).toContain('origin'); }); it('带 commit message 的操作描述(截断)', async () => { checker.setAskCallback(vi.fn().mockResolvedValue({ allow: true, remember: false })); const longMessage = 'a'.repeat(100); await checker.checkGitPermission({ operation: 'commit', message: longMessage, }); const callArg = vi.mocked(checker['askCallback']!).mock.calls[0][0]; expect(callArg.command).toContain('...'); expect(callArg.command.length).toBeLessThan(longMessage.length + 50); }); it('--force 显示在描述中', async () => { checker.setAskCallback(vi.fn().mockResolvedValue({ allow: true, remember: false })); await checker.checkGitPermission({ operation: 'push', force: true, }); const callArg = vi.mocked(checker['askCallback']!).mock.calls[0][0]; expect(callArg.command).toContain('--force'); }); }); describe('check 接口(从命令解析)', () => { it('解析 git status', async () => { const result = await checker.check({ command: 'git status', workdir: '/test', }); expect(result.allowed).toBe(true); }); it('解析 git_commit', async () => { const result = await checker.check({ command: 'git_commit', workdir: '/test', }); expect(result.action).toBe('ask'); }); it('无法解析的命令返回拒绝', async () => { const result = await checker.check({ command: 'invalid command', workdir: '/test', }); expect(result.allowed).toBe(false); expect(result.reason).toContain('无法解析'); }); }); });