import type { INestApplication } from '@nestjs/common'; import type { TestingModule } from '@nestjs/testing'; import type { PlatformContext } from '@rsdk/core'; /** * В этом интерфейсе поля и методы которые мы позволяем использовать из оригинального TestingModule. * Делаем так, а не `implements Pick<...>` чтобы не писать портянки с объявлением всех полей */ // eslint-disable-next-line @typescript-eslint/no-empty-interface,@typescript-eslint/no-unsafe-declaration-merging export interface PlatformTestingModule extends Pick {} export type PlatformTestNestApplication< T extends INestApplication = INestApplication, > = Omit; /** * Private api * * Обертка вокруг `TestingModule`, для того чтобы скрыть неподходящий API, например методы создания приложения */ // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging export class PlatformTestingModule { constructor( private compiledTestingModule: TestingModule, private context: PlatformContext, ) { this.get = this.compiledTestingModule.get.bind(this.compiledTestingModule); this.resolve = this.compiledTestingModule.resolve.bind( this.compiledTestingModule, ); this.close = this.compiledTestingModule.close.bind( this.compiledTestingModule, ); } async init(): Promise { await this.compiledTestingModule.init(); return this; } /** * Возвращает стандартный NestApplication сконфигурированный согласно требованиям платформы * Нужен для использования e2e тестирования http интерфейса через `supertest` или `.inject()` от fastify * *НЕ ИСПОЛЬЗОВАТЬ ДЛЯ ЗАПУСКА* */ async createNestApplication< T extends INestApplication = INestApplication, >(): Promise> { const context = this.context; const createOptions = await context.getNestFactoryCreateOptions(); const app = this.compiledTestingModule.createNestApplication( ...createOptions, ); await context.configureApp(app); return app; } /** * Создаёт, конфигурирует и запускает инстанс `INestApplication` согласно требованиям платформы * Прежде всего нужен для e2e тестирования микросервисов * * Почему не `moduleRef.createNestApplication().listen()`? * Потому что в платформе _свои_ правила запуска приложения, а именно хуки при запуске приложения. * Чтобы окружение в тестах было более приближенно к боевому этот метод также выполняет эти хуки * Да - теоретически можно было сделать два метода, `createNestApplication` и `start` который бы принимал на вход созданное приложение. * Однако автора увидел в таком раскладе потенциальную возможность ошибиться, * поэтому решил сделать `start` который внутри себя инкапсулирует логику создания, конфигурирования и запуска приложения * К тому же если измениться `api` по конфигурированию или запуску приложения то этот метод будет его имплементировать что снимает * с разработчика эту нагрузку */ async start(): Promise< PlatformTestNestApplication > { const app = await this.createNestApplication(); await this.context.runConfiguredApplication( app as unknown as INestApplication, ); return app; } }