splitting unit + integration testing apart attempt #1
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 3m40s
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 3m40s
This commit is contained in:
@@ -33,6 +33,14 @@ vi.mock('./pages/ResetPasswordPage', () => ({ ResetPasswordPage: () => <div data
|
|||||||
vi.mock('./pages/admin/components/AnonymousUserBanner', () => ({ AnonymousUserBanner: () => <div data-testid="anonymous-user-banner-mock">Anonymous User Banner</div> }));
|
vi.mock('./pages/admin/components/AnonymousUserBanner', () => ({ AnonymousUserBanner: () => <div data-testid="anonymous-user-banner-mock">Anonymous User Banner</div> }));
|
||||||
vi.mock('./pages/VoiceLabPage', () => ({ VoiceLabPage: () => <div data-testid="voice-lab-page-mock">Voice Lab Page</div> }));
|
vi.mock('./pages/VoiceLabPage', () => ({ VoiceLabPage: () => <div data-testid="voice-lab-page-mock">Voice Lab Page</div> }));
|
||||||
vi.mock('./components/WhatsNewModal', () => ({ WhatsNewModal: () => <div data-testid="whats-new-modal-mock">What's New Modal</div> }));
|
vi.mock('./components/WhatsNewModal', () => ({ WhatsNewModal: () => <div data-testid="whats-new-modal-mock">What's New Modal</div> }));
|
||||||
|
// 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', () => ({
|
||||||
|
GlobalWorkerOptions: { workerSrc: '' },
|
||||||
|
getDocument: vi.fn(() => ({
|
||||||
|
promise: Promise.resolve({ getPage: vi.fn() }),
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
// By casting the apiClient to `Mocked<typeof apiClient>`, we get type-safe access
|
// By casting the apiClient to `Mocked<typeof apiClient>`, we get type-safe access
|
||||||
// to Vitest's mock functions like `mockResolvedValue`.
|
// to Vitest's mock functions like `mockResolvedValue`.
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ describe('FlyerCorrectionTool', () => {
|
|||||||
|
|
||||||
it('should call onClose when the close button is clicked', () => {
|
it('should call onClose when the close button is clicked', () => {
|
||||||
render(<FlyerCorrectionTool {...defaultProps} />);
|
render(<FlyerCorrectionTool {...defaultProps} />);
|
||||||
fireEvent.click(screen.getByRole('button', { name: /close/i }));
|
fireEvent.click(screen.getByLabelText('Close correction tool'));
|
||||||
expect(defaultProps.onClose).toHaveBeenCalledTimes(1);
|
expect(defaultProps.onClose).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ vi.mock('./passport', () => ({
|
|||||||
const app = express();
|
const app = express();
|
||||||
app.use(express.json({ strict: false }));
|
app.use(express.json({ strict: false }));
|
||||||
app.use(cookieParser()); // Add cookie-parser middleware to populate req.cookies
|
app.use(cookieParser()); // Add cookie-parser middleware to populate req.cookies
|
||||||
app.use('/api/auth', authRouter);
|
app.use('/api/auth', authRouter); // This was missing
|
||||||
|
|
||||||
describe('Auth Routes (/api/auth)', () => {
|
describe('Auth Routes (/api/auth)', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
// src/routes/system.test.ts
|
// src/routes/system.test.ts
|
||||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
|
||||||
import supertest from 'supertest';
|
import supertest from 'supertest';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import type { ExecException } from 'child_process';
|
import type { ExecException, ChildProcess } from 'child_process';
|
||||||
import systemRouter from './system';
|
import systemRouter from './system';
|
||||||
|
|
||||||
// Define a mock function at the module level. This will be our controlled version of `exec`.
|
|
||||||
const mockedExec = vi.fn();
|
|
||||||
|
|
||||||
// Mock the child_process module. We explicitly define its exports.
|
// Mock the child_process module. We explicitly define its exports.
|
||||||
vi.mock('child_process', () => ({
|
// The mock factory MUST NOT reference variables from outside its scope due to hoisting.
|
||||||
exec: mockedExec,
|
// Instead, we import the mocked function after the mock is declared.
|
||||||
}));
|
vi.mock('child_process', () => ({ exec: vi.fn() }));
|
||||||
|
|
||||||
|
// Now we can safely import the mocked function and control it.
|
||||||
|
import { exec } from 'child_process';
|
||||||
|
|
||||||
vi.mock('../services/logger.server', () => ({
|
vi.mock('../services/logger.server', () => ({
|
||||||
logger: {
|
logger: {
|
||||||
@@ -29,6 +29,7 @@ app.use('/api/system', systemRouter);
|
|||||||
|
|
||||||
describe('System Routes (/api/system)', () => {
|
describe('System Routes (/api/system)', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
// We cast here to get type-safe access to mock functions like .mockImplementation
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -42,12 +43,11 @@ describe('System Routes (/api/system)', () => {
|
|||||||
└───────────┴───────────┘
|
└───────────┴───────────┘
|
||||||
`;
|
`;
|
||||||
// The `exec` callback receives (error, stdout, stderr). For success, error is null.
|
// The `exec` callback receives (error, stdout, stderr). For success, error is null.
|
||||||
vi.mocked(mockedExec).mockImplementation((
|
// We must match the overloaded signature of `exec`. The second argument can be options or the callback.
|
||||||
command: string,
|
vi.mocked(exec).mockImplementation((command, options, callback) => {
|
||||||
callback: (error: ExecException | null, stdout: string, stderr: string) => void
|
const cb = typeof options === 'function' ? options : callback;
|
||||||
) => {
|
cb?.(null, pm2OnlineOutput, '');
|
||||||
callback(null, pm2OnlineOutput, '');
|
return {} as ChildProcess; // Return a dummy child process object
|
||||||
return {}; // Return a dummy child process object
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
@@ -63,12 +63,10 @@ describe('System Routes (/api/system)', () => {
|
|||||||
const pm2StoppedOutput = `
|
const pm2StoppedOutput = `
|
||||||
│ status │ stopped │
|
│ status │ stopped │
|
||||||
`;
|
`;
|
||||||
vi.mocked(mockedExec).mockImplementation((
|
vi.mocked(exec).mockImplementation((command, options, callback) => {
|
||||||
command: string,
|
const cb = typeof options === 'function' ? options : callback;
|
||||||
callback: (error: ExecException | null, stdout: string, stderr: string) => void
|
cb?.(null, pm2StoppedOutput, '');
|
||||||
) => {
|
return {} as ChildProcess;
|
||||||
callback(null, pm2StoppedOutput, '');
|
|
||||||
return {};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
@@ -82,12 +80,10 @@ describe('System Routes (/api/system)', () => {
|
|||||||
it('should return success: false when pm2 process does not exist', async () => {
|
it('should return success: false when pm2 process does not exist', async () => {
|
||||||
// Arrange: Simulate the error and stdout when a process is not found.
|
// Arrange: Simulate the error and stdout when a process is not found.
|
||||||
const processNotFoundOutput = "[PM2][ERROR] Process or Namespace flyer-crawler-api doesn't exist";
|
const processNotFoundOutput = "[PM2][ERROR] Process or Namespace flyer-crawler-api doesn't exist";
|
||||||
vi.mocked(mockedExec).mockImplementation((
|
vi.mocked(exec).mockImplementation((command, options, callback) => {
|
||||||
command: string,
|
const cb = typeof options === 'function' ? options : callback;
|
||||||
callback: (error: ExecException | null, stdout: string, stderr: string) => void
|
cb?.(new Error('Command failed') as ExecException, processNotFoundOutput, '');
|
||||||
) => {
|
return {} as ChildProcess;
|
||||||
callback(new Error('Command failed') as ExecException, processNotFoundOutput, '');
|
|
||||||
return {};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
@@ -100,12 +96,10 @@ describe('System Routes (/api/system)', () => {
|
|||||||
|
|
||||||
it('should return 500 on a generic exec error', async () => {
|
it('should return 500 on a generic exec error', async () => {
|
||||||
// Arrange: Simulate a generic failure of the `exec` command.
|
// Arrange: Simulate a generic failure of the `exec` command.
|
||||||
vi.mocked(mockedExec).mockImplementation((
|
vi.mocked(exec).mockImplementation((command, options, callback) => {
|
||||||
command: string,
|
const cb = typeof options === 'function' ? options : callback;
|
||||||
callback: (error: ExecException | null, stdout: string, stderr: string) => void
|
cb?.(new Error('Generic exec error') as ExecException, '', 'Some stderr output');
|
||||||
) => {
|
return {} as ChildProcess;
|
||||||
callback(new Error('Generic exec error') as ExecException, '', 'Some stderr output');
|
|
||||||
return {};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
@@ -120,12 +114,10 @@ describe('System Routes (/api/system)', () => {
|
|||||||
// Arrange: Simulate a scenario where the command writes to stderr but doesn't
|
// Arrange: Simulate a scenario where the command writes to stderr but doesn't
|
||||||
// produce a formal error object for the callback's first argument.
|
// produce a formal error object for the callback's first argument.
|
||||||
const stderrMessage = 'A non-fatal warning or configuration issue.';
|
const stderrMessage = 'A non-fatal warning or configuration issue.';
|
||||||
vi.mocked(mockedExec).mockImplementation((
|
vi.mocked(exec).mockImplementation((command, options, callback) => {
|
||||||
command: string,
|
const cb = typeof options === 'function' ? options : callback;
|
||||||
callback: (error: ExecException | null, stdout: string, stderr: string) => void
|
cb?.(null, '', stderrMessage);
|
||||||
) => {
|
return {} as ChildProcess;
|
||||||
callback(null, '', stderrMessage);
|
|
||||||
return {};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ app.use(express.json());
|
|||||||
app.use('/api/users', express.json({ strict: false }), userRouter);
|
app.use('/api/users', express.json({ strict: false }), userRouter);
|
||||||
|
|
||||||
|
|
||||||
describe.only('User Routes (/api/users)', () => {
|
describe('User Routes (/api/users)', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
// Default to unauthorized for all tests unless overridden in a `describe` block.
|
// Default to unauthorized for all tests unless overridden in a `describe` block.
|
||||||
|
|||||||
@@ -56,29 +56,11 @@ afterEach(cleanup);
|
|||||||
* This is the central point for mocking the database connection pool for unit tests.
|
* This is the central point for mocking the database connection pool for unit tests.
|
||||||
*/
|
*/
|
||||||
vi.mock('pg', () => {
|
vi.mock('pg', () => {
|
||||||
// --- DIAGNOSTIC LOGGING #1: "Is the Mock Even Loading?" ---
|
|
||||||
// This log will appear once at the start of the test run to prove that this global mock is being used.
|
|
||||||
console.log('[vitest-mock-pg] Global `pg` mock from unit-setup.ts is being initialized.');
|
|
||||||
|
|
||||||
// These are the mock functions that will replace the real 'pg' methods.
|
|
||||||
const mockQuery = vi.fn();
|
const mockQuery = vi.fn();
|
||||||
const mockRelease = vi.fn();
|
const mockRelease = vi.fn();
|
||||||
const mockConnect = vi.fn().mockResolvedValue({
|
const mockConnect = vi.fn().mockResolvedValue({ query: mockQuery, release: mockRelease });
|
||||||
query: mockQuery,
|
const mockPool = vi.fn(() => ({ query: mockQuery, connect: mockConnect, totalCount: 10, idleCount: 5, waitingCount: 0, end: vi.fn() }));
|
||||||
release: mockRelease,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Mock the Pool class constructor.
|
|
||||||
const mockPool = vi.fn(() => ({
|
|
||||||
query: mockQuery,
|
|
||||||
connect: mockConnect,
|
|
||||||
totalCount: 10,
|
|
||||||
idleCount: 5,
|
|
||||||
waitingCount: 0,
|
|
||||||
end: vi.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
// The object returned here becomes the exports of the 'pg' module for all unit tests.
|
|
||||||
return {
|
return {
|
||||||
__esModule: true, // This is important for ES module interoperability.
|
__esModule: true, // This is important for ES module interoperability.
|
||||||
Pool: mockPool,
|
Pool: mockPool,
|
||||||
@@ -88,6 +70,27 @@ vi.mock('pg', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mocks the Google Generative AI package. This prevents real API calls during tests.
|
||||||
|
*/
|
||||||
|
vi.mock('@google/generative-ai', () => {
|
||||||
|
const mockGenerativeModel = {
|
||||||
|
generateContent: vi.fn().mockResolvedValue({
|
||||||
|
response: {
|
||||||
|
text: () => 'Mocked AI response',
|
||||||
|
candidates: [{ content: { parts: [{ text: 'Mocked AI response' }] } }],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
GoogleGenerativeAI: vi.fn(() => ({
|
||||||
|
getGenerativeModel: () => mockGenerativeModel,
|
||||||
|
})),
|
||||||
|
HarmCategory: {},
|
||||||
|
HarmBlockThreshold: {},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mocks the entire apiClient module.
|
* Mocks the entire apiClient module.
|
||||||
* This ensures that all test files that import from apiClient will get this mocked version.
|
* This ensures that all test files that import from apiClient will get this mocked version.
|
||||||
@@ -160,4 +163,18 @@ vi.mock('../../services/logger', () => ({
|
|||||||
vi.mock('../../services/notificationService', () => ({
|
vi.mock('../../services/notificationService', () => ({
|
||||||
notifySuccess: vi.fn(),
|
notifySuccess: vi.fn(),
|
||||||
notifyError: vi.fn(),
|
notifyError: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mocks the `react-hot-toast` library to prevent actual toasts from rendering
|
||||||
|
* during tests. This also provides mock controls for functions like `toast.loading`.
|
||||||
|
*/
|
||||||
|
vi.mock('react-hot-toast', () => ({
|
||||||
|
default: {
|
||||||
|
success: vi.fn(),
|
||||||
|
error: vi.fn(),
|
||||||
|
loading: vi.fn(() => 'toast-id-123'), // Return a dummy ID
|
||||||
|
dismiss: vi.fn(),
|
||||||
|
},
|
||||||
|
Toaster: () => null, // The component renders nothing in tests
|
||||||
}));
|
}));
|
||||||
Reference in New Issue
Block a user