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

This commit is contained in:
2025-12-05 16:00:04 -08:00
parent 24ec00113f
commit 88b3b0fe95
12 changed files with 64 additions and 54 deletions

View File

@@ -54,10 +54,12 @@ describe('App Component', () => {
// Clear local storage to prevent auth state from leaking between tests.
localStorage.clear();
// Default mocks for API calls
mockedApiClient.fetchFlyers.mockResolvedValue(new Response(JSON.stringify([])));
mockedApiClient.fetchMasterItems.mockResolvedValue(new Response(JSON.stringify([])));
mockedApiClient.fetchWatchedItems.mockResolvedValue(new Response(JSON.stringify([])));
mockedApiClient.fetchShoppingLists.mockResolvedValue(new Response(JSON.stringify([])));
// Use mockImplementation to create a new Response object for each call,
// preventing "Body has already been read" errors.
mockedApiClient.fetchFlyers.mockImplementation(() => Promise.resolve(new Response(JSON.stringify([]))));
mockedApiClient.fetchMasterItems.mockImplementation(() => Promise.resolve(new Response(JSON.stringify([]))));
mockedApiClient.fetchWatchedItems.mockImplementation(() => Promise.resolve(new Response(JSON.stringify([]))));
mockedApiClient.fetchShoppingLists.mockImplementation(() => Promise.resolve(new Response(JSON.stringify([]))));
mockedApiClient.getAuthenticatedUserProfile.mockRejectedValue(new Error('Not authenticated'));
});

View File

