import { afterAll, beforeAll, describe, expect, it } from 'vitest' import pino from 'pino' import { createApp, type App } from '../src/app' import { API_EVENTS, API_GROUPS } from '../src/api-reference/reference' import { BOT_EVENTS, BOT_GROUPS } from '../src/api-reference/bot-reference' import { renderApiReferenceHtml } from '../src/api-reference/render' import { findChrome } from '../src/api-reference/pdf' import { buildServer } from '../src/http/server' import { registeredV1Routes } from '../src/http/routes/v1' import { registeredBotRoutes } from '../src/teams/routes' import type { EventPayloads, TeamEventPayloads } from '@agent-server/shared' let app: App let server: Awaited> beforeAll(async () => { const log = pino({ level: 'silent' }) app = createApp(log, { providers: {} }) server = await buildServer(app, { logger: log }) }) afterAll(() => server.close()) describe('API 协议文档', () => { it('与实际注册的 /v1 路由完全一致(新增或删除接口时必须同步修改 reference.ts)', () => { const key = (m: string, p: string) => `${m} ${p}` const actual = new Set(registeredV1Routes.map(r => key(r.method, r.path))) const documented = new Set(API_GROUPS.flatMap(g => g.endpoints.map(e => key(e.method, e.path)))) expect([...actual].filter(x => !documented.has(x)), '未写入文档的接口').toEqual([]) expect([...documented].filter(x => !actual.has(x)), '文档中有、但实际不存在的接口').toEqual([]) }) it('Bot API:与实际注册的 /bot/v1 路由完全一致(新增或删除接口时必须同步修改 bot-reference.ts)', () => { const key = (m: string, p: string) => `${m} ${p}` const actual = new Set(registeredBotRoutes.map(r => key(r.method, r.path))) const documented = new Set(BOT_GROUPS.flatMap(g => g.endpoints.map(e => key(e.method, e.path)))) expect([...actual].filter(x => !documented.has(x)), '未写入文档的接口').toEqual([]) expect([...documented].filter(x => !actual.has(x)), '文档中有、但实际不存在的接口').toEqual([]) }) it('Bot API 事件类型与工作组事件模型一致', () => { const all: Record = { 'message.created': true, 'bot.status': true, 'interaction.required': true, 'interaction.resolved': true, 'file.changed': true, 'team.updated': true, 'job.updated': true, 'task.updated': true, 'step.updated': true, } expect(BOT_EVENTS.map(e => e.type).sort()).toEqual(Object.keys(all).sort()) }) it('事件类型与统一事件模型一致', () => { // EventPayloads 的键在类型层面,这里用一个完整的对象字面量确保编译期同步 const all: Record = { 'user.message': true, 'run.queued': true, 'run.started': true, 'message.delta': true, 'message.completed': true, 'reasoning.delta': true, 'reasoning.completed': true, 'tool.started': true, 'tool.completed': true, 'file.changed': true, 'interaction.required': true, 'interaction.resolved': true, 'script.progress': true, usage: true, 'run.completed': true, 'run.failed': true, 'run.interrupted': true, } expect(API_EVENTS.map(e => e.type).sort()).toEqual(Object.keys(all).sort()) }) it('渲染:示例使用传入的服务地址;转义特殊字符;没有脚本', () => { const html = renderApiReferenceHtml({ baseUrl: 'https://agent.example.com/', theme: 'dark', version: 'v1', generatedAt: new Date('2026-10-23T00:00:00Z') }) expect(html).toContain('curl -X POST https://agent.example.com/v1/sessions') // 两套协议都在同一份文档中 expect(html).toContain('第 1 部分 · Agent API') expect(html).toContain('第 2 部分 · Bot API') expect(html).toContain('https://agent.example.com/bot/v1/teams') expect(html).not.toContain('$BASE') expect(html).toContain('data-theme="dark"') expect(html).toContain('<凭证>') expect(html).not.toMatch(/