Files
ai-terminal-assistant/packages/core/tests/unit/permission/manager.test.ts
T
kurihada 5e32375f0e feat: 重构为 Monorepo 架构并实现 HTTP Server
架构变更:
- 采用 pnpm workspaces 实现 Monorepo 结构
- 将现有代码迁移到 packages/core
- 新增 packages/server HTTP 服务层

Server 功能:
- REST API: 会话管理、工具管理、配置管理
- WebSocket: 实时双向通信支持
- SSE: 服务端事件推送
- Hono + Bun 作为运行时

API 端点:
- GET/POST /api/sessions - 会话 CRUD
- GET/POST /api/sessions/:id/messages - 消息管理
- GET /api/sessions/:id/events - SSE 事件流
- WS /api/ws/:sessionId - WebSocket 连接
- GET/POST /api/tools - 工具管理
- GET/PUT /api/config - 配置管理
2025-12-12 10:42:20 +08:00

223 lines
6.3 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from 'vitest';
import {
PermissionManager,
getPermissionManager,
resetPermissionManager,
} from '../../../src/permission/manager.js';
import type { PermissionDecision, PermissionContext } from '../../../src/permission/types.js';
// Mock 检查器以避免文件系统操作
vi.mock('fs', () => ({
existsSync: vi.fn(() => false),
readFileSync: vi.fn(),
writeFileSync: vi.fn(),
mkdirSync: vi.fn(),
}));
vi.mock('../../../src/permission/file-prompt.js', () => ({
promptFilePermission: vi.fn().mockResolvedValue({ allow: true, remember: false }),
}));
describe('PermissionManager - 权限管理器', () => {
let manager: PermissionManager;
const testProjectRoot = '/test/project';
beforeEach(() => {
manager = new PermissionManager(testProjectRoot);
vi.clearAllMocks();
});
describe('初始化', () => {
it('创建时注册默认检查器', () => {
// 应该包含 bash, file, web, git 检查器
expect(manager.getChecker('bash')).toBeDefined();
expect(manager.getChecker('file')).toBeDefined();
expect(manager.getChecker('web')).toBeDefined();
expect(manager.getChecker('git')).toBeDefined();
});
it('未注册的检查器返回 undefined', () => {
expect(manager.getChecker('non-existent')).toBeUndefined();
});
});
describe('registerChecker', () => {
it('注册自定义检查器', () => {
const customChecker = {
name: 'custom',
check: vi.fn().mockResolvedValue({ allowed: true, action: 'allow' }),
clearSessionPermissions: vi.fn(),
};
manager.registerChecker(customChecker);
expect(manager.getChecker('custom')).toBe(customChecker);
});
it('覆盖已有检查器', () => {
const newBashChecker = {
name: 'bash',
check: vi.fn().mockResolvedValue({ allowed: false, action: 'deny' }),
clearSessionPermissions: vi.fn(),
};
manager.registerChecker(newBashChecker);
expect(manager.getChecker('bash')).toBe(newBashChecker);
});
});
describe('setAskCallback', () => {
it('设置回调传递给所有支持回调的检查器', () => {
const callback = vi.fn().mockResolvedValue({
allow: true,
remember: false,
} as PermissionDecision);
manager.setAskCallback(callback);
// 验证回调被设置(通过间接方式)
expect(callback).not.toHaveBeenCalled(); // 设置时不调用
});
});
describe('checkPermission', () => {
it('使用指定检查器检查权限', async () => {
const result = await manager.checkPermission('bash', {
command: 'ls -la',
workdir: testProjectRoot,
});
expect(result.allowed).toBe(true);
});
it('未注册的检查器返回 ask', async () => {
const result = await manager.checkPermission('non-existent', {
command: 'some command',
workdir: testProjectRoot,
});
expect(result.action).toBe('ask');
expect(result.needsConfirmation).toBe(true);
expect(result.reason).toContain('未找到检查器');
});
});
describe('checkBashPermission - 便捷方法', () => {
it('检查安全命令', async () => {
const result = await manager.checkBashPermission({
command: 'git status',
workdir: testProjectRoot,
});
expect(result.allowed).toBe(true);
});
it('检查危险命令', async () => {
const result = await manager.checkBashPermission({
command: 'rm -rf /',
workdir: testProjectRoot,
});
expect(result.allowed).toBe(false);
});
});
describe('checkFilePermission - 便捷方法', () => {
it('检查读操作', async () => {
const result = await manager.checkFilePermission({
operation: 'read',
path: './src/index.ts',
workdir: testProjectRoot,
});
expect(result.allowed).toBe(true);
});
it('检查写操作需要确认', async () => {
const result = await manager.checkFilePermission({
operation: 'write',
path: './src/new-file.ts',
workdir: testProjectRoot,
});
expect(result.action).toBe('ask');
});
});
describe('checkGitPermission - 便捷方法', () => {
it('检查读操作', async () => {
const result = await manager.checkGitPermission({
operation: 'status',
});
expect(result.allowed).toBe(true);
});
it('检查写操作需要确认', async () => {
const result = await manager.checkGitPermission({
operation: 'push',
});
expect(result.action).toBe('ask');
});
});
describe('checkWebPermission - 便捷方法', () => {
it('检查网页访问', async () => {
const result = await manager.checkWebPermission({
operation: 'fetch',
url: 'https://example.com',
});
// Web 检查器的默认行为
expect(result).toBeDefined();
});
});
describe('会话权限管理', () => {
it('clearAllSessionPermissions 清除所有检查器的权限', () => {
// 调用方法不应该抛出错误
expect(() => manager.clearAllSessionPermissions()).not.toThrow();
});
it('clearSessionPermissions 清除指定检查器的权限', () => {
expect(() => manager.clearSessionPermissions('bash')).not.toThrow();
expect(() => manager.clearSessionPermissions('file')).not.toThrow();
expect(() => manager.clearSessionPermissions('git')).not.toThrow();
});
it('清除不存在的检查器权限不报错', () => {
expect(() => manager.clearSessionPermissions('non-existent')).not.toThrow();
});
});
});
describe('全局实例管理', () => {
beforeEach(() => {
resetPermissionManager();
});
it('getPermissionManager 返回单例', () => {
const manager1 = getPermissionManager('/test/project');
const manager2 = getPermissionManager();
expect(manager1).toBe(manager2);
});
it('resetPermissionManager 重置单例', () => {
const manager1 = getPermissionManager('/test/project');
resetPermissionManager();
const manager2 = getPermissionManager('/test/project');
expect(manager1).not.toBe(manager2);
});
it('首次创建使用指定的 projectRoot', () => {
const manager = getPermissionManager('/custom/root');
// 验证管理器已创建
expect(manager).toBeDefined();
});
});