Files
zclaw_openfang/desktop/tests/e2e/openfang-compat/specs/api-endpoints.spec.ts
iven ce562e8bfc feat: complete Phase 1-3 architecture optimization
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>
2026-03-21 22:11:50 +08:00

244 lines
8.1 KiB
TypeScript

/**
* OpenFang API 端点兼容性测试
*
* 验证 ZCLAW 前端与 OpenFang 后端的 REST API 兼容性。
*/
import { test, expect, Page } from '@playwright/test';
import { openFangResponses } from '../fixtures/openfang-responses';
const BASE_URL = 'http://localhost:1420';
async function setupMockAPI(page: Page) {
await page.route('**/api/health', async route => {
await route.fulfill({ json: openFangResponses.health });
});
await page.route('**/api/status', async route => {
await route.fulfill({ json: openFangResponses.status });
});
await page.route('**/api/agents', async route => {
if (route.request().method() === 'GET') {
await route.fulfill({ json: openFangResponses.agents });
} else if (route.request().method() === 'POST') {
await route.fulfill({ json: { clone: { id: 'new-agent-001', name: 'New Agent' } } });
}
});
await page.route('**/api/agents/*', async route => {
await route.fulfill({ json: openFangResponses.agent });
});
await page.route('**/api/models', async route => {
await route.fulfill({ json: openFangResponses.models });
});
await page.route('**/api/hands', async route => {
await route.fulfill({ json: openFangResponses.hands });
});
await page.route('**/api/hands/*', async route => {
if (route.request().method() === 'GET') {
await route.fulfill({ json: openFangResponses.hand });
} else if (route.request().url().includes('/activate')) {
await route.fulfill({ json: openFangResponses.handActivation });
}
});
await page.route('**/api/workflows', async route => {
await route.fulfill({ json: openFangResponses.workflows });
});
await page.route('**/api/workflows/*', async route => {
await route.fulfill({ json: openFangResponses.workflow });
});
await page.route('**/api/sessions', async route => {
await route.fulfill({ json: openFangResponses.sessions });
});
await page.route('**/api/config', async route => {
await route.fulfill({ json: openFangResponses.config });
});
await page.route('**/api/channels', async route => {
await route.fulfill({ json: openFangResponses.channels });
});
await page.route('**/api/skills', async route => {
await route.fulfill({ json: openFangResponses.skills });
});
}
test.describe('OpenFang API 端点兼容性测试', () => {
test.describe('API-01: Health 端点', () => {
test('应返回正确的健康状态', async ({ page }) => {
await setupMockAPI(page);
const response = await page.evaluate(async () => {
const res = await fetch('/api/health');
return res.json();
});
expect(response.status).toBe('ok');
expect(response.version).toBeDefined();
});
});
test.describe('API-02: Agents 端点', () => {
test('应返回 Agent 列表', async ({ page }) => {
await setupMockAPI(page);
const response = await page.evaluate(async () => {
const res = await fetch('/api/agents');
return res.json();
});
expect(Array.isArray(response)).toBe(true);
expect(response[0]).toHaveProperty('id');
expect(response[0]).toHaveProperty('name');
expect(response[0]).toHaveProperty('state');
});
});
test.describe('API-03: Create Agent 端点', () => {
test('应创建新 Agent', async ({ page }) => {
await setupMockAPI(page);
const response = await page.evaluate(async () => {
const res = await fetch('/api/agents', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Test Agent', model: 'qwen3.5-plus' }),
});
return res.json();
});
expect(response.clone).toHaveProperty('id');
expect(response.clone).toHaveProperty('name');
});
});
test.describe('API-04: Hands 端点', () => {
test('应返回 Hands 列表', async ({ page }) => {
await setupMockAPI(page);
const response = await page.evaluate(async () => {
const res = await fetch('/api/hands');
return res.json();
});
expect(response).toHaveProperty('hands');
expect(Array.isArray(response.hands)).toBe(true);
});
});
test.describe('API-05: Hand Activation 端点', () => {
test('应激活 Hand 并返回 instance_id', async ({ page }) => {
await setupMockAPI(page);
const response = await page.evaluate(async () => {
const res = await fetch('/api/hands/Browser/activate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({}),
});
return res.json();
});
expect(response).toHaveProperty('instance_id');
expect(response).toHaveProperty('status');
});
});
test.describe('API-06: Workflows 端点', () => {
test('应返回工作流列表', async ({ page }) => {
await setupMockAPI(page);
const response = await page.evaluate(async () => {
const res = await fetch('/api/workflows');
return res.json();
});
expect(response).toHaveProperty('workflows');
expect(Array.isArray(response.workflows)).toBe(true);
});
});
test.describe('API-07: Sessions 端点', () => {
test('应返回会话列表', async ({ page }) => {
await setupMockAPI(page);
const response = await page.evaluate(async () => {
const res = await fetch('/api/sessions');
return res.json();
});
expect(response).toHaveProperty('sessions');
expect(Array.isArray(response.sessions)).toBe(true);
});
});
test.describe('API-08: Models 端点', () => {
test('应返回模型列表', async ({ page }) => {
await setupMockAPI(page);
const response = await page.evaluate(async () => {
const res = await fetch('/api/models');
return res.json();
});
expect(Array.isArray(response)).toBe(true);
expect(response[0]).toHaveProperty('id');
expect(response[0]).toHaveProperty('name');
expect(response[0]).toHaveProperty('provider');
});
});
test.describe('API-09: Config 端点', () => {
test('应返回配置信息', async ({ page }) => {
await setupMockAPI(page);
const response = await page.evaluate(async () => {
const res = await fetch('/api/config');
return res.json();
});
expect(response).toHaveProperty('data_dir');
expect(response).toHaveProperty('default_model');
});
});
test.describe('API-10: Channels 端点', () => {
test('应返回通道列表', async ({ page }) => {
await setupMockAPI(page);
const response = await page.evaluate(async () => {
const res = await fetch('/api/channels');
return res.json();
});
expect(response).toHaveProperty('channels');
expect(Array.isArray(response.channels)).toBe(true);
});
});
test.describe('API-11: Skills 端点', () => {
test('应返回技能列表', async ({ page }) => {
await setupMockAPI(page);
const response = await page.evaluate(async () => {
const res = await fetch('/api/skills');
return res.json();
});
expect(response).toHaveProperty('skills');
expect(Array.isArray(response.skills)).toBe(true);
});
});
test.describe('API-12: Error Handling', () => {
test('应正确处理 404 错误', async ({ page }) => {
await page.route('**/api/nonexistent', async route => {
await route.fulfill({ status: 404, json: { error: 'Not found' } });
});
const response = await page.evaluate(async () => {
const res = await fetch('/api/nonexistent');
return { status: res.status, body: await res.json() };
});
expect(response.status).toBe(404);
});
test('应正确处理 500 错误', async ({ page }) => {
await page.route('**/api/error', async route => {
await route.fulfill({ status: 500, json: { error: 'Internal server error' } });
});
const response = await page.evaluate(async () => {
const res = await fetch('/api/error');
return { status: res.status, body: await res.json() };
});
expect(response.status).toBe(500);
});
});
});