lootsa tests fixes
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 4m37s

This commit is contained in:
2025-12-05 23:01:22 -08:00
parent 06fc94feb1
commit c85de2a999
16 changed files with 77 additions and 50 deletions

View File

@@ -6,6 +6,12 @@ import { MemoryRouter, Outlet } from 'react-router-dom';
import App from './App';
import * as apiClient from './services/apiClient';
// Mock useAuth to allow overriding the user state in tests
const mockUseAuth = vi.fn();
vi.mock('./hooks/useAuth', () => ({
useAuth: () => mockUseAuth(),
}));
// Mock child components to isolate the App component
vi.mock('./features/flyer/FlyerDisplay', () => ({ FlyerDisplay: () => <div data-testid="flyer-display-mock">Flyer Display</div> }));
vi.mock('./features/flyer/ExtractedDataTable', () => ({ ExtractedDataTable: () => <div data-testid="extracted-data-table-mock">Extracted Data Table</div> }));
@@ -51,6 +57,14 @@ const mockedApiClient = apiClient as Mocked<typeof apiClient>;
describe('App Component', () => {
beforeEach(() => {
vi.clearAllMocks();
// Default auth state: loading or guest
mockUseAuth.mockReturnValue({
user: null,
profile: null,
authStatus: 'Determining...',
isLoading: true,
});
// Clear local storage to prevent auth state from leaking between tests.
localStorage.clear();
// Default mocks for API calls
@@ -84,7 +98,7 @@ describe('App Component', () => {
});
it('should render the BulkImporter for an admin user', async () => {
// FIX 4: Update the BulkImporter test case to simulate an admin user
// FIX 4: Update the BulkImporter test case to simulate an admin user via useAuth mock
const mockAdminProfile = {
user_id: 'admin-id',
user: { user_id: 'admin-id', email: 'admin@example.com' },
@@ -92,14 +106,14 @@ describe('App Component', () => {
full_name: 'Admin',
avatar_url: '',
};
// Mock the response to return the admin profile
mockedApiClient.getAuthenticatedUserProfile.mockResolvedValue(new Response(JSON.stringify(mockAdminProfile)));
// mockedApiClient.getAuthenticatedUserProfile.mockResolvedValue(new Response(JSON.stringify(mockAdminProfile)));
// We need to override the mock implementation specifically for this test to return the admin profile
mockedApiClient.getAuthenticatedUserProfile.mockImplementation(() => Promise.resolve(new Response(JSON.stringify(mockAdminProfile))));
// Set the token to trigger the auth check effect
localStorage.setItem('authToken', 'fake-admin-token');
// Force the auth hook to return an authenticated admin user
mockUseAuth.mockReturnValue({
user: mockAdminProfile.user,
profile: mockAdminProfile,
authStatus: 'AUTHENTICATED',
isLoading: false,
});
renderApp();

View File

@@ -33,7 +33,7 @@ The manual-db-reset.yml workflow would be simplified to call these scripts. The
*/
import { Pool, PoolClient } from 'pg';
import fs from 'fs/promises';
import fs from 'node:fs/promises';
import { logger } from '../services/logger.server';
const pool = new Pool({

View File

@@ -396,7 +396,10 @@ describe('ProfileManager Authenticated User Features', () => {
full_name: 'Updated Name',
avatar_url: 'http://example.com/avatar.png',
});
expect(mockOnProfileUpdate).toHaveBeenCalledWith(expect.objectContaining({ full_name: 'Updated Name' }));
// The component calls the callback with the merged profile
expect(mockOnProfileUpdate).toHaveBeenCalled();
expect(mockOnProfileUpdate.mock.calls[0][0]).toMatchObject({ full_name: 'Updated Name' });
// Match any success message containing "Profile" and "updated"
expect(notifySuccess).toHaveBeenCalledWith(expect.stringMatching(/Profile.*updated/));
});

View File

@@ -42,9 +42,9 @@ vi.mock('../services/logger.server', () => ({
},
}));
// 2. Mock fs/promises.
// 2. Mock node:fs/promises.
// We provide both named exports and a default export to support different import styles.
vi.mock('fs/promises', () => {
vi.mock('node:fs/promises', () => {
const mockAccess = vi.fn();
const mockConstants = { W_OK: 1 };
return {
@@ -56,7 +56,7 @@ vi.mock('fs/promises', () => {
},
};
});
import * as fs from 'fs/promises';
import * as fs from 'node:fs/promises';
// Create the Express app
const app = express();

View File

@@ -2,7 +2,7 @@
import { Router, Request, Response, NextFunction } from 'express';
import * as db from '../services/db/index.db';
import { logger } from '../services/logger.server';
import fs from 'fs/promises';
import fs from 'node:fs/promises';
const router = Router();

View File

@@ -3,7 +3,7 @@ import express, { Request, Response } from 'express';
import passport from './passport.routes';
import multer from 'multer';
import path from 'path';
import fs from 'fs/promises';
import fs from 'node:fs/promises';
import * as bcrypt from 'bcrypt';
import zxcvbn from 'zxcvbn';
import * as db from '../services/db/index.db';

View File

@@ -30,19 +30,20 @@ vi.mock('@google/genai', () => ({
GoogleGenAI: MockGoogleGenAIImplementation,
}));
// 3. Mock fs/promises AND node:fs/promises
// Return a factory function to ensure fresh mocks if needed,
// and satisfy both default and named imports.
// 3. Mock all variants of fs/promises to catch any import style
vi.mock('fs/promises', () => ({
default: {
readFile: mockReadFile,
},
default: { readFile: mockReadFile },
readFile: mockReadFile,
}));
vi.mock('node:fs/promises', () => ({
default: { readFile: mockReadFile },
readFile: mockReadFile,
}));
// Also mock standard 'fs' just in case code uses fs.promises
vi.mock('fs', () => ({
default: { promises: { readFile: mockReadFile } },
promises: { readFile: mockReadFile },
}));
// 4. Mock sharp
vi.mock('sharp', () => ({

View File

@@ -6,7 +6,7 @@
*/
import { GoogleGenAI } from '@google/genai';
import fs from 'fs/promises';
import fs from 'node:fs/promises';
import { logger } from './logger.server';
import type { FlyerItem, MasterGroceryItem } from '../types';

View File

@@ -98,7 +98,7 @@ describe('DB Connection Service', () => {
const { getPoolStatus } = await import('./connection.db');
const status = getPoolStatus();
// These values match the default mock implementation in unit-setup/mock-db
// These values match the default mock implementation in tests-setup-unit.ts/mock-db
expect(status).toEqual({
totalCount: 10,
idleCount: 5,

View File

@@ -1,21 +1,31 @@
import { describe, it, expect, vi, beforeEach, beforeAll } from 'vitest';
import { describe, it, expect, vi, beforeEach, beforeAll, type Mock } from 'vitest';
// FIX: Mock the local re-export with explicit spies on a function object
vi.mock('../lib/toast', () => {
// Create separate mocks for the main function and its methods
const mainToastFn = vi.fn();
// Define a type that represents the toast function with attached methods
type MockToast = Mock & {
success: Mock;
error: Mock;
};
// 1. Hoist the spies so they can be referenced inside the mock factory and the test
const { mockToast } = vi.hoisted(() => {
const successFn = vi.fn();
const errorFn = vi.fn();
// Combine them into a single object that is also a function
const mockToast = Object.assign(mainToastFn, { success: successFn, error: errorFn });
const fn = vi.fn();
return {
default: mockToast,
__esModule: true,
};
// Attach properties
Object.assign(fn, { success: successFn, error: errorFn });
// Cast the return so 'mockToast' is typed correctly in the test body
return { mockToast: fn as MockToast };
});
// 2. Mock the module using the hoisted object
vi.mock('../lib/toast', () => ({
default: mockToast,
__esModule: true,
}));
describe('Notification Service', () => {
beforeAll(() => {
// Strategy #6: Verify JSDOM Window and stub missing browser APIs.
@@ -48,15 +58,15 @@ describe('Notification Service', () => {
describe('notifySuccess', () => {
it('should call the injected toaster.success with correct options', async () => {
// Dynamically import the modules AFTER mocks are set up
const { default: toast } = await import('../lib/toast');
// Import the service
const { notifySuccess } = await import('./notificationService');
const message = 'Operation was successful!';
notifySuccess(message);
expect(toast.success).toHaveBeenCalledTimes(1);
expect(toast.success).toHaveBeenCalledWith(
// Verify against the hoisted mock's property
expect(mockToast.success).toHaveBeenCalledTimes(1);
expect(mockToast.success).toHaveBeenCalledWith(
message,
expect.objectContaining({
style: expect.any(Object),
@@ -71,14 +81,13 @@ describe('Notification Service', () => {
describe('notifyError', () => {
it('should call the injected toaster.error with correct options', async () => {
const { default: toast } = await import('../lib/toast');
const { notifyError } = await import('./notificationService');
const message = 'Something went wrong!';
notifyError(message);
expect(toast.error).toHaveBeenCalledTimes(1);
expect(toast.error).toHaveBeenCalledWith(
expect(mockToast.error).toHaveBeenCalledTimes(1);
expect(mockToast.error).toHaveBeenCalledWith(
message,
expect.objectContaining({
style: expect.any(Object),

View File

@@ -2,7 +2,7 @@
import { Queue, Worker, Job } from 'bullmq';
import IORedis from 'ioredis'; // Correctly imported
import path from 'path';
import fs from 'fs/promises';
import fs from 'node:fs/promises';
import { exec } from 'child_process';
import { promisify } from 'util';

View File

@@ -2,7 +2,7 @@
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import * as apiClient from '../../services/apiClient';
import * as aiApiClient from '../../services/aiApiClient';
import fs from 'fs/promises';
import fs from 'node:fs/promises';
import path from 'path';
/**

View File

@@ -1,6 +1,6 @@
// src/tests/integration/flyer-processing.integration.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import fs from 'fs/promises';
import fs from 'node:fs/promises';
import path from 'path';
import * as apiClient from '../../services/apiClient';
import * as aiApiClient from '../../services/aiApiClient';

View File

@@ -1,6 +1,6 @@
// src/tests/setup/global-setup.ts
import { Pool } from 'pg';
import fs from 'fs/promises';
import fs from 'node:fs/promises';
import path from 'path';
import { execSync } from 'child_process';

View File

@@ -57,7 +57,7 @@ afterEach(cleanup);
// by the factory. However, we define the Constructor function INSIDE the factory
// or via a standard function expression to ensure it remains constructible.
const { mockPoolInstance } = vi.hoisted(() => {
console.log('[DEBUG] unit-setup.ts: Initializing hoisted mock variables');
console.log('[DEBUG] tests-setup-unit.ts: Initializing hoisted mock variables');
const mockQuery = vi.fn().mockResolvedValue({ rows: [], rowCount: 0 });
const mockRelease = vi.fn();
const mockConnect = vi.fn().mockResolvedValue({
@@ -87,12 +87,12 @@ export { mockPoolInstance };
* We define the MockPool function INSIDE the factory to ensure it remains a standard function.
*/
vi.mock('pg', () => {
console.log('[DEBUG] unit-setup.ts: vi.mock("pg") factory executing');
console.log('[DEBUG] tests-setup-unit.ts: vi.mock("pg") factory executing');
// FIX: Use a standard function expression for the Mock Pool Constructor.
// This ensures `new Pool()` works at runtime.
const MockPool = function() {
console.log('[DEBUG] unit-setup.ts: MockPool constructor called via "new"!');
console.log('[DEBUG] tests-setup-unit.ts: MockPool constructor called via "new"!');
return mockPoolInstance;
};

View File

@@ -1,7 +1,7 @@
// src/utils/imageProcessor.ts
import sharp from 'sharp';
import path from 'path';
import fs from 'fs/promises';
import fs from 'node:fs/promises';
import { logger } from '../services/logger.server';
import { sanitizeFilename } from './stringUtils';