Files
lingxi/apps/api/src/testing/test-app.ts
T
Stev_Wang 23067edb8f feat(api): 搭建 API 与 Worker 骨架及契约基线
NestJS 11 + Fastify 统一错误体与 trace_id,/health 真实探活 DB/Redis,OpenAPI 3.1 契约含 20 个错误码;shared 包提供错误码、权限点、ULID、分页与通用 schema;worker 完成队列进程骨架。
2026-11-02 17:20:46 +08:00

92 lines
2.8 KiB
TypeScript

import { API_PREFIX, ErrorCode } from '@lingxi/shared';
import { Controller, Get, NotFoundException } from '@nestjs/common';
import { FastifyAdapter, type NestFastifyApplication } from '@nestjs/platform-fastify';
import { Test } from '@nestjs/testing';
import { type Mock, vi } from 'vitest';
import { AppException } from '../common/errors/app-exception';
import { AllExceptionsFilter } from '../common/filters/all-exceptions.filter';
import { APP_CONFIG } from '../config/config.module';
import type { AppConfig } from '../config/env';
import { PRISMA, REDIS } from '../infra/tokens';
import { HealthController } from '../modules/platform/health/health.controller';
import { HealthService } from '../modules/platform/health/health.service';
export const testConfig: AppConfig = {
nodeEnv: 'test',
isProduction: false,
port: 3000,
host: '127.0.0.1',
logLevel: 'error',
databaseUrl: 'postgresql://lingxi:lingxi@127.0.0.1:15432/lingxi_test',
redisUrl: 'redis://127.0.0.1:16379',
webOrigins: ['http://127.0.0.1:5173'],
maxUploadMb: 500,
featureVideo: false,
};
@Controller('__test')
class ThrowingController {
@Get('app-error')
appError(): never {
throw new AppException({ code: ErrorCode.STATE_NOT_ALLOWED, message: '当前状态不允许该操作' });
}
@Get('http-404')
notFound(): never {
throw new NotFoundException('资源不存在');
}
@Get('boom')
boom(): never {
throw new Error('unexpected failure');
}
}
export interface TestAppOptions {
databaseUp?: boolean;
redisUp?: boolean;
includeErrorRoutes?: boolean;
}
export interface TestApp {
app: NestFastifyApplication;
prisma: { $queryRaw: Mock };
redis: { ping: Mock };
}
export async function createTestApp(options: TestAppOptions = {}): Promise<TestApp> {
const prisma = {
$queryRaw:
options.databaseUp === false
? vi.fn().mockRejectedValue(new Error('connect ECONNREFUSED 127.0.0.1:15432'))
: vi.fn().mockResolvedValue([{ '?column?': 1 }]),
};
const redis = {
ping:
options.redisUp === false
? vi.fn().mockRejectedValue(new Error('connect ECONNREFUSED 127.0.0.1:16379'))
: vi.fn().mockResolvedValue('PONG'),
};
const moduleRef = await Test.createTestingModule({
controllers:
options.includeErrorRoutes === true
? [HealthController, ThrowingController]
: [HealthController],
providers: [
HealthService,
{ provide: PRISMA, useValue: prisma },
{ provide: REDIS, useValue: redis },
{ provide: APP_CONFIG, useValue: testConfig },
],
}).compile();
const app = moduleRef.createNestApplication<NestFastifyApplication>(new FastifyAdapter());
app.setGlobalPrefix(API_PREFIX, { exclude: ['health'] });
app.useGlobalFilters(new AllExceptionsFilter());
await app.init();
return { app, prisma, redis };
}