Files
kurihada 5835799b69 test(server): 添加 server 模块单元测试
- 新增 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%
2025-12-15 00:07:32 +08:00

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();
});
});
});