Files
TREK/server/tests/unit/nest/app-config.test.ts
T

96 lines
3.5 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import fs from 'node:fs';
import path from 'node:path';
import { Test } from '@nestjs/testing';
import { ConfigService, ConfigType } from '@nestjs/config';
import { AppConfigModule } from '../../../src/nest/app-config/app-config.module';
import { RuntimeEnvService } from '../../../src/nest/app-config/runtime-env.service';
import { httpConfig, mcpConfig, BOOT_STABLE_TOKENS } from '../../../src/nest/app-config/tokens';
describe('RuntimeEnvService', () => {
const service = new RuntimeEnvService();
let prevDemo: string | undefined;
beforeEach(() => {
prevDemo = process.env.DEMO_MODE;
});
afterEach(() => {
if (prevDemo === undefined) delete process.env.DEMO_MODE;
else process.env.DEMO_MODE = prevDemo;
});
it('reads live — a mid-lifetime env mutation is visible on the next call', () => {
process.env.DEMO_MODE = 'true';
expect(service.isDemoMode()).toBe(true);
process.env.DEMO_MODE = 'off';
expect(service.isDemoMode()).toBe(false);
delete process.env.DEMO_MODE;
expect(service.isDemoMode()).toBe(false);
});
it('env() exposes the full live-derived namespaces', () => {
process.env.DEMO_MODE = 'yes';
expect(service.env().demo.enabled).toBe(true);
expect(service.env().app.port).toBeTypeOf('number');
});
});
describe('boot-stable tokens', () => {
it('factories re-derive from the current env on each invocation', () => {
const prev = process.env.FORCE_HTTPS;
try {
process.env.FORCE_HTTPS = 'on';
expect(httpConfig().forceHttps).toBe(true);
process.env.FORCE_HTTPS = 'off';
expect(httpConfig().forceHttps).toBe(false);
} finally {
if (prev === undefined) delete process.env.FORCE_HTTPS;
else process.env.FORCE_HTTPS = prev;
}
});
it('every token carries a namespaced KEY for @Inject', () => {
for (const token of BOOT_STABLE_TOKENS) {
expect(token.KEY).toMatch(/^CONFIGURATION\(.+\)$/);
}
});
});
describe('AppConfigModule', () => {
it('provides ConfigService, RuntimeEnvService and the loaded namespaces', async () => {
const moduleRef = await Test.createTestingModule({ imports: [AppConfigModule] }).compile();
try {
const config = moduleRef.get(ConfigService);
const runtime = moduleRef.get(RuntimeEnvService);
const mcp = moduleRef.get<ConfigType<typeof mcpConfig>>(mcpConfig.KEY);
expect(runtime).toBeInstanceOf(RuntimeEnvService);
expect(mcp.rateLimitMax).toBe(300);
// Plain get() falls through to live process.env (cache: false).
expect(config.get('NODE_ENV')).toBe('test');
} finally {
await moduleRef.close();
}
});
// Guards `ignoreEnvFile: true`: a marker var present only in server/.env must
// stay invisible. Skipped locally when a real server/.env exists (we won't
// clobber it); CI has none, so the regression is always covered there.
const envPath = path.resolve(process.cwd(), '.env');
it.skipIf(fs.existsSync(envPath))('never reads a .env file (dotenv stays index.ts-only)', async () => {
fs.writeFileSync(envPath, 'TREK_TEST_ENVFILE_MARKER=leaked\n');
try {
const moduleRef = await Test.createTestingModule({ imports: [AppConfigModule] }).compile();
try {
const config = moduleRef.get(ConfigService);
expect(config.get('TREK_TEST_ENVFILE_MARKER')).toBeUndefined();
expect(process.env.TREK_TEST_ENVFILE_MARKER).toBeUndefined();
} finally {
await moduleRef.close();
}
} finally {
fs.rmSync(envPath, { force: true });
}
});
});