From af8816e0af501a12881e6bdcc564afc6740dc1c5 Mon Sep 17 00:00:00 2001 From: Torben Sorensen Date: Sun, 4 Jan 2026 14:34:12 -0800 Subject: [PATCH] more and more test fixes --- src/App.test.tsx | 88 +++++++++++++++++++ src/services/authService.test.ts | 3 +- src/services/db/address.db.test.ts | 8 +- src/services/flyerProcessingService.server.ts | 47 +++++----- 4 files changed, 124 insertions(+), 22 deletions(-) diff --git a/src/App.test.tsx b/src/App.test.tsx index 706c84c1..c62a9f81 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -25,6 +25,93 @@ import { useAppInitialization } from './hooks/useAppInitialization'; // Mock top-level components rendered by App's routes +vi.mock('./components/Header', () => ({ + Header: ({ onOpenProfile, onOpenVoiceAssistant }: any) => ( +
+ + +
+ ), +})); + +vi.mock('./components/Footer', () => ({ + Footer: () =>
Mock Footer
, +})); + +vi.mock('./layouts/MainLayout', async () => { + const { Outlet } = await vi.importActual('react-router-dom'); + return { + MainLayout: () => ( +
+ +
+ ), + }; +}); + +vi.mock('./pages/HomePage', () => ({ + HomePage: ({ selectedFlyer, onOpenCorrectionTool }: any) => ( +
+ +
+ ), +})); + +vi.mock('./pages/admin/AdminPage', () => ({ + AdminPage: () =>
AdminPage
, +})); + +vi.mock('./pages/admin/CorrectionsPage', () => ({ + CorrectionsPage: () =>
CorrectionsPage
, +})); + +vi.mock('./pages/admin/AdminStatsPage', () => ({ + AdminStatsPage: () =>
AdminStatsPage
, +})); + +vi.mock('./pages/admin/FlyerReviewPage', () => ({ + FlyerReviewPage: () =>
FlyerReviewPage
, +})); + +vi.mock('./pages/VoiceLabPage', () => ({ + VoiceLabPage: () =>
VoiceLabPage
, +})); + +vi.mock('./pages/ResetPasswordPage', () => ({ + ResetPasswordPage: () =>
ResetPasswordPage
, +})); + +vi.mock('./pages/admin/components/ProfileManager', () => ({ + ProfileManager: ({ isOpen, onClose, onProfileUpdate, onLoginSuccess }: any) => + isOpen ? ( +
+ + + +
+ ) : null, +})); + +vi.mock('./features/voice-assistant/VoiceAssistant', () => ({ + VoiceAssistant: ({ isOpen, onClose }: any) => + isOpen ? ( +
+ +
+ ) : null, +})); + +vi.mock('./components/FlyerCorrectionTool', () => ({ + FlyerCorrectionTool: ({ isOpen, onClose, onDataExtracted }: any) => + isOpen ? ( +
+ + + +
+ ) : null, +})); + // Mock pdfjs-dist to prevent the "DOMMatrix is not defined" error in JSDOM. // This must be done in any test file that imports App.tsx. vi.mock('pdfjs-dist', () => ({ @@ -128,6 +215,7 @@ describe('App Component', () => { mockUseUserData.mockReturnValue({ watchedItems: [], shoppingLists: [], + isLoadingShoppingLists: false, setWatchedItems: vi.fn(), setShoppingLists: vi.fn(), }); diff --git a/src/services/authService.test.ts b/src/services/authService.test.ts index b9a03de9..b54e69d9 100644 --- a/src/services/authService.test.ts +++ b/src/services/authService.test.ts @@ -2,7 +2,6 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import type { UserProfile } from '../types'; import type * as jsonwebtoken from 'jsonwebtoken'; -import { DatabaseError } from './processingErrors'; const { transactionalUserRepoMocks, transactionalAdminRepoMocks } = vi.hoisted(() => { return { @@ -33,6 +32,7 @@ describe('AuthService', () => { let adminRepo: typeof import('./db/index.db').adminRepo; let logger: typeof import('./logger.server').logger; let sendPasswordResetEmail: typeof import('./emailService.server').sendPasswordResetEmail; + let DatabaseError: typeof import('./processingErrors').DatabaseError; let UniqueConstraintError: typeof import('./db/errors.db').UniqueConstraintError; let RepositoryError: typeof import('./db/errors.db').RepositoryError; let withTransaction: typeof import('./db/index.db').withTransaction; @@ -106,6 +106,7 @@ describe('AuthService', () => { const { validatePasswordStrength } = await import('../utils/authUtils'); vi.mocked(validatePasswordStrength).mockReturnValue({ isValid: true, feedback: '' }); sendPasswordResetEmail = (await import('./emailService.server')).sendPasswordResetEmail; + DatabaseError = (await import('./processingErrors')).DatabaseError; UniqueConstraintError = (await import('./db/errors.db')).UniqueConstraintError; RepositoryError = (await import('./db/errors.db')).RepositoryError; }); diff --git a/src/services/db/address.db.test.ts b/src/services/db/address.db.test.ts index f4e4af13..4bf0ce52 100644 --- a/src/services/db/address.db.test.ts +++ b/src/services/db/address.db.test.ts @@ -106,7 +106,13 @@ describe('Address DB Service', () => { 'An identical address already exists.', ); expect(mockLogger.error).toHaveBeenCalledWith( - { err: dbError, address: addressData }, + { + err: dbError, + address: addressData, + code: '23505', + constraint: undefined, + detail: undefined, + }, 'Database error in upsertAddress', ); }); diff --git a/src/services/flyerProcessingService.server.ts b/src/services/flyerProcessingService.server.ts index 6ee92dd7..a21adb96 100644 --- a/src/services/flyerProcessingService.server.ts +++ b/src/services/flyerProcessingService.server.ts @@ -108,25 +108,32 @@ export class FlyerProcessingService { stages[3].status = 'in-progress'; await job.updateProgress({ stages }); - const { flyer } = await db.withTransaction(async (client) => { - // This assumes createFlyerAndItems is refactored to accept a transactional client. - const { flyer: newFlyer } = await createFlyerAndItems(flyerData, itemsForDb, logger, client); + let flyerId: number; + try { + const { flyer } = await db.withTransaction(async (client) => { + // This assumes createFlyerAndItems is refactored to accept a transactional client. + const { flyer: newFlyer } = await createFlyerAndItems(flyerData, itemsForDb, logger, client); - // Instantiate a new AdminRepository with the transactional client to ensure - // the activity log is part of the same transaction. - const transactionalAdminRepo = new AdminRepository(client); - await transactionalAdminRepo.logActivity( - { - action: 'flyer_processed', - displayText: `Processed flyer for ${flyerData.store_name}`, - details: { flyer_id: newFlyer.flyer_id, store_name: flyerData.store_name }, - userId: job.data.userId, - }, - logger, - ); + // Instantiate a new AdminRepository with the transactional client to ensure + // the activity log is part of the same transaction. + const transactionalAdminRepo = new AdminRepository(client); + await transactionalAdminRepo.logActivity( + { + action: 'flyer_processed', + displayText: `Processed flyer for ${flyerData.store_name}`, + details: { flyer_id: newFlyer.flyer_id, store_name: flyerData.store_name }, + userId: job.data.userId, + }, + logger, + ); - return { flyer: newFlyer }; - }); + return { flyer: newFlyer }; + }); + flyerId = flyer.flyer_id; + } catch (error) { + if (error instanceof FlyerProcessingError) throw error; + throw new DatabaseError(error instanceof Error ? error.message : String(error)); + } stages[3].status = 'completed'; await job.updateProgress({ stages }); @@ -134,12 +141,12 @@ export class FlyerProcessingService { // Enqueue a job to clean up the original and any generated files. await this.cleanupQueue.add( 'cleanup-flyer-files', - { flyerId: flyer.flyer_id, paths: allFilePaths }, + { flyerId, paths: allFilePaths }, { removeOnComplete: true }, ); - logger.info(`Successfully processed job and enqueued cleanup for flyer ID: ${flyer.flyer_id}`); + logger.info(`Successfully processed job and enqueued cleanup for flyer ID: ${flyerId}`); - return { flyerId: flyer.flyer_id }; + return { flyerId }; } catch (error) { logger.warn('Job failed. Temporary files will NOT be cleaned up to allow for manual inspection.'); // Add detailed logging of the raw error object