import { APP_GUARD } from '@nestjs/core'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { MODULE_METADATA } from '@nestjs/common/constants'; import { Test } from '@nestjs/testing'; import { getOptionsToken, ThrottlerGuard, ThrottlerModule, } from '@nestjs/throttler'; import { JwtAuthGuard } from 'src/auth/jwt-auth.guard'; import { AppModule } from 'src/app.module'; type DynamicModuleLike = { module?: unknown; global?: boolean; providers?: Array<{ provide?: unknown; useClass?: unknown; useValue?: unknown; }>; }; type AppImportEntry = DynamicModuleLike | Promise; describe('AppModule конфигурация', () => { afterEach(() => { delete process.env.ALLOWED_ORIGINS; delete process.env.JWT_SECRET; }); it('должен регистрировать ThrottlerModule с лимитом 1000/60000ms (лояльный API)', () => { const imports = (Reflect.getMetadata(MODULE_METADATA.IMPORTS, AppModule) ?? []) as DynamicModuleLike[]; const throttlerDynamicModule = imports.find( (entry) => entry.module === ThrottlerModule, ); expect(throttlerDynamicModule).toBeDefined(); const throttlerOptionsProvider = throttlerDynamicModule?.providers?.find( (provider) => provider.provide === getOptionsToken(), ); expect(throttlerOptionsProvider).toBeDefined(); expect(throttlerOptionsProvider?.useValue).toEqual( expect.arrayContaining([ expect.objectContaining({ limit: 1000, ttl: 60000, }), ]), ); }); it('должен регистрировать ThrottlerGuard и JwtAuthGuard как глобальные guards', () => { const providers = (Reflect.getMetadata( MODULE_METADATA.PROVIDERS, AppModule, ) ?? []) as DynamicModuleLike['providers']; expect(providers).toEqual( expect.arrayContaining([ expect.objectContaining({ provide: APP_GUARD, useClass: ThrottlerGuard, }), expect.objectContaining({ provide: APP_GUARD, useClass: JwtAuthGuard, }), ]), ); }); it('должен подключать ConfigModule как глобальный', async () => { const imports = (Reflect.getMetadata(MODULE_METADATA.IMPORTS, AppModule) ?? []) as AppImportEntry[]; const resolvedImports = await Promise.all( imports.map((entry) => entry instanceof Promise ? entry : Promise.resolve(entry), ), ); const configDynamicModule = resolvedImports.find( (entry) => entry.module === ConfigModule, ); expect(configDynamicModule).toBeDefined(); expect(configDynamicModule?.global).toBe(true); }); it('должен делать ConfigService доступным и читать ALLOWED_ORIGINS из env', async () => { process.env.ALLOWED_ORIGINS = 'http://test.local:8080,http://localhost'; const moduleRef = await Test.createTestingModule({ imports: [ ConfigModule.forRoot({ isGlobal: true, ignoreEnvFile: true, }), ], }).compile(); const configService = moduleRef.get(ConfigService); expect(configService).toBeDefined(); expect(configService.get('ALLOWED_ORIGINS')).toBe( 'http://test.local:8080,http://localhost', ); }); it('должен использовать достаточно длинный JWT_SECRET в тестовых env', () => { process.env.JWT_SECRET = 'test-secret-key-for-jwt-signing-1234567890'; const jwtSecret = process.env.JWT_SECRET; expect(jwtSecret).toBeDefined(); expect(jwtSecret?.length).toBeGreaterThanOrEqual(32); }); });