232 lines
8.6 KiB
TypeScript
232 lines
8.6 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]);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|