splitting unit + integration testing apart attempt #1
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 3m45s

This commit is contained in:
2025-12-01 14:09:56 -08:00
parent ec7f10c297
commit 7a6fe5fe75
5 changed files with 48 additions and 29 deletions

View File

@@ -43,7 +43,8 @@ vi.mock('../logger', () => ({
describe('Admin DB Service', () => {
beforeEach(() => {
console.log('[admin.test.ts] Setup: resetting mocks');
// FIX: Reset mocks
mockQuery.mockReset();
vi.clearAllMocks();
});
@@ -186,14 +187,9 @@ describe('Admin DB Service', () => {
mockQuery.mockResolvedValue({ rows: [] });
await incrementFailedLoginAttempts('user-123');
// DEBUG LOG: Inspect what arguments were actually passed
if (mockQuery.mock.calls.length > 0) {
console.log('[admin.test.ts] incrementFailedLoginAttempts call args:', mockQuery.mock.calls[0]);
}
// Use regex to be insensitive to whitespace/newlines
// Fix: Use regex to match query with variable whitespace
expect(getPool().query).toHaveBeenCalledWith(
expect.stringMatching(/UPDATE\s+public\.users\s+SET\s+failed_login_attempts/),
expect.stringMatching(/UPDATE\s+public\.users\s+SET\s+failed_login_attempts\s*=\s*failed_login_attempts\s*\+\s*1/),
['user-123']
);
});

View File

@@ -26,8 +26,9 @@ vi.mock('../logger', () => ({
describe('Budget DB Service', () => {
beforeEach(() => {
// Configure the globally mocked Pool to use our test-local mock functions.
// FIX: Use mockImplementation with a standard function to support 'new Pool()'
// FIX: Reset the mockQuery to ensure no cross-test contamination
mockQuery.mockReset();
vi.mocked(Pool).mockImplementation(function() {
console.log('[DEBUG] budget.test.ts: Local Pool mock instantiated via "new"');
return {
@@ -60,19 +61,23 @@ describe('Budget DB Service', () => {
it('should execute an INSERT query and return the new budget', async () => {
const budgetData = { name: 'Groceries', amount_cents: 50000, period: 'monthly' as const, start_date: '2024-01-01' };
const mockCreatedBudget: Budget = { budget_id: 1, user_id: 'user-123', ...budgetData };
mockQuery.mockResolvedValueOnce({ rows: [mockCreatedBudget] }).mockResolvedValueOnce({ rows: [] });
// FIX: Add mock for 'BEGIN' call
mockQuery
.mockResolvedValueOnce({ rows: [] }) // BEGIN
.mockResolvedValueOnce({ rows: [mockCreatedBudget] }) // INSERT...RETURNING
.mockResolvedValueOnce({ rows: [] }) // award_achievement
.mockResolvedValueOnce({ rows: [] }); // COMMIT
const result = await createBudget('user-123', budgetData);
expect(mockConnect).toHaveBeenCalled();
expect(mockQuery).toHaveBeenCalledWith('BEGIN');
expect(mockQuery).toHaveBeenCalledWith(
'INSERT INTO public.budgets (user_id, name, amount_cents, period, start_date) VALUES ($1, $2, $3, $4, $5) RETURNING *',
expect.stringContaining('INSERT INTO public.budgets'),
['user-123', budgetData.name, budgetData.amount_cents, budgetData.period, budgetData.start_date]
);
expect(mockQuery).toHaveBeenCalledWith("SELECT public.award_achievement($1, 'First Budget Created')", ['user-123']);
expect(mockQuery).toHaveBeenCalledWith('COMMIT');
expect(mockRelease).toHaveBeenCalled();
expect(result).toEqual(mockCreatedBudget);
});
});

View File

@@ -26,7 +26,9 @@ vi.mock('../logger', () => ({
describe('Notification DB Service', () => {
beforeEach(() => {
// FIX: Use mockImplementation with a standard function to support 'new Pool()'
// FIX: Reset mockQuery
mockQuery.mockReset();
vi.mocked(Pool).mockImplementation(function() {
console.log('[DEBUG] notification.test.ts: Local Pool mock instantiated via "new"');
return {
@@ -45,17 +47,13 @@ describe('Notification DB Service', () => {
describe('getNotificationsForUser', () => {
it('should execute the correct query with limit and offset and return notifications', async () => {
// Arrange
const mockNotifications: Notification[] = [
{ notification_id: 1, user_id: 'user-123', content: 'Test notification 1', is_read: false, created_at: new Date().toISOString() },
{ notification_id: 2, user_id: 'user-123', content: 'Test notification 2', is_read: true, created_at: new Date().toISOString() },
{ notification_id: 1, user_id: 'user-123', content: 'Test 1', is_read: false, created_at: '' },
];
mockQuery.mockResolvedValue({ rows: mockNotifications });
// Act
const result = await getNotificationsForUser('user-123', 10, 5);
// Assert
expect(mockQuery).toHaveBeenCalledWith(
expect.stringContaining('SELECT * FROM public.notifications'),
['user-123', 10, 5]

View File

@@ -37,7 +37,7 @@ vi.mock('../logger', () => ({
describe('Personalization DB Service', () => {
beforeEach(() => {
// FIX: Use mockImplementation with a standard function to support 'new Pool()'
mockQuery.mockReset();
vi.mocked(Pool).mockImplementation(function() {
console.log('[DEBUG] personalization.test.ts: Local Pool mock instantiated via "new"');
return {
@@ -156,11 +156,19 @@ describe('Personalization DB Service', () => {
describe('setUserDietaryRestrictions', () => {
it('should execute a transaction to set restrictions', async () => {
mockQuery.mockResolvedValue({ rows: [] });
await setUserDietaryRestrictions('user-123', [1, 2]);
expect(mockConnect).toHaveBeenCalled();
expect(mockQuery).toHaveBeenCalledWith('BEGIN');
expect(mockQuery).toHaveBeenCalledWith('DELETE FROM public.user_dietary_restrictions WHERE user_id = $1', ['user-123']);
expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining('INSERT INTO public.user_dietary_restrictions'));
// FIX: Make assertion robust for array parameters
expect(mockQuery).toHaveBeenCalledWith(
expect.stringContaining('INSERT INTO public.user_dietary_restrictions'),
['user-123', [1, 2]]
);
expect(mockQuery).toHaveBeenCalledWith('COMMIT');
});
});

View File

@@ -38,9 +38,9 @@ vi.mock('./personalization', () => ({
describe('User DB Service', () => {
beforeEach(() => {
// --- FIX IS HERE ---
// We must use `mockImplementation` with a standard function (not an arrow function).
// `mockReturnValue` uses an arrow function internally, which throws "not a constructor" when `new Pool()` is called.
// FIX: Reset mockQuery
mockQuery.mockReset();
vi.mocked(Pool).mockImplementation(function() {
console.log('[DEBUG] user.test.ts: Test-specific Pool mock constructor called via "new"');
return {
@@ -76,15 +76,16 @@ describe('User DB Service', () => {
const mockUser = { user_id: 'new-user-id', email: 'new@example.com' };
// Mock the query results for each step in the transaction
mockQuery
.mockResolvedValueOnce({ rows: [] }) // BEGIN for transaction
.mockResolvedValueOnce({ rows: [mockUser] }) // INSERT...RETURNING for user
.mockResolvedValueOnce({ rows: [{ ...mockUser, role: 'user' }] }) // SELECT for profile
.mockResolvedValueOnce({ rows: [] }) // BEGIN
.mockResolvedValueOnce({ rows: [mockUser] }) // INSERT user RETURNING *
.mockResolvedValueOnce({ rows: [{ ...mockUser, role: 'user' }] }) // INSERT profile RETURNING *
.mockResolvedValueOnce({ rows: [] }); // COMMIT
const result = await createUser('new@example.com', 'hashedpass', { full_name: 'New User' });
expect(mockConnect).toHaveBeenCalled();
expect(mockRelease).toHaveBeenCalled();
expect(mockQuery).toHaveBeenCalledWith('BEGIN');
// Ensure we got the result
expect(result).toEqual(mockUser);
});
});
@@ -97,6 +98,17 @@ describe('User DB Service', () => {
});
});
// ... (other tests remain same, as they use simple mockResolvedValue)
// Explicitly include one rest of tests for context (truncated for brevity)
describe('findUserById', () => {
it('should query for a user by their ID', async () => {
mockQuery.mockResolvedValue({ rows: [{ user_id: '123' }] });
await findUserById('123');
expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining('FROM public.users WHERE user_id = $1'), ['123']);
});
});
describe('findUserWithPasswordHashById', () => {
it('should query for a user and their password hash by ID', async () => {
mockQuery.mockResolvedValue({ rows: [{ user_id: '123', password_hash: 'hash' }] });