fix(portal-bff): set REDIS_URL + SESSION_* in auth.module.spec so ci:check passes on a clean runner
#115 made AuthModule import SessionModule (for AuthController to inject UserSessionIndexService). SessionModule transitively pulls in RedisModule whose factory calls assertRedisConfig() — that throws when REDIS_URL is unset. auth.module.spec only seeded the ENTRA_* env vars, so compile() blew up the moment it walked the new import graph. caught only by ci, not locally: nx auto-loads apps/portal-bff/.env for tasks, which my dev box has. the ci runner starts with a clean environment, surfaces the real failure path, and ci:check went red on main right after #115 merged. repro of the ci condition locally: env -u REDIS_URL -u SESSION_SECRET -u SESSION_ENCRYPTION_KEY \ -u DATABASE_URL -u ENTRA_INSTANCE_URL -u ENTRA_TENANT_ID \ -u ENTRA_CLIENT_ID -u ENTRA_CLIENT_SECRET \ -u ENTRA_REDIRECT_URI -u ENTRA_POST_LOGOUT_REDIRECT_URI \ pnpm exec nx test portal-bff --skip-nx-cache fix: extend the spec's VALID env block with REDIS_URL (a well-formed but unreachable url so ioredis stays lazy), SESSION_SECRET, and SESSION_ENCRYPTION_KEY — same pattern as session.module.spec.ts. and dispose the ioredis client in afterEach so jest doesn't hang on its reconnect timer (the spec now opens a real connection at module init). 123/123 pass under the clean-env repro. no production code change.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { randomBytes } from 'node:crypto';
|
||||
import { ConfidentialClientApplication } from '@azure/msal-node';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { LoggerModule } from 'nestjs-pino';
|
||||
@@ -5,6 +6,13 @@ import { AuthModule } from './auth.module';
|
||||
import { ENTRA_CONFIG, type EntraConfig } from './entra-config.token';
|
||||
import { MSAL_CLIENT } from './msal-client.token';
|
||||
|
||||
// AuthModule imports SessionModule (so AuthController can inject
|
||||
// `UserSessionIndexService`), which transitively pulls in
|
||||
// RedisModule + the session-secret / session-encryption-key
|
||||
// validators. The spec has to satisfy all of those env vars or the
|
||||
// test fails as soon as `compile()` walks the import graph — which
|
||||
// is exactly what bit the CI on PR #115 (CI starts with a clean env;
|
||||
// locally `nx test` was loading apps/portal-bff/.env and masking it).
|
||||
const VALID = {
|
||||
ENTRA_INSTANCE_URL: 'https://login.microsoftonline.com/',
|
||||
ENTRA_TENANT_ID: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee',
|
||||
@@ -12,6 +20,11 @@ const VALID = {
|
||||
ENTRA_CLIENT_SECRET: 's3cret-value-from-entra',
|
||||
ENTRA_REDIRECT_URI: 'http://localhost:3000/api/auth/callback',
|
||||
ENTRA_POST_LOGOUT_REDIRECT_URI: 'http://localhost:4200/',
|
||||
// Well-formed but unreachable Redis URL — `ioredis` opens the
|
||||
// socket lazily so the module compiles without any network access.
|
||||
REDIS_URL: 'redis://default:test-pass@127.0.0.1:65535/0',
|
||||
SESSION_SECRET: randomBytes(32).toString('base64url'),
|
||||
SESSION_ENCRYPTION_KEY: randomBytes(32).toString('base64url'),
|
||||
};
|
||||
|
||||
// AuthModule's MSAL_CLIENT factory injects the Pino-backed `Logger`
|
||||
@@ -27,6 +40,7 @@ async function compile() {
|
||||
|
||||
describe('AuthModule', () => {
|
||||
const originalEnv: Record<string, string | undefined> = {};
|
||||
let ref: Awaited<ReturnType<typeof compile>> | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
for (const key of Object.keys(VALID) as Array<keyof typeof VALID>) {
|
||||
@@ -35,7 +49,16 @@ describe('AuthModule', () => {
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
afterEach(async () => {
|
||||
// SessionModule (transitive) opens an `ioredis` client at module
|
||||
// init; explicitly disconnect so Jest doesn't hang on its
|
||||
// reconnect timer between tests.
|
||||
if (ref) {
|
||||
const redis = ref.get<{ disconnect: () => void }>('REDIS_CLIENT');
|
||||
redis.disconnect();
|
||||
await ref.close();
|
||||
ref = undefined;
|
||||
}
|
||||
for (const key of Object.keys(VALID) as Array<keyof typeof VALID>) {
|
||||
const saved = originalEnv[key];
|
||||
if (saved === undefined) {
|
||||
@@ -47,14 +70,14 @@ describe('AuthModule', () => {
|
||||
});
|
||||
|
||||
it('provides EntraConfig via the ENTRA_CONFIG token', async () => {
|
||||
const ref = await compile();
|
||||
ref = await compile();
|
||||
const config = ref.get<EntraConfig>(ENTRA_CONFIG);
|
||||
expect(config.clientId).toBe(VALID.ENTRA_CLIENT_ID);
|
||||
expect(config.authority).toBe(`${VALID.ENTRA_INSTANCE_URL}${VALID.ENTRA_TENANT_ID}`);
|
||||
});
|
||||
|
||||
it('provides a ConfidentialClientApplication via the MSAL_CLIENT token', async () => {
|
||||
const ref = await compile();
|
||||
ref = await compile();
|
||||
const client = ref.get<ConfidentialClientApplication>(MSAL_CLIENT);
|
||||
expect(client).toBeInstanceOf(ConfidentialClientApplication);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user