diff --git a/src/config/env.ts b/src/config/env.ts index 7aba5ac9..3e58ca59 100644 --- a/src/config/env.ts +++ b/src/config/env.ts @@ -262,8 +262,9 @@ function parseConfig(): EnvConfig { '', ].join('\n'); - // In test environment, throw instead of exiting to allow test frameworks to catch - if (process.env.NODE_ENV === 'test') { + // In test/staging environment, throw instead of exiting to allow test frameworks to catch + // and to provide better visibility into config errors during staging deployments + if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'staging') { throw new Error(errorMessage); } @@ -323,6 +324,19 @@ export const isDevelopment = config.server.nodeEnv === 'development'; */ export const isStaging = config.server.nodeEnv === 'staging'; +/** + * Returns true if running in a test-like environment (test or staging). + * Use this for behaviors that should be shared between unit/integration tests + * and the staging deployment server, such as: + * - Using mock AI services (no GEMINI_API_KEY required) + * - Verbose error logging + * - Fallback URL handling + * + * Do NOT use this for security bypasses (auth, rate limiting) - those should + * only be active in NODE_ENV=test, not staging. + */ +export const isTestLikeEnvironment = isTest || isStaging; + /** * Returns true if SMTP is configured (all required fields present). */ diff --git a/src/middleware/errorHandler.ts b/src/middleware/errorHandler.ts index 550dfd1f..a977062a 100644 --- a/src/middleware/errorHandler.ts +++ b/src/middleware/errorHandler.ts @@ -161,9 +161,12 @@ export const errorHandler = (err: Error, req: Request, res: Response, next: Next `Unhandled API Error (ID: ${errorId})`, ); - // Also log to console in test environment for visibility in test runners - if (process.env.NODE_ENV === 'test') { - console.error(`--- [TEST] UNHANDLED ERROR (ID: ${errorId}) ---`, err); + // Also log to console in test/staging environments for visibility in test runners + if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'staging') { + console.error( + `--- [${process.env.NODE_ENV?.toUpperCase()}] UNHANDLED ERROR (ID: ${errorId}) ---`, + err, + ); } // In production, send a generic message to avoid leaking implementation details. diff --git a/src/routes/ai.routes.ts b/src/routes/ai.routes.ts index 485432a0..c5e1114a 100644 --- a/src/routes/ai.routes.ts +++ b/src/routes/ai.routes.ts @@ -239,10 +239,13 @@ router.post( 'Handling /upload-and-process', ); - // Fix: Explicitly clear userProfile if no auth header is present in test env + // Fix: Explicitly clear userProfile if no auth header is present in test/staging env // This prevents mockAuth from injecting a non-existent user ID for anonymous requests. let userProfile = req.user as UserProfile | undefined; - if (process.env.NODE_ENV === 'test' && !req.headers['authorization']) { + if ( + (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'staging') && + !req.headers['authorization'] + ) { userProfile = undefined; } diff --git a/src/services/aiService.server.ts b/src/services/aiService.server.ts index b2a8e52c..0fedad64 100644 --- a/src/services/aiService.server.ts +++ b/src/services/aiService.server.ts @@ -160,7 +160,11 @@ export class AIService { this.logger = logger; this.logger.info('---------------- [AIService] Constructor Start ----------------'); - const isTestEnvironment = process.env.NODE_ENV === 'test' || !!process.env.VITEST_POOL_ID; + // Use mock AI in test and staging environments (no real API calls, no GEMINI_API_KEY needed) + const isTestEnvironment = + process.env.NODE_ENV === 'test' || + process.env.NODE_ENV === 'staging' || + !!process.env.VITEST_POOL_ID; if (aiClient) { this.logger.info( diff --git a/src/utils/serverUtils.ts b/src/utils/serverUtils.ts index 4b362650..ac2ed60c 100644 --- a/src/utils/serverUtils.ts +++ b/src/utils/serverUtils.ts @@ -15,9 +15,9 @@ export function getBaseUrl(logger: Logger): string { let baseUrl = (process.env.FRONTEND_URL || process.env.BASE_URL || '').trim(); if (!baseUrl || !baseUrl.startsWith('http')) { const port = process.env.PORT || 3000; - // In test/development, use http://localhost. In production, this should never be reached. + // In test/staging/development, use http://localhost. In production, this should never be reached. const fallbackUrl = - process.env.NODE_ENV === 'test' + process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'staging' ? `http://localhost:${port}` : `http://example.com:${port}`; if (baseUrl) { @@ -39,4 +39,4 @@ export function getBaseUrl(logger: Logger): string { } return finalUrl; -} \ No newline at end of file +}