import { type AddressInfo } from 'node:net'; import { describe, expect, it } from 'vitest'; import { bootstrapApi } from '../apps/api/src/main'; import { bootstrapScheduler } from '../apps/scheduler/src/main'; import { bootstrapWorker } from '../apps/worker/src/main'; import type { ApiRuntimeConfig, SchedulerRuntimeConfig, WorkerRuntimeConfig, } from '../libs/config/src/runtime-config'; const redisConfig = Object.freeze({ host: '127.0.0.1', port: 6379, username: '', password: '', database: 0, tls: false, }); const mysqlConfig = Object.freeze({ enabled: false, host: '127.0.0.1', port: 3306, database: 'a_share_analysis_dev', username: '', password: '', charset: 'utf8mb4' as const, collation: 'utf8mb4_general_ci' as const, timezone: '+08:00' as const, }); const aiConfig = Object.freeze({ enabled: false, baseUrl: '', apiKey: '', model: '', timeoutMs: 30_000, }); const rocketMqConfig = Object.freeze({ enabled: false, nameServerAddress: '127.0.0.1:9876', endpoint: '127.0.0.1:8081', topicPrefix: 'a-share-analysis', }); describe('NestJS 三进程入口', () => { it('API 使用真实 HTTP 应用监听注入端口并可关闭', async () => { const config: ApiRuntimeConfig = { processName: 'api', appEnv: 'development', logLevel: 'debug', api: Object.freeze({ host: '127.0.0.1', port: 3000 }), redis: redisConfig, mysql: mysqlConfig, ai: aiConfig, }; const app = await bootstrapApi({ config, port: 0, logger: false }); const address = app.getHttpServer().address() as AddressInfo; expect(address.address).toBe('127.0.0.1'); expect(address.port).toBeGreaterThan(0); await app.close(); expect(app.getHttpServer().listening).toBe(false); }); it('Scheduler 使用无 HTTP 端口的应用上下文并可关闭', async () => { const config: SchedulerRuntimeConfig = { processName: 'scheduler', appEnv: 'development', logLevel: 'debug', scheduler: Object.freeze({ enabled: true, timezone: 'Asia/Shanghai' }), redis: Object.freeze({ ...redisConfig, database: 1 }), rocketMq: Object.freeze({ ...rocketMqConfig, sendTimeoutMs: 3000 }), }; const app = await bootstrapScheduler({ config, logger: false }); expect('getHttpServer' in app).toBe(false); await expect(app.close()).resolves.toBeUndefined(); }); it('Worker 使用无 HTTP 端口的应用上下文并可关闭', async () => { const config: WorkerRuntimeConfig = { processName: 'worker', appEnv: 'development', logLevel: 'debug', marketData: Object.freeze({ mode: 'mock', provider: 'deterministic-fixture', baseUrl: '', apiKey: '', }), redis: Object.freeze({ ...redisConfig, database: 2 }), mysql: mysqlConfig, rocketMq: Object.freeze({ ...rocketMqConfig, consumerGroup: 'a-share-analysis-worker', consumerConcurrency: 4, }), ai: aiConfig, }; const app = await bootstrapWorker({ config, logger: false }); expect('getHttpServer' in app).toBe(false); await expect(app.close()).resolves.toBeUndefined(); }); });