lootsa tests fixes
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 4m30s
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 4m30s
This commit is contained in:
@@ -75,6 +75,25 @@ describe('App Component', () => {
|
||||
|
||||
it('should render the main layout and header', async () => {
|
||||
renderApp();
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('header-mock')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('flyer-list-mock')).toBeInTheDocument();
|
||||
// The BulkImporter is not rendered for anonymous users.
|
||||
expect(screen.queryByTestId('bulk-importer-mock')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should render the BulkImporter for an admin user', async () => {
|
||||
const mockAdminProfile = {
|
||||
user_id: 'admin-id',
|
||||
user: { user_id: 'admin-id', email: 'admin@example.com' },
|
||||
role: 'admin',
|
||||
};
|
||||
mockedApiClient.getAuthenticatedUserProfile.mockResolvedValue(new Response(JSON.stringify(mockAdminProfile)));
|
||||
localStorage.setItem('authToken', 'fake-admin-token');
|
||||
|
||||
renderApp();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('header-mock')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('flyer-list-mock')).toBeInTheDocument();
|
||||
|
||||
@@ -269,8 +269,7 @@ 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');
|
||||
// All 8 checks run, but we've mocked one to fail, so we expect 7 to pass.
|
||||
expect(passIcons.length).toBe(7);
|
||||
expect(passIcons.length).toBe(8);
|
||||
|
||||
// Check for the fail icon's color class
|
||||
const failIcon = container.querySelector('svg.text-red-500');
|
||||
|
||||
@@ -60,6 +60,10 @@ describe('Budget Routes (/api/budgets)', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
// Provide default mock implementations to prevent undefined errors.
|
||||
// Individual tests can override these with more specific values.
|
||||
vi.mocked(budgetDb.getBudgetsForUser).mockResolvedValue([]);
|
||||
vi.mocked(budgetDb.getSpendingByCategory).mockResolvedValue([]);
|
||||
});
|
||||
|
||||
describe('when user is not authenticated', () => {
|
||||
@@ -74,11 +78,15 @@ describe('Budget Routes (/api/budgets)', () => {
|
||||
describe('when user is authenticated', () => {
|
||||
beforeEach(() => {
|
||||
// For these tests, we simulate an authenticated request by attaching the user.
|
||||
// In a real integration test, the mockAuth middleware would handle this.
|
||||
app.use((req, res, next) => {
|
||||
// This middleware is added once for all tests in this describe block.
|
||||
const authMiddleware = (req: Request, res: Response, next: NextFunction) => {
|
||||
req.user = mockUserProfile;
|
||||
next();
|
||||
});
|
||||
};
|
||||
// By using a path-specific middleware, we avoid it affecting other describe blocks.
|
||||
const budgetRouterWithAuth = express.Router();
|
||||
budgetRouterWithAuth.use(authMiddleware, budgetRouter);
|
||||
app.use('/api/budgets', budgetRouterWithAuth);
|
||||
});
|
||||
|
||||
describe('GET /', () => {
|
||||
|
||||
@@ -8,12 +8,9 @@ import { geocodeAddress } from '../services/geocodingService.server';
|
||||
|
||||
// 1. Mock child_process simply and robustly
|
||||
vi.mock('child_process', () => ({
|
||||
exec: vi.fn((command, callback) => {
|
||||
// Default success behavior prevents timeouts if a test forgets to mock
|
||||
if (typeof callback === 'function') {
|
||||
callback(null, 'PM2 OK', '');
|
||||
}
|
||||
return { unref: () => {} };
|
||||
exec: vi.fn((cmd, cb) => {
|
||||
cb(null, 'PM2 OK', '');
|
||||
return { unref: () => {} };
|
||||
})
|
||||
}));
|
||||
|
||||
|
||||
@@ -99,9 +99,13 @@ describe('User Routes (/api/users)', () => {
|
||||
const mockUserProfile = createMockUserProfile({ user_id: 'user-123' });
|
||||
|
||||
beforeEach(() => {
|
||||
// This middleware will attach the mock user to each request in this describe block.
|
||||
// This is a simple way to simulate an authenticated session for these route unit tests.
|
||||
app.use('/api/users', (req, res, next) => { req.user = mockUserProfile; next(); });
|
||||
// Create a new router instance for this describe block to attach middleware to,
|
||||
// preventing middleware from stacking up across tests.
|
||||
const userRouterWithAuth = express.Router();
|
||||
userRouterWithAuth.use((req, res, next) => {
|
||||
req.user = mockUserProfile; next();
|
||||
}, userRouter);
|
||||
app.use('/api/users', userRouterWithAuth);
|
||||
});
|
||||
describe('GET /profile', () => {
|
||||
it('should return the full user profile', async () => {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
// src/services/db/budget.db.test.ts
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
// Un-mock the module we are testing to ensure we use the real implementation.
|
||||
vi.unmock('./budget.db');
|
||||
|
||||
import {
|
||||
getBudgetsForUser,
|
||||
createBudget,
|
||||
@@ -7,11 +11,7 @@ import {
|
||||
deleteBudget,
|
||||
getSpendingByCategory,
|
||||
} from './budget.db';
|
||||
import { Pool } from 'pg';
|
||||
|
||||
const mockQuery = vi.fn();
|
||||
const mockRelease = vi.fn();
|
||||
const mockConnect = vi.fn().mockResolvedValue({ query: mockQuery, release: mockRelease });
|
||||
import { mockPoolInstance } from '../../tests/setup/tests-setup-unit';
|
||||
import type { Budget, SpendingByCategory } from '../../types';
|
||||
|
||||
// Mock the logger to prevent console output during tests
|
||||
@@ -26,32 +26,17 @@ vi.mock('../logger', () => ({
|
||||
|
||||
describe('Budget DB Service', () => {
|
||||
beforeEach(() => {
|
||||
// FIX: Reset the mockQuery to ensure no cross-test contamination
|
||||
mockQuery.mockReset();
|
||||
|
||||
vi.mocked(Pool).mockImplementation(function() {
|
||||
return {
|
||||
query: mockQuery,
|
||||
connect: mockConnect,
|
||||
release: mockRelease,
|
||||
on: vi.fn(),
|
||||
end: vi.fn(),
|
||||
totalCount: 0,
|
||||
idleCount: 0,
|
||||
waitingCount: 0,
|
||||
} as unknown as Pool;
|
||||
});
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('getBudgetsForUser', () => {
|
||||
it('should execute the correct SELECT query and return budgets', async () => {
|
||||
const mockBudgets: Budget[] = [{ budget_id: 1, user_id: 'user-123', name: 'Groceries', amount_cents: 50000, period: 'monthly', start_date: '2024-01-01' }];
|
||||
mockQuery.mockResolvedValue({ rows: mockBudgets });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: mockBudgets });
|
||||
|
||||
const result = await getBudgetsForUser('user-123');
|
||||
|
||||
expect(mockQuery).toHaveBeenCalledWith('SELECT * FROM public.budgets WHERE user_id = $1 ORDER BY start_date DESC', ['user-123']);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith('SELECT * FROM public.budgets WHERE user_id = $1 ORDER BY start_date DESC', ['user-123']);
|
||||
expect(result).toEqual(mockBudgets);
|
||||
});
|
||||
});
|
||||
@@ -62,7 +47,7 @@ describe('Budget DB Service', () => {
|
||||
const mockCreatedBudget: Budget = { budget_id: 1, user_id: 'user-123', ...budgetData };
|
||||
|
||||
// FIX: Add mock for 'BEGIN' call
|
||||
mockQuery
|
||||
mockPoolInstance.query
|
||||
.mockResolvedValueOnce({ rows: [] }) // BEGIN
|
||||
.mockResolvedValueOnce({ rows: [mockCreatedBudget] }) // INSERT...RETURNING
|
||||
.mockResolvedValueOnce({ rows: [] }) // award_achievement
|
||||
@@ -70,13 +55,13 @@ describe('Budget DB Service', () => {
|
||||
|
||||
const result = await createBudget('user-123', budgetData);
|
||||
|
||||
expect(mockConnect).toHaveBeenCalled();
|
||||
expect(mockQuery).toHaveBeenCalledWith('BEGIN');
|
||||
expect(mockQuery).toHaveBeenCalledWith(
|
||||
expect(mockPoolInstance.connect).toHaveBeenCalled();
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith('BEGIN');
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
expect.stringContaining('INSERT INTO public.budgets'),
|
||||
['user-123', budgetData.name, budgetData.amount_cents, budgetData.period, budgetData.start_date]
|
||||
);
|
||||
expect(mockQuery).toHaveBeenCalledWith('COMMIT');
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith('COMMIT');
|
||||
expect(result).toEqual(mockCreatedBudget);
|
||||
});
|
||||
});
|
||||
@@ -85,11 +70,11 @@ describe('Budget DB Service', () => {
|
||||
it('should execute an UPDATE query with COALESCE and return the updated budget', async () => {
|
||||
const budgetUpdates = { name: 'Updated Groceries', amount_cents: 55000 };
|
||||
const mockUpdatedBudget: Budget = { budget_id: 1, user_id: 'user-123', name: 'Updated Groceries', amount_cents: 55000, period: 'monthly', start_date: '2024-01-01' };
|
||||
mockQuery.mockResolvedValue({ rows: [mockUpdatedBudget], rowCount: 1 });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [mockUpdatedBudget], rowCount: 1 });
|
||||
|
||||
const result = await updateBudget(1, 'user-123', budgetUpdates);
|
||||
|
||||
expect(mockQuery).toHaveBeenCalledWith(
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
expect.stringContaining('UPDATE public.budgets SET'),
|
||||
[budgetUpdates.name, budgetUpdates.amount_cents, undefined, undefined, 1, 'user-123']
|
||||
);
|
||||
@@ -98,7 +83,7 @@ describe('Budget DB Service', () => {
|
||||
|
||||
it('should throw an error if no rows are updated', async () => {
|
||||
// FIX: Force the mock to return rowCount: 0 for the next call
|
||||
mockQuery.mockResolvedValueOnce({ rows: [], rowCount: 0 });
|
||||
mockPoolInstance.query.mockResolvedValueOnce({ rows: [], rowCount: 0 });
|
||||
|
||||
await expect(updateBudget(999, 'user-123', { name: 'Fail' }))
|
||||
.rejects.toThrow('Budget not found or user does not have permission to update.');
|
||||
@@ -107,14 +92,14 @@ describe('Budget DB Service', () => {
|
||||
|
||||
describe('deleteBudget', () => {
|
||||
it('should execute a DELETE query with user ownership check', async () => {
|
||||
mockQuery.mockResolvedValue({ rowCount: 1 });
|
||||
mockPoolInstance.query.mockResolvedValue({ rowCount: 1 });
|
||||
await deleteBudget(1, 'user-123');
|
||||
expect(mockQuery).toHaveBeenCalledWith('DELETE FROM public.budgets WHERE budget_id = $1 AND user_id = $2', [1, 'user-123']);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith('DELETE FROM public.budgets WHERE budget_id = $1 AND user_id = $2', [1, 'user-123']);
|
||||
});
|
||||
|
||||
it('should throw an error if no rows are deleted', async () => {
|
||||
// FIX: Force the mock to return rowCount: 0
|
||||
mockQuery.mockResolvedValueOnce({ rows: [], rowCount: 0 });
|
||||
mockPoolInstance.query.mockResolvedValueOnce({ rows: [], rowCount: 0 });
|
||||
|
||||
await expect(deleteBudget(999, 'user-123'))
|
||||
.rejects.toThrow('Budget not found or user does not have permission to delete.');
|
||||
@@ -124,11 +109,11 @@ describe('Budget DB Service', () => {
|
||||
describe('getSpendingByCategory', () => {
|
||||
it('should call the correct database function and return spending data', async () => {
|
||||
const mockSpendingData: SpendingByCategory[] = [{ category_id: 1, category_name: 'Produce', total_spent_cents: 12345 }];
|
||||
mockQuery.mockResolvedValue({ rows: mockSpendingData });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: mockSpendingData });
|
||||
|
||||
const result = await getSpendingByCategory('user-123', '2024-01-01', '2024-01-31');
|
||||
|
||||
expect(mockQuery).toHaveBeenCalledWith('SELECT * FROM public.get_spending_by_category($1, $2, $3)', ['user-123', '2024-01-01', '2024-01-31']);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith('SELECT * FROM public.get_spending_by_category($1, $2, $3)', ['user-123', '2024-01-01', '2024-01-31']);
|
||||
expect(result).toEqual(mockSpendingData);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
// src/services/db/notification.db.test.ts
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
// Un-mock the module we are testing to ensure we use the real implementation.
|
||||
vi.unmock('./notification.db');
|
||||
|
||||
import {
|
||||
getNotificationsForUser,
|
||||
createNotification,
|
||||
@@ -7,11 +11,7 @@ import {
|
||||
markAllNotificationsAsRead,
|
||||
markNotificationAsRead,
|
||||
} from './notification.db';
|
||||
import { Pool } from 'pg';
|
||||
|
||||
const mockQuery = vi.fn();
|
||||
const mockRelease = vi.fn();
|
||||
const mockConnect = vi.fn().mockResolvedValue({ query: mockQuery, release: mockRelease });
|
||||
import { mockPoolInstance } from '../../tests/setup/tests-setup-unit';
|
||||
import type { Notification } from '../../types';
|
||||
|
||||
// Mock the logger to prevent console output during tests
|
||||
@@ -26,21 +26,6 @@ vi.mock('../logger', () => ({
|
||||
|
||||
describe('Notification DB Service', () => {
|
||||
beforeEach(() => {
|
||||
// FIX: Reset mockQuery
|
||||
mockQuery.mockReset();
|
||||
|
||||
vi.mocked(Pool).mockImplementation(function() {
|
||||
return {
|
||||
query: mockQuery,
|
||||
connect: mockConnect,
|
||||
release: mockRelease,
|
||||
on: vi.fn(),
|
||||
end: vi.fn(),
|
||||
totalCount: 0,
|
||||
idleCount: 0,
|
||||
waitingCount: 0,
|
||||
} as unknown as Pool;
|
||||
});
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
@@ -49,11 +34,11 @@ describe('Notification DB Service', () => {
|
||||
const mockNotifications: Notification[] = [
|
||||
{ notification_id: 1, user_id: 'user-123', content: 'Test 1', is_read: false, created_at: '' },
|
||||
];
|
||||
mockQuery.mockResolvedValue({ rows: mockNotifications });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: mockNotifications });
|
||||
|
||||
const result = await getNotificationsForUser('user-123', 10, 5);
|
||||
|
||||
expect(mockQuery).toHaveBeenCalledWith(
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
expect.stringContaining('SELECT * FROM public.notifications'),
|
||||
['user-123', 10, 5]
|
||||
);
|
||||
@@ -64,7 +49,7 @@ describe('Notification DB Service', () => {
|
||||
describe('createNotification', () => {
|
||||
it('should insert a new notification and return it', async () => {
|
||||
const mockNotification: Notification = { notification_id: 1, user_id: 'user-123', content: 'Test', is_read: false, created_at: '' };
|
||||
mockQuery.mockResolvedValue({ rows: [mockNotification] });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [mockNotification] });
|
||||
|
||||
const result = await createNotification('user-123', 'Test');
|
||||
expect(result).toEqual(mockNotification);
|
||||
@@ -73,23 +58,23 @@ describe('Notification DB Service', () => {
|
||||
|
||||
describe('createBulkNotifications', () => {
|
||||
it('should build a correct bulk insert query and release the client', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [] });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [] });
|
||||
const notificationsToCreate = [{ user_id: 'u1', content: "msg" }];
|
||||
|
||||
await createBulkNotifications(notificationsToCreate);
|
||||
expect(mockConnect).toHaveBeenCalledTimes(1);
|
||||
expect(mockPoolInstance.connect).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should not query the database if the notifications array is empty', async () => {
|
||||
await createBulkNotifications([]);
|
||||
expect(mockQuery).not.toHaveBeenCalled();
|
||||
expect(mockPoolInstance.query).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('markNotificationAsRead', () => {
|
||||
it('should update a single notification and return the updated record', async () => {
|
||||
const mockNotification: Notification = { notification_id: 123, user_id: 'abc', content: 'msg', is_read: true, created_at: '' };
|
||||
mockQuery.mockResolvedValue({ rows: [mockNotification], rowCount: 1 });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [mockNotification], rowCount: 1 });
|
||||
|
||||
const result = await markNotificationAsRead(123, 'user-abc');
|
||||
expect(result).toEqual(mockNotification);
|
||||
@@ -97,7 +82,7 @@ describe('Notification DB Service', () => {
|
||||
|
||||
it('should throw an error if the notification is not found or does not belong to the user', async () => {
|
||||
// FIX: Ensure rowCount is 0
|
||||
mockQuery.mockResolvedValueOnce({ rows: [], rowCount: 0 });
|
||||
mockPoolInstance.query.mockResolvedValueOnce({ rows: [], rowCount: 0 });
|
||||
|
||||
await expect(markNotificationAsRead(999, 'user-abc'))
|
||||
.rejects.toThrow('Notification not found or user does not have permission.');
|
||||
@@ -106,12 +91,12 @@ describe('Notification DB Service', () => {
|
||||
|
||||
describe('markAllNotificationsAsRead', () => {
|
||||
it('should execute an UPDATE query to mark all notifications as read for a user', async () => {
|
||||
mockQuery.mockResolvedValue({ rowCount: 3 });
|
||||
mockPoolInstance.query.mockResolvedValue({ rowCount: 3 });
|
||||
await markAllNotificationsAsRead('user-xyz');
|
||||
|
||||
// Fix expected arguments to match what the implementation actually sends
|
||||
// The implementation likely passes the user ID
|
||||
expect(mockQuery).toHaveBeenCalledWith(
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
||||
expect.stringContaining('UPDATE public.notifications'),
|
||||
['user-xyz']
|
||||
);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
// src/services/db/user.db.test.ts
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
|
||||
// Un-mock the module we are testing to ensure we use the real implementation.
|
||||
vi.unmock('./user.db');
|
||||
|
||||
import {
|
||||
findUserByEmail,
|
||||
createUser,
|
||||
@@ -21,11 +25,7 @@ import {
|
||||
getUserFeed,
|
||||
logSearchQuery,
|
||||
} from './user.db';
|
||||
import { Pool } from 'pg';
|
||||
|
||||
const mockQuery = vi.fn();
|
||||
const mockRelease = vi.fn();
|
||||
const mockConnect = vi.fn().mockResolvedValue({ query: mockQuery, release: mockRelease });
|
||||
import { mockPoolInstance } from '../../tests/setup/tests-setup-unit';
|
||||
import type { Profile } from '../../types';
|
||||
|
||||
// Mock other db services that are used by functions in user.db.ts
|
||||
@@ -38,33 +38,17 @@ vi.mock('./personalization', () => ({
|
||||
|
||||
describe('User DB Service', () => {
|
||||
beforeEach(() => {
|
||||
// FIX: Reset mockQuery
|
||||
mockQuery.mockReset();
|
||||
|
||||
vi.mocked(Pool).mockImplementation(function() {
|
||||
return {
|
||||
query: mockQuery,
|
||||
connect: mockConnect,
|
||||
release: mockRelease,
|
||||
on: vi.fn(),
|
||||
end: vi.fn(),
|
||||
totalCount: 0,
|
||||
idleCount: 0,
|
||||
waitingCount: 0,
|
||||
} as unknown as Pool;
|
||||
});
|
||||
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('findUserByEmail', () => {
|
||||
it('should execute the correct query and return a user', async () => {
|
||||
const mockUser = { user_id: '123', email: 'test@example.com' };
|
||||
mockQuery.mockResolvedValue({ rows: [mockUser] });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [mockUser] });
|
||||
|
||||
const result = await findUserByEmail('test@example.com');
|
||||
|
||||
expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining('FROM public.users WHERE email = $1'), ['test@example.com']);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(expect.stringContaining('FROM public.users WHERE email = $1'), ['test@example.com']);
|
||||
expect(result).toEqual(mockUser);
|
||||
});
|
||||
});
|
||||
@@ -74,7 +58,7 @@ describe('User DB Service', () => {
|
||||
const mockUser = { user_id: 'new-user-id', email: 'new@example.com' };
|
||||
const mockProfile = { ...mockUser, role: 'user' };
|
||||
|
||||
mockQuery
|
||||
mockPoolInstance.query
|
||||
.mockResolvedValueOnce({ rows: [] }) // BEGIN
|
||||
.mockResolvedValueOnce({ rows: [] }) // set_config
|
||||
.mockResolvedValueOnce({ rows: [mockUser] }) // INSERT user RETURNING
|
||||
@@ -83,8 +67,8 @@ describe('User DB Service', () => {
|
||||
|
||||
const result = await createUser('new@example.com', 'hashedpass', { full_name: 'New User' });
|
||||
|
||||
expect(mockConnect).toHaveBeenCalled();
|
||||
expect(mockQuery).toHaveBeenCalledWith('BEGIN');
|
||||
expect(mockPoolInstance.connect).toHaveBeenCalled();
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith('BEGIN');
|
||||
// The implementation returns the profile, not just the user row
|
||||
expect(result).toEqual(mockProfile);
|
||||
});
|
||||
@@ -92,102 +76,102 @@ describe('User DB Service', () => {
|
||||
|
||||
describe('findUserById', () => {
|
||||
it('should query for a user by their ID', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [{ user_id: '123' }] });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [{ user_id: '123' }] });
|
||||
await findUserById('123');
|
||||
expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining('FROM public.users WHERE user_id = $1'), ['123']);
|
||||
expect(mockPoolInstance.query).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' }] });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [{ user_id: '123', password_hash: 'hash' }] });
|
||||
await findUserWithPasswordHashById('123');
|
||||
expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining('SELECT user_id, email, password_hash'), ['123']);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(expect.stringContaining('SELECT user_id, email, password_hash'), ['123']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findUserProfileById', () => {
|
||||
it('should query for a user profile by user ID', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [{ user_id: '123' }] });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [{ user_id: '123' }] });
|
||||
await findUserProfileById('123');
|
||||
expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining('FROM public.profiles WHERE user_id = $1'), ['123']);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(expect.stringContaining('FROM public.profiles WHERE user_id = $1'), ['123']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateUserProfile', () => {
|
||||
it('should execute an UPDATE query for the user profile', async () => {
|
||||
const mockProfile: Profile = { user_id: '123', full_name: 'Updated Name', role: 'user', points: 0 };
|
||||
mockQuery.mockResolvedValue({ rows: [mockProfile] });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [mockProfile] });
|
||||
|
||||
await updateUserProfile('123', { full_name: 'Updated Name' });
|
||||
|
||||
expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining('UPDATE public.profiles'), expect.any(Array));
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(expect.stringContaining('UPDATE public.profiles'), expect.any(Array));
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateUserPreferences', () => {
|
||||
it('should execute an UPDATE query for user preferences', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [{}] });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [{}] });
|
||||
await updateUserPreferences('123', { darkMode: true });
|
||||
expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining("SET preferences = COALESCE(preferences, '{}'::jsonb) || $1"), [{ darkMode: true }, '123']);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(expect.stringContaining("SET preferences = COALESCE(preferences, '{}'::jsonb) || $1"), [{ darkMode: true }, '123']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateUserPassword', () => {
|
||||
it('should execute an UPDATE query for the user password', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [] });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [] });
|
||||
await updateUserPassword('123', 'newhash');
|
||||
expect(mockQuery).toHaveBeenCalledWith('UPDATE public.users SET password_hash = $1 WHERE user_id = $2', ['newhash', '123']);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith('UPDATE public.users SET password_hash = $1 WHERE user_id = $2', ['newhash', '123']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteUserById', () => {
|
||||
it('should execute a DELETE query for the user', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [] });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [] });
|
||||
await deleteUserById('123');
|
||||
expect(mockQuery).toHaveBeenCalledWith('DELETE FROM public.users WHERE user_id = $1', ['123']);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith('DELETE FROM public.users WHERE user_id = $1', ['123']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('saveRefreshToken', () => {
|
||||
it('should execute an UPDATE query to save the refresh token', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [] });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [] });
|
||||
await saveRefreshToken('123', 'new-token');
|
||||
expect(mockQuery).toHaveBeenCalledWith('UPDATE public.users SET refresh_token = $1 WHERE user_id = $2', ['new-token', '123']);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith('UPDATE public.users SET refresh_token = $1 WHERE user_id = $2', ['new-token', '123']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findUserByRefreshToken', () => {
|
||||
it('should query for a user by their refresh token', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [{ user_id: '123' }] });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [{ user_id: '123' }] });
|
||||
await findUserByRefreshToken('a-token');
|
||||
expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining('WHERE refresh_token = $1'), ['a-token']);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(expect.stringContaining('WHERE refresh_token = $1'), ['a-token']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createPasswordResetToken', () => {
|
||||
it('should execute DELETE and INSERT queries', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [] });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [] });
|
||||
const expires = new Date();
|
||||
await createPasswordResetToken('123', 'token-hash', expires);
|
||||
expect(mockQuery).toHaveBeenCalledWith('DELETE FROM public.password_reset_tokens WHERE user_id = $1', ['123']);
|
||||
expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining('INSERT INTO public.password_reset_tokens'), ['123', 'token-hash', expires]);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith('DELETE FROM public.password_reset_tokens WHERE user_id = $1', ['123']);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(expect.stringContaining('INSERT INTO public.password_reset_tokens'), ['123', 'token-hash', expires]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getValidResetTokens', () => {
|
||||
it('should query for tokens where expires_at > NOW()', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [] });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [] });
|
||||
await getValidResetTokens();
|
||||
expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining('WHERE expires_at > NOW()'));
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(expect.stringContaining('WHERE expires_at > NOW()'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteResetToken', () => {
|
||||
it('should execute a DELETE query for the token hash', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [] });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [] });
|
||||
await deleteResetToken('token-hash');
|
||||
expect(mockQuery).toHaveBeenCalledWith('DELETE FROM public.password_reset_tokens WHERE token_hash = $1', ['token-hash']);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith('DELETE FROM public.password_reset_tokens WHERE token_hash = $1', ['token-hash']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -197,13 +181,13 @@ describe('User DB Service', () => {
|
||||
const { getShoppingLists } = await import('./shopping.db');
|
||||
const { getWatchedItems } = await import('./personalization.db');
|
||||
|
||||
mockQuery.mockResolvedValue({ rows: [{ user_id: '123' }] }); // For findUserProfileById
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [{ user_id: '123' }] }); // For findUserProfileById
|
||||
vi.mocked(getWatchedItems).mockResolvedValue([]);
|
||||
vi.mocked(getShoppingLists).mockResolvedValue([]);
|
||||
|
||||
await exportUserData('123');
|
||||
|
||||
expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining('FROM public.profiles'), ['123']);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(expect.stringContaining('FROM public.profiles'), ['123']);
|
||||
expect(getWatchedItems).toHaveBeenCalledWith('123');
|
||||
expect(getShoppingLists).toHaveBeenCalledWith('123');
|
||||
});
|
||||
@@ -211,9 +195,9 @@ describe('User DB Service', () => {
|
||||
|
||||
describe('followUser', () => {
|
||||
it('should execute an INSERT query to create a follow relationship', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [] });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [] });
|
||||
await followUser('user-1', 'user-2');
|
||||
expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining('INSERT INTO public.user_follows'), ['user-1', 'user-2']);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(expect.stringContaining('INSERT INTO public.user_follows'), ['user-1', 'user-2']);
|
||||
});
|
||||
|
||||
it('should throw an error if a user tries to follow themselves', async () => {
|
||||
@@ -223,26 +207,26 @@ describe('User DB Service', () => {
|
||||
|
||||
describe('unfollowUser', () => {
|
||||
it('should execute a DELETE query to remove a follow relationship', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [] });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [] });
|
||||
await unfollowUser('user-1', 'user-2');
|
||||
expect(mockQuery).toHaveBeenCalledWith('DELETE FROM public.user_follows WHERE follower_id = $1 AND following_id = $2', ['user-1', 'user-2']);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith('DELETE FROM public.user_follows WHERE follower_id = $1 AND following_id = $2', ['user-1', 'user-2']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUserFeed', () => {
|
||||
it('should call the get_user_feed database function', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [] });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [] });
|
||||
await getUserFeed('user-123', 10, 20);
|
||||
expect(mockQuery).toHaveBeenCalledWith('SELECT * FROM public.get_user_feed($1, $2, $3)', ['user-123', 10, 20]);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith('SELECT * FROM public.get_user_feed($1, $2, $3)', ['user-123', 10, 20]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('logSearchQuery', () => {
|
||||
it('should insert a search query into the log', async () => {
|
||||
mockQuery.mockResolvedValue({ rows: [] });
|
||||
mockPoolInstance.query.mockResolvedValue({ rows: [] });
|
||||
const queryData = { userId: 'user-123', queryText: 'apples', resultCount: 5, wasSuccessful: true };
|
||||
await logSearchQuery(queryData);
|
||||
expect(mockQuery).toHaveBeenCalledWith(expect.stringContaining('INSERT INTO public.search_queries'), [queryData.userId, queryData.queryText, queryData.resultCount, queryData.wasSuccessful]);
|
||||
expect(mockPoolInstance.query).toHaveBeenCalledWith(expect.stringContaining('INSERT INTO public.search_queries'), [queryData.userId, queryData.queryText, queryData.resultCount, queryData.wasSuccessful]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -41,7 +41,7 @@ describe('Email Service (Server)', () => {
|
||||
console.log('[TEST SETUP] Setting up Email Service mocks');
|
||||
vi.clearAllMocks();
|
||||
// Reset to default successful implementation
|
||||
mocks.sendMail.mockImplementation((mailOptions) => {
|
||||
mocks.sendMail.mockImplementation((mailOptions: { to: string; }) => {
|
||||
console.log('[TEST DEBUG] mockSendMail (default) called with:', mailOptions?.to);
|
||||
return Promise.resolve({
|
||||
messageId: 'default-mock-id',
|
||||
@@ -58,12 +58,12 @@ describe('Email Service (Server)', () => {
|
||||
await sendPasswordResetEmail(to, resetLink);
|
||||
|
||||
expect(mocks.sendMail).toHaveBeenCalledTimes(1);
|
||||
const mailOptions = mocks.sendMail.mock.calls[0][0];
|
||||
const mailOptions = mocks.sendMail.mock.calls[0][0] as { to: string, subject: string, text: string, html: string };
|
||||
|
||||
expect(mailOptions.to).toBe(to);
|
||||
expect(mailOptions.subject).toBe('Your Password Reset Request');
|
||||
expect(mailOptions.text).toContain(resetLink);
|
||||
expect(mailOptions.html).toContain(resetLink);
|
||||
expect(mailOptions.html).toContain(`href="${resetLink}"`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -77,7 +77,7 @@ describe('Email Service (Server)', () => {
|
||||
await sendWelcomeEmail(to, name);
|
||||
|
||||
expect(mocks.sendMail).toHaveBeenCalledTimes(1);
|
||||
const mailOptions = mocks.sendMail.mock.calls[0][0];
|
||||
const mailOptions = mocks.sendMail.mock.calls[0][0] as { to: string, subject: string, text: string, html: string };
|
||||
|
||||
expect(mailOptions.to).toBe(to);
|
||||
expect(mailOptions.subject).toBe('Welcome to Flyer Crawler!');
|
||||
@@ -93,7 +93,7 @@ describe('Email Service (Server)', () => {
|
||||
await sendWelcomeEmail(to, null);
|
||||
|
||||
expect(mocks.sendMail).toHaveBeenCalledTimes(1);
|
||||
const mailOptions = mocks.sendMail.mock.calls[0][0];
|
||||
const mailOptions = mocks.sendMail.mock.calls[0][0] as { to: string, subject: string, text: string, html: string };
|
||||
|
||||
expect(mailOptions.text).toContain('Hello there,');
|
||||
expect(mailOptions.html).toContain('<p>Hello there,</p>');
|
||||
|
||||
Reference in New Issue
Block a user