Files
lindyson-ai/apps/api/tests/apiContractCoverage.test.ts
Juno ChowandClaude Fable 5.1 f665082dad chore: 建立产品仓库,完成开源前的敏感信息清理
前端(Vite + React)、API/worker(Fastify + BullMQ)、api-contracts 契约包、
迁移脚本、测试与自托管 docker-compose 示例。

开源准备:运维脚本与机器信息已拆到私有仓库;公司名、备案号、隐私邮箱改读
publicCompliance.local.json(仓库只保留占位模板);站点地址与客服二维码改为
VITE_ 环境变量;fixtures 中的真实用户标识已匿名化;三个 package.json 标注
Apache-2.0。

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-16 02:10:46 +08:00

93 lines
3.0 KiB
TypeScript

import fastify from 'fastify';
import { serializerCompiler, validatorCompiler } from 'fastify-type-provider-zod';
import { describe, expect, it } from 'vitest';
import {
nonJsonApiRouteExemptions,
registerApiContractRouteHook,
} from '../src/apiContractRoutes.js';
import { setApiErrorHandler } from '../src/app.js';
describe('API 路由契约覆盖', () => {
it('第一方 JSON 路由缺失共享契约时在注册阶段失败', async () => {
const app = fastify({ logger: false });
registerApiContractRouteHook(app);
try {
expect(() => {
app.get('/api/contract-was-forgotten', async () => ({ ok: true }));
}).toThrow(/\u7f3a\u5c11\u5171\u4eab API \u5951\u7ea6/);
} finally {
await app.close();
}
});
it('只豁免明确的 Provider 回调、原始对象流和 302 下载', () => {
expect([...nonJsonApiRouteExemptions.keys()]).toEqual([
'POST /api/payments/notify',
'POST /api/payments/refunds/notify',
'POST /api/providers/ark/callback/:token',
'GET /api/providers/volc/real-portrait-validation/:opaqueNonce',
'GET /api/assets/:id/download',
'PUT /api/storage/local/*',
'GET /api/storage/local/*',
]);
});
it('自动注入的成功响应 Schema 会过滤未声明字段', async () => {
const app = contractApp();
app.get('/api/health', async () => ({ ok: true, internalState: 'hidden' }));
try {
const response = await app.inject({ method: 'GET', url: '/api/health' });
expect(response.statusCode).toBe(200);
expect(response.json()).toEqual({ ok: true });
} finally {
await app.close();
}
});
it('自动注入的成功响应 Schema 不匹配时返回统一 500', async () => {
const app = contractApp();
app.get('/api/health', async () => ({ ok: false }));
try {
const response = await app.inject({ method: 'GET', url: '/api/health' });
expect(response.statusCode).toBe(500);
expect(response.json()).toEqual({
message: '服务器响应格式错误',
code: 'API_RESPONSE_SCHEMA_MISMATCH',
requestId: expect.any(String),
});
} finally {
await app.close();
}
});
it('自动注入的公共错误 Schema 会过滤未声明的服务端字段', async () => {
const app = contractApp();
app.get('/api/health', async (request, reply) => reply.code(418).send({
message: '测试错误',
code: 'TEST_ERROR',
requestId: request.id,
databaseDebug: '不得发送',
}));
try {
const response = await app.inject({ method: 'GET', url: '/api/health' });
expect(response.statusCode).toBe(418);
expect(response.json()).toEqual({
message: '测试错误',
code: 'TEST_ERROR',
requestId: expect.any(String),
});
} finally {
await app.close();
}
});
});
function contractApp() {
const app = fastify({ logger: false });
app.setValidatorCompiler(validatorCompiler);
app.setSerializerCompiler(serializerCompiler);
registerApiContractRouteHook(app);
setApiErrorHandler(app);
return app;
}