Some checks failed
Deploy to Test Environment / deploy-to-test (push) Failing after 7m32s
207 lines
9.4 KiB
TypeScript
207 lines
9.4 KiB
TypeScript
// 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 { NotificationRepository } from './notification.db';
|
|
import { mockPoolInstance } from '../../tests/setup/tests-setup-unit';
|
|
import { ForeignKeyConstraintError } from './errors.db';
|
|
import type { Notification } from '../../types';
|
|
|
|
// Mock the logger to prevent console output during tests
|
|
vi.mock('../logger.server', () => ({
|
|
logger: {
|
|
info: vi.fn(),
|
|
warn: vi.fn(),
|
|
error: vi.fn(),
|
|
debug: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
describe('Notification DB Service', () => {
|
|
let notificationRepo: NotificationRepository;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
// Instantiate the repository with the mock pool for each test
|
|
notificationRepo = new NotificationRepository(mockPoolInstance as any);
|
|
});
|
|
|
|
describe('getNotificationsForUser', () => {
|
|
it('should execute the correct query with limit and offset and return notifications', async () => {
|
|
const mockNotifications: Notification[] = [
|
|
{ notification_id: 1, user_id: 'user-123', content: 'Test 1', is_read: false, created_at: '' },
|
|
];
|
|
mockPoolInstance.query.mockResolvedValue({ rows: mockNotifications });
|
|
|
|
const result = await notificationRepo.getNotificationsForUser('user-123', 10, 5);
|
|
|
|
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
|
expect.stringContaining('SELECT * FROM public.notifications'),
|
|
['user-123', 10, 5]
|
|
);
|
|
expect(result).toEqual(mockNotifications);
|
|
});
|
|
|
|
it('should return an empty array if the user has no notifications', async () => {
|
|
mockPoolInstance.query.mockResolvedValue({ rows: [] });
|
|
const result = await notificationRepo.getNotificationsForUser('user-456', 10, 0);
|
|
expect(result).toEqual([]);
|
|
expect(mockPoolInstance.query).toHaveBeenCalledWith(expect.any(String), ['user-456', 10, 0]);
|
|
});
|
|
|
|
it('should throw an error if the database query fails', async () => {
|
|
const dbError = new Error('DB Error');
|
|
mockPoolInstance.query.mockRejectedValue(dbError);
|
|
await expect(notificationRepo.getNotificationsForUser('user-123', 10, 5)).rejects.toThrow('Failed to retrieve notifications.');
|
|
});
|
|
});
|
|
|
|
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: '' };
|
|
mockPoolInstance.query.mockResolvedValue({ rows: [mockNotification] });
|
|
|
|
const result = await notificationRepo.createNotification('user-123', 'Test');
|
|
expect(result).toEqual(mockNotification);
|
|
});
|
|
|
|
it('should insert a notification with a linkUrl', async () => {
|
|
const mockNotification: Notification = { notification_id: 2, user_id: 'user-123', content: 'Test with link', link_url: '/some/link', is_read: false, created_at: '' };
|
|
mockPoolInstance.query.mockResolvedValue({ rows: [mockNotification] });
|
|
|
|
const result = await notificationRepo.createNotification('user-123', 'Test with link', '/some/link');
|
|
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
|
expect.stringContaining('INSERT INTO public.notifications'),
|
|
['user-123', 'Test with link', '/some/link']
|
|
);
|
|
expect(result).toEqual(mockNotification);
|
|
});
|
|
|
|
it('should throw ForeignKeyConstraintError if user does not exist', async () => {
|
|
const dbError = new Error('violates foreign key constraint');
|
|
(dbError as any).code = '23503';
|
|
mockPoolInstance.query.mockRejectedValueOnce(dbError);
|
|
await expect(notificationRepo.createNotification('non-existent-user', 'Test')).rejects.toThrow(ForeignKeyConstraintError);
|
|
});
|
|
|
|
it('should throw a generic error if the database query fails', async () => {
|
|
const dbError = new Error('DB Error');
|
|
mockPoolInstance.query.mockRejectedValue(dbError);
|
|
await expect(notificationRepo.createNotification('user-123', 'Test')).rejects.toThrow('Failed to create notification.');
|
|
});
|
|
});
|
|
|
|
describe('createBulkNotifications', () => {
|
|
it('should build a correct bulk insert query and release the client', async () => {
|
|
mockPoolInstance.query.mockResolvedValue({ rows: [] });
|
|
const notificationsToCreate = [{ user_id: 'u1', content: "msg" }];
|
|
|
|
await notificationRepo.createBulkNotifications(notificationsToCreate);
|
|
// Check that the query was called with the correct unnest structure
|
|
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
|
expect.stringContaining('SELECT * FROM unnest($1::uuid[], $2::text[], $3::text[])'),
|
|
[['u1'], ['msg'], [null]]
|
|
);
|
|
});
|
|
|
|
it('should not query the database if the notifications array is empty', async () => {
|
|
await notificationRepo.createBulkNotifications([]);
|
|
expect(mockPoolInstance.query).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should throw ForeignKeyConstraintError if any user does not exist', async () => {
|
|
const dbError = new Error('violates foreign key constraint');
|
|
(dbError as any).code = '23503';
|
|
mockPoolInstance.query.mockRejectedValue(dbError);
|
|
const notificationsToCreate = [{ user_id: 'non-existent', content: "msg" }];
|
|
await expect(notificationRepo.createBulkNotifications(notificationsToCreate)).rejects.toThrow('One or more of the specified users do not exist.');
|
|
});
|
|
|
|
it('should throw a generic error if the database query fails', async () => {
|
|
const dbError = new Error('DB Error');
|
|
mockPoolInstance.query.mockRejectedValue(dbError);
|
|
const notificationsToCreate = [{ user_id: 'u1', content: "msg" }];
|
|
await expect(notificationRepo.createBulkNotifications(notificationsToCreate)).rejects.toThrow('Failed to create bulk notifications.');
|
|
});
|
|
});
|
|
|
|
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: '' };
|
|
mockPoolInstance.query.mockResolvedValue({ rows: [mockNotification], rowCount: 1 });
|
|
|
|
const result = await notificationRepo.markNotificationAsRead(123, 'abc');
|
|
expect(result).toEqual(mockNotification);
|
|
});
|
|
|
|
it('should throw an error if the notification is not found or does not belong to the user', async () => {
|
|
// FIX: Ensure rowCount is 0
|
|
mockPoolInstance.query.mockResolvedValue({ rows: [], rowCount: 0 });
|
|
|
|
await expect(notificationRepo.markNotificationAsRead(999, 'abc'))
|
|
.rejects.toThrow('Notification not found or user does not have permission.');
|
|
});
|
|
|
|
it('should re-throw the specific "not found" error if it occurs', async () => {
|
|
// This tests the `if (error instanceof Error && error.message.startsWith('Notification not found'))` line
|
|
const notFoundError = new Error('Notification not found or user does not have permission.');
|
|
mockPoolInstance.query.mockImplementation(() => {
|
|
throw notFoundError;
|
|
});
|
|
|
|
await expect(notificationRepo.markNotificationAsRead(999, 'user-abc')).rejects.toThrow(notFoundError);
|
|
});
|
|
|
|
it('should throw a generic error if the database query fails', async () => {
|
|
const dbError = new Error('DB Error');
|
|
mockPoolInstance.query.mockRejectedValue(dbError);
|
|
await expect(notificationRepo.markNotificationAsRead(123, 'abc')).rejects.toThrow('Failed to mark notification as read.');
|
|
});
|
|
});
|
|
|
|
describe('markAllNotificationsAsRead', () => {
|
|
it('should execute an UPDATE query to mark all notifications as read for a user', async () => {
|
|
mockPoolInstance.query.mockResolvedValue({ rowCount: 3 });
|
|
await notificationRepo.markAllNotificationsAsRead('user-xyz');
|
|
|
|
// Fix expected arguments to match what the implementation actually sends
|
|
// The implementation likely passes the user ID
|
|
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
|
expect.stringContaining('UPDATE public.notifications'),
|
|
['user-xyz']
|
|
);
|
|
});
|
|
|
|
it('should throw an error if the database query fails', async () => {
|
|
const dbError = new Error('DB Error');
|
|
mockPoolInstance.query.mockRejectedValue(dbError);
|
|
await expect(notificationRepo.markAllNotificationsAsRead('user-xyz')).rejects.toThrow('Failed to mark notifications as read.');
|
|
});
|
|
});
|
|
|
|
describe('deleteOldNotifications', () => {
|
|
it('should execute a DELETE query and return the number of deleted rows', async () => {
|
|
mockPoolInstance.query.mockResolvedValue({ rowCount: 5 });
|
|
const result = await notificationRepo.deleteOldNotifications(30);
|
|
expect(mockPoolInstance.query).toHaveBeenCalledWith(
|
|
`DELETE FROM public.notifications WHERE created_at < NOW() - ($1 * interval '1 day')`,
|
|
[30]
|
|
);
|
|
expect(result).toBe(5);
|
|
});
|
|
|
|
it('should return 0 if rowCount is null or undefined', async () => {
|
|
mockPoolInstance.query.mockResolvedValue({ rowCount: null });
|
|
const result = await notificationRepo.deleteOldNotifications(30);
|
|
expect(result).toBe(0);
|
|
});
|
|
|
|
it('should throw an error if the database query fails', async () => {
|
|
const dbError = new Error('DB Error');
|
|
mockPoolInstance.query.mockRejectedValue(dbError);
|
|
await expect(notificationRepo.deleteOldNotifications(30)).rejects.toThrow('Failed to delete old notifications.');
|
|
});
|
|
});
|
|
}); |