/** * 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); }); }); });