5835799b69
- 新增 vitest 配置和测试基础设施 - 添加 adapter.test.ts: 测试 Core 模块初始化和 Agent 管理 (18 tests) - 添加 token.test.ts: 测试 Token 生成、验证和中间件 (25 tests) - 添加 handler.test.ts: 测试权限处理器 (18 tests) - 添加 ws.test.ts: 测试 WebSocket 连接和消息处理 (19 tests) - 添加 sse.test.ts: 测试 SSE 事件发送 (14 tests) - 添加 sessions.test.ts: 测试会话路由 (16 tests) - 添加 config.test.ts: 测试配置路由 (10 tests) - 添加 context.test.ts: 测试上下文压缩路由 (9 tests) - 添加 providers.test.ts: 测试 Provider 管理路由 (18 tests) - 添加 manager.test.ts: 测试 SessionManager (48 tests) 总计 195 个测试,覆盖率从 0% 提升至 29.59%
364 lines
12 KiB
TypeScript
364 lines
12 KiB
TypeScript
/**
|
|
* Permission Handler 测试
|
|
*
|
|
* 测试权限类型检测、请求构建、响应处理、超时处理等功能
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
|
|
// Mock broadcastToSession before importing handler
|
|
vi.mock('../../../src/ws.js', () => ({
|
|
broadcastToSession: vi.fn(),
|
|
}));
|
|
|
|
import {
|
|
createServerPermissionCallback,
|
|
handlePermissionResponse,
|
|
cancelPendingRequests,
|
|
getPendingRequestCount,
|
|
} from '../../../src/permission/handler.js';
|
|
import { broadcastToSession } from '../../../src/ws.js';
|
|
|
|
describe('Permission Handler', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.useFakeTimers();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
describe('detectPermissionType - 权限类型检测', () => {
|
|
it('检测 git 命令', async () => {
|
|
const callback = createServerPermissionCallback('session-1');
|
|
// 触发一个 git 命令的权限请求
|
|
const promise = callback({ command: 'git push origin main', workdir: '/test' });
|
|
|
|
// 检查发送的消息类型
|
|
expect(broadcastToSession).toHaveBeenCalledWith(
|
|
'session-1',
|
|
expect.objectContaining({
|
|
type: 'permission_request',
|
|
payload: expect.objectContaining({
|
|
permissionType: 'git',
|
|
context: expect.objectContaining({
|
|
gitOperation: 'push',
|
|
}),
|
|
}),
|
|
})
|
|
);
|
|
|
|
// 模拟响应
|
|
const call = vi.mocked(broadcastToSession).mock.calls[0];
|
|
const requestId = (call[1] as any).payload.requestId;
|
|
handlePermissionResponse(requestId, true);
|
|
await promise;
|
|
});
|
|
|
|
it('检测文件操作命令', async () => {
|
|
const callback = createServerPermissionCallback('session-1');
|
|
const promise = callback({ command: 'read /etc/passwd', workdir: '/test' });
|
|
|
|
expect(broadcastToSession).toHaveBeenCalledWith(
|
|
'session-1',
|
|
expect.objectContaining({
|
|
payload: expect.objectContaining({
|
|
permissionType: 'file',
|
|
context: expect.objectContaining({
|
|
operation: 'read',
|
|
path: '/etc/passwd',
|
|
}),
|
|
}),
|
|
})
|
|
);
|
|
|
|
const call = vi.mocked(broadcastToSession).mock.calls[0];
|
|
const requestId = (call[1] as any).payload.requestId;
|
|
handlePermissionResponse(requestId, true);
|
|
await promise;
|
|
});
|
|
|
|
it('检测 write 文件操作', async () => {
|
|
const callback = createServerPermissionCallback('session-1');
|
|
const promise = callback({ command: 'write /tmp/test.txt', workdir: '/test' });
|
|
|
|
expect(broadcastToSession).toHaveBeenCalledWith(
|
|
'session-1',
|
|
expect.objectContaining({
|
|
payload: expect.objectContaining({
|
|
permissionType: 'file',
|
|
context: expect.objectContaining({
|
|
operation: 'write',
|
|
}),
|
|
}),
|
|
})
|
|
);
|
|
|
|
const call = vi.mocked(broadcastToSession).mock.calls[0];
|
|
const requestId = (call[1] as any).payload.requestId;
|
|
handlePermissionResponse(requestId, true);
|
|
await promise;
|
|
});
|
|
|
|
it('检测 web 操作', async () => {
|
|
const callback = createServerPermissionCallback('session-1');
|
|
const promise = callback({ command: 'fetch https://api.example.com', workdir: '/test' });
|
|
|
|
expect(broadcastToSession).toHaveBeenCalledWith(
|
|
'session-1',
|
|
expect.objectContaining({
|
|
payload: expect.objectContaining({
|
|
permissionType: 'web',
|
|
}),
|
|
})
|
|
);
|
|
|
|
const call = vi.mocked(broadcastToSession).mock.calls[0];
|
|
const requestId = (call[1] as any).payload.requestId;
|
|
handlePermissionResponse(requestId, true);
|
|
await promise;
|
|
});
|
|
|
|
it('默认为 bash 类型', async () => {
|
|
const callback = createServerPermissionCallback('session-1');
|
|
const promise = callback({ command: 'npm install', workdir: '/test' });
|
|
|
|
expect(broadcastToSession).toHaveBeenCalledWith(
|
|
'session-1',
|
|
expect.objectContaining({
|
|
payload: expect.objectContaining({
|
|
permissionType: 'bash',
|
|
}),
|
|
})
|
|
);
|
|
|
|
const call = vi.mocked(broadcastToSession).mock.calls[0];
|
|
const requestId = (call[1] as any).payload.requestId;
|
|
handlePermissionResponse(requestId, true);
|
|
await promise;
|
|
});
|
|
});
|
|
|
|
describe('buildRequestContext - 请求上下文构建', () => {
|
|
it('git 命令包含 gitOperation', async () => {
|
|
const callback = createServerPermissionCallback('session-1');
|
|
const promise = callback({ command: 'git commit -m "test"', workdir: '/test' });
|
|
|
|
expect(broadcastToSession).toHaveBeenCalledWith(
|
|
'session-1',
|
|
expect.objectContaining({
|
|
payload: expect.objectContaining({
|
|
context: expect.objectContaining({
|
|
command: 'git commit -m "test"',
|
|
gitOperation: 'commit',
|
|
}),
|
|
}),
|
|
})
|
|
);
|
|
|
|
const call = vi.mocked(broadcastToSession).mock.calls[0];
|
|
const requestId = (call[1] as any).payload.requestId;
|
|
handlePermissionResponse(requestId, true);
|
|
await promise;
|
|
});
|
|
|
|
it('文件操作包含 patterns 和 externalPaths', async () => {
|
|
const callback = createServerPermissionCallback('session-1');
|
|
const promise = callback({
|
|
command: 'edit /test/file.ts',
|
|
workdir: '/test',
|
|
patterns: ['*.ts'],
|
|
externalPaths: ['/external'],
|
|
});
|
|
|
|
expect(broadcastToSession).toHaveBeenCalledWith(
|
|
'session-1',
|
|
expect.objectContaining({
|
|
payload: expect.objectContaining({
|
|
context: expect.objectContaining({
|
|
operation: 'edit',
|
|
patterns: ['*.ts'],
|
|
externalPaths: ['/external'],
|
|
}),
|
|
}),
|
|
})
|
|
);
|
|
|
|
const call = vi.mocked(broadcastToSession).mock.calls[0];
|
|
const requestId = (call[1] as any).payload.requestId;
|
|
handlePermissionResponse(requestId, true);
|
|
await promise;
|
|
});
|
|
|
|
it('bash 命令包含 command', async () => {
|
|
const callback = createServerPermissionCallback('session-1');
|
|
const promise = callback({ command: 'rm -rf /tmp/test', workdir: '/test' });
|
|
|
|
expect(broadcastToSession).toHaveBeenCalledWith(
|
|
'session-1',
|
|
expect.objectContaining({
|
|
payload: expect.objectContaining({
|
|
context: expect.objectContaining({
|
|
command: 'rm -rf /tmp/test',
|
|
}),
|
|
}),
|
|
})
|
|
);
|
|
|
|
const call = vi.mocked(broadcastToSession).mock.calls[0];
|
|
const requestId = (call[1] as any).payload.requestId;
|
|
handlePermissionResponse(requestId, true);
|
|
await promise;
|
|
});
|
|
});
|
|
|
|
describe('createServerPermissionCallback - 权限回调', () => {
|
|
it('发送权限请求消息', async () => {
|
|
const callback = createServerPermissionCallback('session-1');
|
|
const promise = callback({ command: 'ls -la', workdir: '/test' });
|
|
|
|
expect(broadcastToSession).toHaveBeenCalledTimes(1);
|
|
expect(broadcastToSession).toHaveBeenCalledWith(
|
|
'session-1',
|
|
expect.objectContaining({
|
|
type: 'permission_request',
|
|
sessionId: 'session-1',
|
|
payload: expect.objectContaining({
|
|
requestId: expect.any(String),
|
|
permissionType: 'bash',
|
|
}),
|
|
})
|
|
);
|
|
|
|
// 响应以完成 promise
|
|
const call = vi.mocked(broadcastToSession).mock.calls[0];
|
|
const requestId = (call[1] as any).payload.requestId;
|
|
handlePermissionResponse(requestId, true);
|
|
await promise;
|
|
});
|
|
|
|
it('返回 allow: true 当响应允许时', async () => {
|
|
const callback = createServerPermissionCallback('session-1');
|
|
const promise = callback({ command: 'ls', workdir: '/test' });
|
|
|
|
const call = vi.mocked(broadcastToSession).mock.calls[0];
|
|
const requestId = (call[1] as any).payload.requestId;
|
|
handlePermissionResponse(requestId, true, true);
|
|
|
|
const result = await promise;
|
|
expect(result).toEqual({ allow: true, remember: true });
|
|
});
|
|
|
|
it('返回 allow: false 当响应拒绝时', async () => {
|
|
const callback = createServerPermissionCallback('session-1');
|
|
const promise = callback({ command: 'rm -rf /', workdir: '/test' });
|
|
|
|
const call = vi.mocked(broadcastToSession).mock.calls[0];
|
|
const requestId = (call[1] as any).payload.requestId;
|
|
handlePermissionResponse(requestId, false, false);
|
|
|
|
const result = await promise;
|
|
expect(result).toEqual({ allow: false, remember: false });
|
|
});
|
|
});
|
|
|
|
describe('handlePermissionResponse - 权限响应处理', () => {
|
|
it('处理存在的请求返回 true', async () => {
|
|
const callback = createServerPermissionCallback('session-1');
|
|
const promise = callback({ command: 'ls', workdir: '/test' });
|
|
|
|
const call = vi.mocked(broadcastToSession).mock.calls[0];
|
|
const requestId = (call[1] as any).payload.requestId;
|
|
|
|
const result = handlePermissionResponse(requestId, true);
|
|
expect(result).toBe(true);
|
|
|
|
await promise;
|
|
});
|
|
|
|
it('处理不存在的请求返回 false', () => {
|
|
const result = handlePermissionResponse('non-existent-request-id', true);
|
|
expect(result).toBe(false);
|
|
});
|
|
|
|
it('清除超时定时器', async () => {
|
|
const callback = createServerPermissionCallback('session-1');
|
|
const promise = callback({ command: 'ls', workdir: '/test' });
|
|
|
|
const call = vi.mocked(broadcastToSession).mock.calls[0];
|
|
const requestId = (call[1] as any).payload.requestId;
|
|
|
|
// 响应请求
|
|
handlePermissionResponse(requestId, true);
|
|
await promise;
|
|
|
|
// 快进超过超时时间,不应有任何错误
|
|
vi.advanceTimersByTime(70000);
|
|
});
|
|
});
|
|
|
|
describe('超时处理', () => {
|
|
it('超时后返回 allow: false', async () => {
|
|
const callback = createServerPermissionCallback('session-1');
|
|
const promise = callback({ command: 'dangerous-command', workdir: '/test' });
|
|
|
|
// 快进 60 秒(超时时间)
|
|
vi.advanceTimersByTime(60000);
|
|
|
|
const result = await promise;
|
|
expect(result).toEqual({ allow: false, remember: false });
|
|
});
|
|
|
|
it('超时后从 pending 中移除', async () => {
|
|
const initialCount = getPendingRequestCount();
|
|
|
|
const callback = createServerPermissionCallback('session-1');
|
|
const promise = callback({ command: 'test', workdir: '/test' });
|
|
|
|
expect(getPendingRequestCount()).toBe(initialCount + 1);
|
|
|
|
// 超时
|
|
vi.advanceTimersByTime(60000);
|
|
await promise;
|
|
|
|
expect(getPendingRequestCount()).toBe(initialCount);
|
|
});
|
|
});
|
|
|
|
describe('getPendingRequestCount - 待处理请求计数', () => {
|
|
it('返回正确的待处理请求数', async () => {
|
|
const initialCount = getPendingRequestCount();
|
|
|
|
const callback = createServerPermissionCallback('session-1');
|
|
const promise1 = callback({ command: 'cmd1', workdir: '/test' });
|
|
const promise2 = callback({ command: 'cmd2', workdir: '/test' });
|
|
|
|
expect(getPendingRequestCount()).toBe(initialCount + 2);
|
|
|
|
// 响应第一个
|
|
const call1 = vi.mocked(broadcastToSession).mock.calls[0];
|
|
const requestId1 = (call1[1] as any).payload.requestId;
|
|
handlePermissionResponse(requestId1, true);
|
|
await promise1;
|
|
|
|
expect(getPendingRequestCount()).toBe(initialCount + 1);
|
|
|
|
// 响应第二个
|
|
const call2 = vi.mocked(broadcastToSession).mock.calls[1];
|
|
const requestId2 = (call2[1] as any).payload.requestId;
|
|
handlePermissionResponse(requestId2, true);
|
|
await promise2;
|
|
|
|
expect(getPendingRequestCount()).toBe(initialCount);
|
|
});
|
|
});
|
|
|
|
describe('cancelPendingRequests - 取消待处理请求', () => {
|
|
it('调用不抛出错误', () => {
|
|
// 目前这个函数是占位符,只是确保不抛出错误
|
|
expect(() => cancelPendingRequests('session-1')).not.toThrow();
|
|
});
|
|
});
|
|
});
|