5e32375f0e
架构变更: - 采用 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 - 配置管理
223 lines
6.3 KiB
TypeScript
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();
|
|
});
|
|
});
|