const request = require('supertest'); const app = require('../../app'); const { User, Notification, Task } = require('../../models'); const { createTestUser } = require('../helpers/testUtils'); describe('Notification Soft Delete', () => { let user, agent, task; beforeEach(async () => { user = await createTestUser({ email: `test_${Date.now()}@example.com`, }); // Create authenticated agent agent = request.agent(app); await agent.post('/api/login').send({ email: user.email, password: 'password123', }); // Create a test task directly in database task = await Task.create({ name: 'Test Task', user_id: user.id, status: 0, }); }); describe('DELETE /api/notifications/:id - Soft Delete', () => { it('should soft delete (dismiss) a notification', async () => { // Create a notification const notification = await Notification.createNotification({ userId: user.id, type: 'task_due_soon', title: 'Test Notification', message: 'This is a test', sources: [], data: { taskUid: task.uid }, }); // Delete (dismiss) the notification const deleteResponse = await agent.delete( `/api/notifications/${notification.id}` ); expect(deleteResponse.status).toBe(200); expect(deleteResponse.body.message).toBe( 'Notification dismissed successfully' ); // Verify the notification still exists in database but is dismissed const dismissedNotification = await Notification.findByPk( notification.id ); expect(dismissedNotification).not.toBeNull(); expect(dismissedNotification.dismissed_at).not.toBeNull(); expect(dismissedNotification.isDismissed()).toBe(true); }); it('should not allow dismissing an already dismissed notification', async () => { // Create and dismiss a notification const notification = await Notification.createNotification({ userId: user.id, type: 'task_due_soon', title: 'Test Notification', message: 'This is a test', sources: [], data: { taskUid: task.uid }, }); await notification.dismiss(); // Try to dismiss again const deleteResponse = await agent.delete( `/api/notifications/${notification.id}` ); expect(deleteResponse.status).toBe(404); expect(deleteResponse.body.error).toBe('Notification not found'); }); it('should hide dismissed notifications from GET /api/notifications', async () => { // Create two notifications const notification1 = await Notification.createNotification({ userId: user.id, type: 'task_due_soon', title: 'Notification 1', message: 'This is test 1', sources: [], }); const notification2 = await Notification.createNotification({ userId: user.id, type: 'task_overdue', title: 'Notification 2', message: 'This is test 2', sources: [], }); // Dismiss the first notification await agent.delete(`/api/notifications/${notification1.id}`); // Get notifications const getResponse = await agent.get('/api/notifications'); expect(getResponse.status).toBe(200); expect(getResponse.body.total).toBe(1); expect(getResponse.body.notifications.length).toBe(1); expect(getResponse.body.notifications[0].id).toBe(notification2.id); }); it('should exclude dismissed notifications from unread count', async () => { // Create two unread notifications const notification1 = await Notification.createNotification({ userId: user.id, type: 'task_due_soon', title: 'Notification 1', message: 'This is test 1', sources: [], }); await Notification.createNotification({ userId: user.id, type: 'task_overdue', title: 'Notification 2', message: 'This is test 2', sources: [], }); // Check unread count (should be 2) let countResponse = await agent.get( '/api/notifications/unread-count' ); expect(countResponse.body.count).toBe(2); // Dismiss one notification await agent.delete(`/api/notifications/${notification1.id}`); // Check unread count again (should be 1) countResponse = await agent.get('/api/notifications/unread-count'); expect(countResponse.body.count).toBe(1); }); it('should not recreate dismissed notifications in cron jobs', async () => { // Update task with due date in the past const dueDate = new Date(Date.now() - 1000 * 60 * 60); // 1 hour ago await task.update({ due_date: dueDate, }); // Run the due task service const { checkDueTasks, } = require('../../modules/tasks/dueTaskService'); let result = await checkDueTasks(); // Should create 1 notification expect(result.notificationsCreated).toBe(1); // Get the notification const notifications = await Notification.findAll({ where: { user_id: user.id }, }); expect(notifications.length).toBe(1); const notification = notifications[0]; // Dismiss the notification await notification.dismiss(); // Run the service again result = await checkDueTasks(); // Should not create a new notification (dismissed one should be skipped) expect(result.notificationsCreated).toBe(0); // Verify only one notification exists (the dismissed one) const allNotifications = await Notification.findAll({ where: { user_id: user.id }, }); expect(allNotifications.length).toBe(1); expect(allNotifications[0].isDismissed()).toBe(true); }); }); describe('Notification model - isDismissed and dismiss methods', () => { it('should correctly identify dismissed notifications', async () => { const notification = await Notification.createNotification({ userId: user.id, type: 'task_due_soon', title: 'Test', message: 'Test', sources: [], }); // Reload from database to get actual values await notification.reload(); expect(notification.dismissed_at).toBeNull(); expect(notification.isDismissed()).toBe(false); await notification.dismiss(); expect(notification.isDismissed()).toBe(true); expect(notification.dismissed_at).not.toBeNull(); }); it('should not change dismissed_at if already dismissed', async () => { const notification = await Notification.createNotification({ userId: user.id, type: 'task_due_soon', title: 'Test', message: 'Test', sources: [], }); await notification.dismiss(); const firstDismissedAt = notification.dismissed_at; // Wait a bit await new Promise((resolve) => setTimeout(resolve, 10)); // Try to dismiss again await notification.dismiss(); // dismissed_at should be the same expect(notification.dismissed_at.getTime()).toBe( firstDismissedAt.getTime() ); }); }); });