api-server/test/m1.e2e-spec.ts
WangDL a08fd4970a
All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 39s
feat: M1-04 — Content Safety deepening, reports CAPI, violation records
- Add ViolationRecord table (Prisma + migration)
- CAPI POST /api/reports for user report submission
- AAPI reports list + handle, violations list + penalty apply
- Admin page: reports management + violation records tabs

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 10:53:19 +08:00

292 lines
11 KiB
TypeScript

import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import request from 'supertest';
import { AppModule } from '../src/app.module';
describe('M1 E2E Tests', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
app.setGlobalPrefix('api', { exclude: ['admin-api/(.*)', 'internal/(.*)'] });
await app.init();
});
afterAll(async () => {
await app.close();
});
async function loginAdmin(): Promise<string> {
const res = await request(app.getHttpServer())
.post('/admin-api/auth/login')
.send({ email: 'admin@zhixi.app', password: 'admin123' });
return res.body?.data?.accessToken || '';
}
// ══════════════════════════════════════════════
// M1-01: AI Gateway 深化
// ══════════════════════════════════════════════
describe('M1-01 AI Gateway Deepening', () => {
let token: string;
beforeAll(async () => { token = await loginAdmin(); });
it('GET /admin-api/ai-gateway/status → 200 with routes info', async () => {
if (!token) return;
const res = await request(app.getHttpServer())
.get('/admin-api/ai-gateway/status')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(res.body.data).toHaveProperty('providers');
expect(res.body.data).toHaveProperty('routes');
expect(res.body.data).toHaveProperty('activeRoutes');
});
// ── Model Routes CRUD ──
it('GET /admin-api/ai-gateway/routes → 200 with route list', async () => {
if (!token) return;
const res = await request(app.getHttpServer())
.get('/admin-api/ai-gateway/routes')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(Array.isArray(res.body.data)).toBe(true);
});
it('GET /admin-api/ai-gateway/routes → 401 without token', async () => {
await request(app.getHttpServer())
.get('/admin-api/ai-gateway/routes')
.expect(401);
});
it('POST /admin-api/ai-gateway/routes → creates route and reloads cache', async () => {
if (!token) return;
const res = await request(app.getHttpServer())
.post('/admin-api/ai-gateway/routes')
.set('Authorization', `Bearer ${token}`)
.send({
tier: 'cheap',
taskType: 'test-e2e',
preferredProvider: 'deepseek',
preferredModel: 'deepseek-v4-flash',
fallbackProvider: 'deepseek',
fallbackModel: 'deepseek-v4-flash',
maxRetries: 1,
})
.expect([200, 201]);
if (res.body?.data?.id) {
await request(app.getHttpServer())
.delete(`/admin-api/ai-gateway/routes/${res.body.data.id}`)
.set('Authorization', `Bearer ${token}`);
}
});
it('PUT /admin-api/ai-gateway/routes/:id → updates route', async () => {
if (!token) return;
const create = await request(app.getHttpServer())
.post('/admin-api/ai-gateway/routes')
.set('Authorization', `Bearer ${token}`)
.send({
tier: 'strong',
taskType: 'test-update',
preferredProvider: 'deepseek',
preferredModel: 'deepseek-v4-pro',
fallbackProvider: 'minimax',
fallbackModel: 'minimax-m2.7',
maxRetries: 2,
});
const id = create.body?.data?.id;
if (!id) return;
await request(app.getHttpServer())
.put(`/admin-api/ai-gateway/routes/${id}`)
.set('Authorization', `Bearer ${token}`)
.send({ maxRetries: 4 })
.expect(200);
await request(app.getHttpServer())
.delete(`/admin-api/ai-gateway/routes/${id}`)
.set('Authorization', `Bearer ${token}`);
});
// ── Provider management ──
it('GET /admin-api/ai-gateway/providers → 200 with provider list', async () => {
if (!token) return;
const res = await request(app.getHttpServer())
.get('/admin-api/ai-gateway/providers')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(Array.isArray(res.body.data)).toBe(true);
});
it('PUT /admin-api/ai-gateway/providers/:name → enables/disables provider', async () => {
if (!token) return;
await request(app.getHttpServer())
.put('/admin-api/ai-gateway/providers/deepseek')
.set('Authorization', `Bearer ${token}`)
.send({ enabled: true })
.expect(200);
});
// ── Fallback events ──
it('GET /admin-api/ai-gateway/fallback-events → 200 with events list', async () => {
if (!token) return;
const res = await request(app.getHttpServer())
.get('/admin-api/ai-gateway/fallback-events')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(Array.isArray(res.body.data)).toBe(true);
});
});
// ══════════════════════════════════════════════
// M1-02: Vector & Retrieval Module
// ══════════════════════════════════════════════
describe('M1-02 Vector & Retrieval', () => {
let token: string;
beforeAll(async () => { token = await loginAdmin(); });
it('GET /admin-api/vector/collection → 200 with collection info', async () => {
if (!token) return;
const res = await request(app.getHttpServer())
.get('/admin-api/vector/collection')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(res.body.data).toHaveProperty('name');
expect(res.body.data).toHaveProperty('pointsCount');
});
it('GET /admin-api/vector/collection → 401 without token', async () => {
await request(app.getHttpServer())
.get('/admin-api/vector/collection')
.expect(401);
});
it('GET /admin-api/vector/count → 200 with vector count', async () => {
if (!token) return;
const res = await request(app.getHttpServer())
.get('/admin-api/vector/count')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(res.body.data).toHaveProperty('collection');
expect(res.body.data).toHaveProperty('count');
});
it('POST /admin-api/vector/reindex → 200 (reserved)', async () => {
if (!token) return;
const res = await request(app.getHttpServer())
.post('/admin-api/vector/reindex')
.set('Authorization', `Bearer ${token}`)
.expect([200, 201]);
expect(res.body.data).toHaveProperty('message');
});
});
// ══════════════════════════════════════════════
// M1-03: Task Queue 深化
// ══════════════════════════════════════════════
describe('M1-03 Task Queue Deepening', () => {
let token: string;
beforeAll(async () => { token = await loginAdmin(); });
it('GET /admin-api/events/stats → 200 with task type configs', async () => {
if (!token) return;
const res = await request(app.getHttpServer())
.get('/admin-api/events/stats')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(res.body.data).toHaveProperty('taskStats');
expect(res.body.data).toHaveProperty('totalTaskTypes');
});
it('GET /admin-api/events/stats → 401 without token', async () => {
await request(app.getHttpServer())
.get('/admin-api/events/stats')
.expect(401);
});
it('GET /admin-api/events/workers → 200 with worker status', async () => {
if (!token) return;
const res = await request(app.getHttpServer())
.get('/admin-api/events/workers')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(res.body.data).toHaveProperty('workers');
expect(res.body.data).toHaveProperty('count');
});
it('POST /admin-api/events/:queue/jobs/batch-retry → 200', async () => {
if (!token) return;
await request(app.getHttpServer())
.post('/admin-api/events/ai-analysis/jobs/batch-retry')
.set('Authorization', `Bearer ${token}`)
.send({ count: 10 })
.expect([200, 201]);
});
});
// ══════════════════════════════════════════════
// M1-04: Content Safety 深化
// ══════════════════════════════════════════════
describe('M1-04 Content Safety Deepening', () => {
let token: string;
beforeAll(async () => { token = await loginAdmin(); });
it('POST /api/reports → 201 submit user report', async () => {
const res = await request(app.getHttpServer())
.post('/api/reports')
.send({ targetType: 'knowledge_item', targetId: 'test123', reason: '包含错误信息', reporterId: 'user1' })
.expect([200, 201]);
expect(res.body.success).toBe(true);
});
it('GET /admin-api/content-safety/reports → 200 list reports', async () => {
if (!token) return;
const res = await request(app.getHttpServer())
.get('/admin-api/content-safety/reports')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(Array.isArray(res.body.data)).toBe(true);
});
it('GET /admin-api/content-safety/reports → 401 without token', async () => {
await request(app.getHttpServer())
.get('/admin-api/content-safety/reports')
.expect(401);
});
it('GET /admin-api/content-safety/violations → 200 list violations', async () => {
if (!token) return;
const res = await request(app.getHttpServer())
.get('/admin-api/content-safety/violations')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(Array.isArray(res.body.data)).toBe(true);
});
it('POST /admin-api/content-safety/violations/:id/penalty → apply penalty', async () => {
if (!token) return;
const res = await request(app.getHttpServer())
.post('/admin-api/content-safety/violations/test-id/penalty')
.set('Authorization', `Bearer ${token}`)
.send({ penalty: 'warning' })
.expect([200, 201]);
expect(res.body.success).toBe(true);
});
it('POST /admin-api/content-safety/reports/:id/handle → handle report', async () => {
if (!token) return;
const res = await request(app.getHttpServer())
.post('/admin-api/content-safety/reports/test-id/handle')
.set('Authorization', `Bearer ${token}`)
.send({ action: 'dismissed', note: '已处理' })
.expect([200, 201]);
expect(res.body.success).toBe(true);
});
});
});