Phase 1 - Security: - Add AES-GCM encryption for localStorage fallback - Enforce WSS protocol for non-localhost WebSocket connections - Add URL sanitization to prevent XSS in markdown links Phase 2 - Domain Reorganization: - Create Intelligence Domain with Valtio store and caching - Add unified intelligence-client for Rust backend integration - Migrate from legacy agent-memory, heartbeat, reflection modules Phase 3 - Core Optimization: - Add virtual scrolling for ChatArea with react-window - Implement LRU cache with TTL for intelligence operations - Add message virtualization utilities Additional: - Add OpenFang compatibility test suite - Update E2E test fixtures - Add audit logging infrastructure - Update project documentation and plans Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
110 lines
3.5 KiB
TypeScript
110 lines
3.5 KiB
TypeScript
/**
|
|
* OpenFang 协议兼容性测试
|
|
*
|
|
* 验证 ZCLAW 前端与 OpenFang 后端的协议兼容性。
|
|
*/
|
|
|
|
import { test, expect } from '@playwright/test';
|
|
import { openFangResponses, streamEvents, gatewayFrames } from '../fixtures/openfang-responses';
|
|
|
|
const BASE_URL = 'http://localhost:1420';
|
|
|
|
test.describe('OpenFang 协议兼容性测试', () => {
|
|
|
|
test.describe('PROTO-01: 流事件类型解析', () => {
|
|
test('应正确解析 text_delta 事件', () => {
|
|
const event = streamEvents.textDelta('Hello World');
|
|
expect(event.type).toBe('text_delta');
|
|
expect(event.content).toBe('Hello World');
|
|
});
|
|
|
|
test('应正确解析 phase 事件', () => {
|
|
const doneEvent = streamEvents.phaseDone;
|
|
expect(doneEvent.type).toBe('phase');
|
|
expect(doneEvent.phase).toBe('done');
|
|
});
|
|
|
|
test('应正确解析 tool_call 和 tool_result 事件', () => {
|
|
const toolCall = streamEvents.toolCall('search', { query: 'test' });
|
|
expect(toolCall.type).toBe('tool_call');
|
|
expect(toolCall.tool).toBe('search');
|
|
|
|
const toolResult = streamEvents.toolResult('search', { results: [] });
|
|
expect(toolResult.type).toBe('tool_result');
|
|
});
|
|
|
|
test('应正确解析 hand 事件', () => {
|
|
const handEvent = streamEvents.hand('Browser', 'completed', { pages: 5 });
|
|
expect(handEvent.type).toBe('hand');
|
|
expect(handEvent.hand_name).toBe('Browser');
|
|
expect(handEvent.hand_status).toBe('completed');
|
|
});
|
|
|
|
test('应正确解析 error 事件', () => {
|
|
const errorEvent = streamEvents.error('TIMEOUT', 'Request timed out');
|
|
expect(errorEvent.type).toBe('error');
|
|
expect(errorEvent.code).toBe('TIMEOUT');
|
|
});
|
|
});
|
|
|
|
test.describe('PROTO-02: Gateway 帧格式兼容', () => {
|
|
test('应正确构造请求帧', () => {
|
|
const frame = gatewayFrames.request(1, 'chat', { message: 'Hello' });
|
|
expect(frame.type).toBe('req');
|
|
expect(frame.id).toBe(1);
|
|
expect(frame.method).toBe('chat');
|
|
});
|
|
|
|
test('应正确构造响应帧', () => {
|
|
const frame = gatewayFrames.response(1, { status: 'ok' });
|
|
expect(frame.type).toBe('res');
|
|
expect(frame.id).toBe(1);
|
|
});
|
|
|
|
test('应正确构造事件帧', () => {
|
|
const frame = gatewayFrames.event({ type: 'text_delta', content: 'test' });
|
|
expect(frame.type).toBe('event');
|
|
});
|
|
|
|
test('应正确构造 pong 帧', () => {
|
|
const frame = gatewayFrames.pong(1);
|
|
expect(frame.type).toBe('pong');
|
|
expect(frame.id).toBe(1);
|
|
});
|
|
});
|
|
|
|
test.describe('PROTO-03: 连接状态管理', () => {
|
|
const validStates = ['disconnected', 'connecting', 'handshaking', 'connected', 'reconnecting'];
|
|
|
|
test('连接状态应为有效值', () => {
|
|
validStates.forEach(state => {
|
|
expect(['disconnected', 'connecting', 'handshaking', 'connected', 'reconnecting']).toContain(state);
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe('PROTO-04: 心跳机制', () => {
|
|
test('心跳帧格式正确', () => {
|
|
const pingFrame = { type: 'ping' };
|
|
expect(pingFrame.type).toBe('ping');
|
|
});
|
|
|
|
test('pong 响应格式正确', () => {
|
|
const pongFrame = gatewayFrames.pong(1);
|
|
expect(pongFrame.type).toBe('pong');
|
|
});
|
|
});
|
|
|
|
test.describe('PROTO-05: 设备认证流程', () => {
|
|
test('设备认证响应格式', () => {
|
|
const authResponse = {
|
|
status: 'authenticated',
|
|
device_id: 'device-001',
|
|
token: 'jwt-token-here',
|
|
};
|
|
expect(authResponse.status).toBe('authenticated');
|
|
expect(authResponse.device_id).toBeDefined();
|
|
});
|
|
});
|
|
});
|