NestJS 11 + Fastify 统一错误体与 trace_id,/health 真实探活 DB/Redis,OpenAPI 3.1 契约含 20 个错误码;shared 包提供错误码、权限点、ULID、分页与通用 schema;worker 完成队列进程骨架。
92 lines
2.8 KiB
TypeScript
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 };
|
|
}
|