@@ -269,8 +269,8 @@ describe('SystemCheck', () => {
// Instead of test-ids, we check for the result: the icon's color class.
// This is more robust as it doesn't depend on the icon component's internal props.
const passIcons = container.querySelectorAll('svg.text-green-500'); // Redis check is now passing
// 7 checks should pass (API key, backend, PM2, DB Pool, Redis, Seed, Storage)
expect(passIcons.length).toBe(7);
// All 8 checks should pass in this scenario, but one is mocked to fail.
expect(passIcons.length).toBe(7); // This was correct, the error was likely a red herring from other failing tests.
// Check for the fail icon's color class
const failIcon = container.querySelector('svg.text-red-500');

View File

@@ -1,5 +1,5 @@
// src/routes/admin.test.ts
import { describe, it, expect, vi, beforeEach, afterAll, type Mocked, type Mock } from 'vitest';
import { describe, it, expect, vi, beforeEach, afterAll, type Mocked } from 'vitest';
import supertest from 'supertest';
import express, { Request, Response, NextFunction } from 'express';
import path from 'path';
@@ -7,7 +7,7 @@ import fs from 'node:fs/promises';
import adminRouter from './admin.routes'; // Correctly imported
import { createMockUserProfile, createMockSuggestedCorrection, createMockBrand, createMockRecipe, createMockRecipeComment, createMockActivityLogItem } from '../tests/utils/mockFactories';
import { Job } from 'bullmq';
import { UserProfile, SuggestedCorrection, Brand, Recipe, RecipeComment, User, UnmatchedFlyerItem } from '../types';
import { SuggestedCorrection, Brand, User, UnmatchedFlyerItem } from '../types';
// Mock the specific DB modules used by the admin router.
// This is more robust than mocking the barrel file ('../services/db').
@@ -55,16 +55,18 @@ vi.mock('../services/queueService.server', () => ({
// The adapters expect real BullMQ queues, which we don't have in tests.
vi.mock('@bull-board/api', () => ({
// Mock createBullBoard to do nothing.
createBullBoard: vi.fn(),
// Mock the adapter constructor.
BullMQAdapter: vi.fn(),
createBullBoard: vi.fn(() => ({ router: (req: Request, res: Response, next: NextFunction) => next() })),
// Mock the adapter as a class since the code uses `new BullMQAdapter()`.
BullMQAdapter: class MockBullMQAdapter {},
}));
vi.mock('@bull-board/express', () => ({
// Mock the ExpressAdapter to return a dummy router.
ExpressAdapter: vi.fn(() => ({
setBasePath: vi.fn(),
getRouter: () => (req: Request, res: Response, next: NextFunction) => next(),
})),
// Mock the ExpressAdapter as a class since the code uses `new ExpressAdapter()`.
ExpressAdapter: class MockExpressAdapter {
setBasePath() {}
getRouter() {
return (req: Request, res: Response, next: NextFunction) => next();
}
},
}));
// Import the mocked modules to control them in tests.

View File

@@ -14,7 +14,6 @@ vi.mock('../services/aiService.server');
// Mock the specific DB modules used by the AI router.
vi.mock('../services/db/flyer.db');
vi.mock('../services/db/admin.db');
const mockedDb = { ...flyerDb, ...adminDb } as Mocked<typeof flyerDb & typeof adminDb>;
// Mock the logger to keep test output clean
vi.mock('../services/logger.server', () => ({

View File

@@ -13,7 +13,6 @@ import { UserProfile } from '../types';
// This decouples the route tests from the SQL implementation details.
vi.mock('../services/db/user.db');
vi.mock('../services/db/admin.db');
const mockedDb = { ...userDb, ...adminDb } as Mocked<typeof userDb & typeof adminDb>;
// Mock the logger to keep test output clean
vi.mock('../services/logger.server', () => ({
logger: {

View File

@@ -5,11 +5,16 @@ import express, { Request, Response, NextFunction } from 'express';
import budgetRouter from './budget.routes';
import * as budgetDb from '../services/db/budget.db';
import { createMockUserProfile, createMockBudget, createMockSpendingByCategory } from '../tests/utils/mockFactories';
import { Budget, SpendingByCategory } from '../types';
// 1. Mock the Service Layer directly.
// This decouples the route tests from the database logic.
vi.mock('../services/db/budget.db');
vi.mock('../services/db/budget.db', () => ({
getBudgetsForUser: vi.fn(),
createBudget: vi.fn(),
updateBudget: vi.fn(),
deleteBudget: vi.fn(),
getSpendingByCategory: vi.fn(),
}));
// Mock the logger to keep test output clean
vi.mock('../services/logger.server', () => ({

View File

@@ -5,7 +5,6 @@ import express, { Request, Response, NextFunction } from 'express';
import gamificationRouter from './gamification.routes';
import * as gamificationDb from '../services/db/gamification.db';
import { createMockUserProfile, createMockAchievement, createMockUserAchievement } from '../tests/utils/mockFactories';
import { Achievement, UserAchievement } from '../types';
// Mock the entire db service
vi.mock('../services/db/gamification.db');

View File

@@ -29,9 +29,12 @@ vi.mock('passport-jwt', () => ({
}));
import * as db from '../services/db/index.db';
import { UserProfile } from '../types';
// Mock dependencies before importing the passport configuration
vi.mock('../services/db');
vi.mock('../services/db/index.db', () => ({
findUserProfileById: vi.fn(),
}));
const mockedDb = db as Mocked<typeof db>;
vi.mock('../services/logger.server', () => ({
@@ -65,12 +68,8 @@ describe('Passport Configuration', () => {
describe('JwtStrategy (Isolated Callback Logic)', () => {
it('should call done(null, userProfile) on successful authentication', async () => {
// Arrange
const jwtPayload = { user_id: 'user-123' };
const mockProfile: Profile = {
user_id: 'user-123',
role: 'user',
points: 100, // Add missing required property
};
const jwtPayload = { user_id: 'user-123' };
const mockProfile = { user_id: 'user-123', role: 'user', points: 100, user: { user_id: 'user-123', email: 'test@test.com' } } as UserProfile;
vi.mocked(mockedDb.findUserProfileById).mockResolvedValue(mockProfile);
const done = vi.fn();

View File

@@ -8,7 +8,6 @@ import * as flyerDb from '../services/db/flyer.db';
import * as recipeDb from '../services/db/recipe.db';
import * as adminDb from '../services/db/admin.db';
import { createMockFlyer, createMockFlyerItem, createMockMasterGroceryItem, createMockRecipe } from '../tests/utils/mockFactories';
import { Flyer, Recipe } from '../types';
// 1. Mock the Service Layer directly.
// This decouples the route tests from the SQL implementation details.

View File

@@ -8,13 +8,17 @@ import systemRouter from './system.routes';
// Define a type for the exec callback to avoid using `any`.
type ExecCallback = (error: ExecException | null, stdout: string, stderr: string) => void;
// Mock the 'child_process' module.
// Mock the 'child_process' module to control the behavior of `exec`.
vi.mock('child_process', () => {
// The mock for `exec` needs to accept the command and a callback,
// and then it must *call* that callback to prevent the test from hanging.
const execMock = vi.fn((command: string, callback: ExecCallback) => {
// Provide a default success behavior. Individual tests can override this with .mockImplementation().
callback(null, 'PM2 is online', '');
return {} as ChildProcess; // Return a dummy ChildProcess object.
});
return {
// Mock the named export 'exec'.
exec: vi.fn(),
// Also mock the default export if it's used elsewhere.
default: { exec: vi.fn() },
exec: execMock,
};
});

View File

@@ -8,7 +8,7 @@ import * as userDb from '../services/db/user.db';
import * as personalizationDb from '../services/db/personalization.db';
import * as shoppingDb from '../services/db/shopping.db';
import { createMockUserProfile, createMockMasterGroceryItem, createMockShoppingList, createMockShoppingListItem } from '../tests/utils/mockFactories';
import { UserProfile, Appliance } from '../types';
import { Appliance } from '../types';
// 1. Mock the Service Layer directly.
vi.mock('../services/db/user.db', () => ({

View File

@@ -2,22 +2,24 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
// Mock the nodemailer library BEFORE importing the email service.
const mockSendMail = vi.fn().mockImplementation((mailOptions) => {
console.log('[TEST DEBUG] mockSendMail called with:', mailOptions?.to);
return Promise.resolve({
messageId: 'default-id',
// Fix: Provide a buffer for the message to prevent "cannot read properties of undefined (reading toString)" in service logger
message: Buffer.from('mock-email-content'),
});
});
// Use vi.hoisted to define mocks that need to be available inside vi.mock factories.
// This prevents "Cannot access before initialization" errors.
const mocks = vi.hoisted(() => ({
sendMail: vi.fn().mockImplementation((mailOptions) => {
console.log('[TEST DEBUG] mockSendMail called with:', mailOptions?.to);
return Promise.resolve({
messageId: 'default-id',
message: Buffer.from('mock-email-content'),
});
}),
}));
vi.mock('nodemailer', () => {
return {
// Mock the default export which is what the service uses
default: {
// The createTransport function returns an object with a sendMail method
createTransport: vi.fn(() => ({ sendMail: mockSendMail })),
createTransport: vi.fn(() => ({ sendMail: mocks.sendMail })),
},
};
});
@@ -39,7 +41,7 @@ describe('Email Service (Server)', () => {
console.log('[TEST SETUP] Setting up Email Service mocks');
vi.clearAllMocks();
// Reset to default successful implementation
mockSendMail.mockImplementation((mailOptions) => {
mocks.sendMail.mockImplementation((mailOptions) => {
console.log('[TEST DEBUG] mockSendMail (default) called with:', mailOptions?.to);
return Promise.resolve({
messageId: 'default-mock-id',
@@ -55,8 +57,8 @@ describe('Email Service (Server)', () => {
await sendPasswordResetEmail(to, resetLink);
expect(mockSendMail).toHaveBeenCalledTimes(1);
const mailOptions = mockSendMail.mock.calls[0][0];
expect(mocks.sendMail).toHaveBeenCalledTimes(1);
const mailOptions = mocks.sendMail.mock.calls[0][0];
expect(mailOptions.to).toBe(to);
expect(mailOptions.subject).toBe('Your Password Reset Request');
@@ -70,12 +72,12 @@ describe('Email Service (Server)', () => {
const to = 'new.user@example.com';
const name = 'Jane Doe';
mockSendMail.mockResolvedValue({ messageId: 'test-id', message: Buffer.from('content') });
mocks.sendMail.mockResolvedValue({ messageId: 'test-id', message: Buffer.from('content') });
await sendWelcomeEmail(to, name);
expect(mockSendMail).toHaveBeenCalledTimes(1);
const mailOptions = mockSendMail.mock.calls[0][0];
expect(mocks.sendMail).toHaveBeenCalledTimes(1);
const mailOptions = mocks.sendMail.mock.calls[0][0];
expect(mailOptions.to).toBe(to);
expect(mailOptions.subject).toBe('Welcome to Flyer Crawler!');
@@ -86,12 +88,12 @@ describe('Email Service (Server)', () => {
it('should send a generic welcome email when a name is not provided', async () => {
const to = 'another.user@example.com';
mockSendMail.mockResolvedValue({ messageId: 'test-id', message: Buffer.from('content') });
mocks.sendMail.mockResolvedValue({ messageId: 'test-id', message: Buffer.from('content') });
await sendWelcomeEmail(to, null);
expect(mockSendMail).toHaveBeenCalledTimes(1);
const mailOptions = mockSendMail.mock.calls[0][0];
expect(mocks.sendMail).toHaveBeenCalledTimes(1);
const mailOptions = mocks.sendMail.mock.calls[0][0];
expect(mailOptions.text).toContain('Hello there,');
expect(mailOptions.html).toContain('<p>Hello there,</p>');