diff --git a/src/routes/system.routes.test.ts b/src/routes/system.routes.test.ts index 2dc751a4..ff66ae56 100644 --- a/src/routes/system.routes.test.ts +++ b/src/routes/system.routes.test.ts @@ -12,11 +12,10 @@ vi.mock('util', async (importOriginal) => { const actual = await importOriginal(); return { ...actual, - default: actual, promisify: (fn: Function) => { return (...args: any[]) => { return new Promise((resolve, reject) => { - fn(...args, (err: Error | null, stdout: unknown, stderr: unknown) => { + fn(...args, (err: Error | null, stdout: string, stderr: string) => { if (err) { // Attach stdout/stderr to the error object to mimic child_process.exec behavior Object.assign(err, { stdout, stderr }); @@ -41,7 +40,6 @@ vi.mock('child_process', () => { }); return { - default: { exec: mockExec }, exec: mockExec, }; }); diff --git a/src/services/analyticsService.server.test.ts b/src/services/analyticsService.server.test.ts index 3de501bc..8311d9c4 100644 --- a/src/services/analyticsService.server.test.ts +++ b/src/services/analyticsService.server.test.ts @@ -127,12 +127,21 @@ describe('AnalyticsService', () => { throw new Error('Processing failed'); }); // "Successfully generated..." - const promise = service.processWeeklyReportJob(job); + // Wrap the async operation that is expected to reject in a function. + // This prevents an "unhandled rejection" error by ensuring the `expect.rejects` + // is actively waiting for the promise to reject when the timers are advanced. + const testFunction = async () => { + const promise = service.processWeeklyReportJob(job); + // Advance timers to trigger the part of the code that throws. + await vi.advanceTimersByTimeAsync(30000); + // Await the promise to allow the rejection to be caught by `expect.rejects`. + await promise; + }; - await vi.advanceTimersByTimeAsync(30000); - - await expect(promise).rejects.toThrow('Processing failed'); + // Now, assert that the entire operation rejects as expected. + await expect(testFunction()).rejects.toThrow('Processing failed'); + // Verify the side effect (error logging) after the rejection is confirmed. expect(mockLoggerInstance.error).toHaveBeenCalledWith( expect.objectContaining({ err: expect.any(Error), diff --git a/src/services/authService.test.ts b/src/services/authService.test.ts index 1101dd23..ccf014d1 100644 --- a/src/services/authService.test.ts +++ b/src/services/authService.test.ts @@ -1,70 +1,16 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; - -// Set environment variables before importing the service -process.env.JWT_SECRET = 'test-secret'; -process.env.FRONTEND_URL = 'http://localhost:3000'; - -import { authService } from './authService'; -import * as bcrypt from 'bcrypt'; -import jwt from 'jsonwebtoken'; -import crypto from 'crypto'; -import { userRepo, adminRepo } from './db/index.db'; import { UniqueConstraintError } from './db/errors.db'; -import { logger } from './logger.server'; -import { sendPasswordResetEmail } from './emailService.server'; import type { UserProfile } from '../types'; -// Mock dependencies -vi.mock('bcrypt'); -vi.mock('jsonwebtoken'); -vi.mock('crypto', () => ({ - default: { - randomBytes: vi.fn().mockReturnValue({ - toString: vi.fn().mockReturnValue('mocked-random-string'), - }), - }, -})); - -vi.mock('./db/index.db', () => ({ - userRepo: { - createUser: vi.fn(), - saveRefreshToken: vi.fn(), - findUserByEmail: vi.fn(), - createPasswordResetToken: vi.fn(), - getValidResetTokens: vi.fn(), - updateUserPassword: vi.fn(), - deleteResetToken: vi.fn(), - findUserByRefreshToken: vi.fn(), - findUserProfileById: vi.fn(), - deleteRefreshToken: vi.fn(), - }, - adminRepo: { - logActivity: vi.fn(), - }, -})); - -vi.mock('./logger.server', () => ({ - logger: { - info: vi.fn(), - error: vi.fn(), - warn: vi.fn(), - debug: vi.fn(), - }, -})); - -vi.mock('./emailService.server', () => ({ - sendPasswordResetEmail: vi.fn(), -})); - -vi.mock('./db/connection.db', () => ({ - getPool: vi.fn(), -})); - -vi.mock('../utils/authUtils', () => ({ - validatePasswordStrength: vi.fn(), -})); - describe('AuthService', () => { + let authService: typeof import('./authService').authService; + let bcrypt: typeof import('bcrypt'); + let jwt: typeof import('jsonwebtoken'); + let userRepo: typeof import('./db/index.db').userRepo; + let adminRepo: typeof import('./db/index.db').adminRepo; + let logger: typeof import('./logger.server').logger; + let sendPasswordResetEmail: typeof import('./emailService.server').sendPasswordResetEmail; + const reqLog = {}; // Mock request logger object const mockUser = { user_id: 'user-123', @@ -76,8 +22,59 @@ describe('AuthService', () => { role: 'user', } as unknown as UserProfile; - beforeEach(() => { + beforeEach(async () => { vi.clearAllMocks(); + vi.resetModules(); + + // Set environment variables before any modules are imported + process.env.JWT_SECRET = 'test-secret'; + process.env.FRONTEND_URL = 'http://localhost:3000'; + + // Mock all dependencies before dynamically importing the service + vi.mock('bcrypt'); + vi.mock('jsonwebtoken'); + vi.mock('crypto', () => ({ + default: { + randomBytes: vi.fn().mockReturnValue({ + toString: vi.fn().mockReturnValue('mocked-random-string'), + }), + }, + })); + vi.mock('./db/index.db', () => ({ + userRepo: { + createUser: vi.fn(), + saveRefreshToken: vi.fn(), + findUserByEmail: vi.fn(), + createPasswordResetToken: vi.fn(), + getValidResetTokens: vi.fn(), + updateUserPassword: vi.fn(), + deleteResetToken: vi.fn(), + findUserByRefreshToken: vi.fn(), + findUserProfileById: vi.fn(), + deleteRefreshToken: vi.fn(), + }, + adminRepo: { + logActivity: vi.fn(), + }, + })); + vi.mock('./logger.server', () => ({ + logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() }, + })); + vi.mock('./emailService.server', () => ({ + sendPasswordResetEmail: vi.fn(), + })); + vi.mock('./db/connection.db', () => ({ getPool: vi.fn() })); + vi.mock('../utils/authUtils', () => ({ validatePasswordStrength: vi.fn() })); + + // Dynamically import modules to get the mocked versions and the service instance + authService = (await import('./authService')).authService; + bcrypt = await import('bcrypt'); + jwt = await import('jsonwebtoken'); + const dbModule = await import('./db/index.db'); + userRepo = dbModule.userRepo; + adminRepo = dbModule.adminRepo; + logger = (await import('./logger.server')).logger; + sendPasswordResetEmail = (await import('./emailService.server')).sendPasswordResetEmail; }); describe('registerUser', () => { diff --git a/src/services/flyerProcessingService.server.ts b/src/services/flyerProcessingService.server.ts index e0c5e501..981ffb1b 100644 --- a/src/services/flyerProcessingService.server.ts +++ b/src/services/flyerProcessingService.server.ts @@ -242,7 +242,10 @@ export class FlyerProcessingService { // Mark subsequent critical stages as skipped for (let i = errorStageIndex + 1; i < stagesToReport.length; i++) { if (stagesToReport[i].critical) { - stagesToReport[i] = { ...stagesToReport[i], status: 'skipped' }; + // When a stage is skipped, we don't need its previous 'detail' property. + // This creates a clean 'skipped' state object by removing `detail` and keeping the rest. + const { detail, ...restOfStage } = stagesToReport[i]; + stagesToReport[i] = { ...restOfStage, status: 'skipped' }; } } } diff --git a/src/services/systemService.test.ts b/src/services/systemService.test.ts index 98a7fd15..ad68f493 100644 --- a/src/services/systemService.test.ts +++ b/src/services/systemService.test.ts @@ -1,3 +1,4 @@ +// src/services/systemService.test.ts import { describe, it, expect, vi, beforeEach } from 'vitest'; import { exec, type ExecException } from 'child_process'; import { logger } from './logger.server'; @@ -35,10 +36,11 @@ vi.mock('util', async (importOriginal) => { }); // Mock child_process -vi.mock('child_process', () => { - const mockExec = vi.fn(); +vi.mock('child_process', async (importOriginal) => { + const actual = await importOriginal(); return { - exec: mockExec, + ...actual, + exec: vi.fn(), }; });