feat(api): 搭建 API 与 Worker 骨架及契约基线
NestJS 11 + Fastify 统一错误体与 trace_id,/health 真实探活 DB/Redis,OpenAPI 3.1 契约含 20 个错误码;shared 包提供错误码、权限点、ULID、分页与通用 schema;worker 完成队列进程骨架。
This commit is contained in:
1 parent
09dbb27b66
commit
23067edb8f
70 files changed
+2445
No files matched your search
@@ -0,0 +1,91 @@
|
||||
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 };
|
||||
}
|
||||
Reference in new issue
Block a user