import type { Logger } from '@core/shared/domain/Logger'; import { Result } from '@core/shared/domain/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import { describe, expect, it, vi, type Mock } from 'vitest'; import { Notification } from '../../domain/entities/Notification'; import { DismissNotificationUseCase, MarkAllNotificationsReadUseCase, MarkNotificationReadUseCase, type DismissNotificationCommand, type MarkAllNotificationsReadInput, type MarkNotificationReadCommand, } from './MarkNotificationReadUseCase'; interface NotificationRepositoryMock { findById: Mock; update: Mock; markAllAsReadByRecipientId: Mock; } describe('MarkNotificationReadUseCase', () => { let notificationRepository: NotificationRepositoryMock; let logger: Logger; let useCase: MarkNotificationReadUseCase; beforeEach(() => { notificationRepository = { findById: vi.fn(), update: vi.fn(), markAllAsReadByRecipientId: vi.fn(), }; logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), } as unknown as Logger; useCase = new MarkNotificationReadUseCase( notificationRepository as unknown as NotificationRepository, logger, ); }); it('returns NOTIFICATION_NOT_FOUND when notification is not found', async () => { notificationRepository.findById.mockResolvedValue(null); const command: MarkNotificationReadCommand = { notificationId: 'n1', recipientId: 'driver-1', }; const result = await useCase.execute(command); expect(result).toBeInstanceOf(Result); expect(result.isErr()).toBe(true); const err = result.unwrapErr() as ApplicationErrorCode<'NOTIFICATION_NOT_FOUND', { message: string }>; expect(err.code).toBe('NOTIFICATION_NOT_FOUND'); }); it('returns RECIPIENT_MISMATCH when recipientId does not match', async () => { const notification = Notification.create({ id: 'n1', recipientId: 'driver-2', type: 'system_announcement', title: 'Test', body: 'Body', channel: 'in_app', }); notificationRepository.findById.mockResolvedValue(notification); const command: MarkNotificationReadCommand = { notificationId: 'n1', recipientId: 'driver-1', }; const result = await useCase.execute(command); expect(result.isErr()).toBe(true); const err = result.unwrapErr() as ApplicationErrorCode<'RECIPIENT_MISMATCH', { message: string }>; expect(err.code).toBe('RECIPIENT_MISMATCH'); }); it('marks notification as read when unread and returns success result', async () => { const notification = Notification.create({ id: 'n1', recipientId: 'driver-1', type: 'system_announcement', title: 'Test', body: 'Body', channel: 'in_app', }); notificationRepository.findById.mockResolvedValue(notification); const command: MarkNotificationReadCommand = { notificationId: 'n1', recipientId: 'driver-1', }; const result = await useCase.execute(command); expect(result.isOk()).toBe(true); expect(notificationRepository.update).toHaveBeenCalled(); const successResult = result.unwrap(); expect(successResult.notificationId).toBe('n1'); expect(successResult.recipientId).toBe('driver-1'); expect(successResult.wasAlreadyRead).toBe(false); }); it('returns already read when notification is already read', async () => { const notification = Notification.create({ id: 'n1', recipientId: 'driver-1', type: 'system_announcement', title: 'Test', body: 'Body', channel: 'in_app', }); // Mark as read const readNotification = notification.markAsRead(); notificationRepository.findById.mockResolvedValue(readNotification); const command: MarkNotificationReadCommand = { notificationId: 'n1', recipientId: 'driver-1', }; const result = await useCase.execute(command); expect(result.isOk()).toBe(true); expect(notificationRepository.update).not.toHaveBeenCalled(); const successResult = result.unwrap(); expect(successResult.wasAlreadyRead).toBe(true); }); }); describe('MarkAllNotificationsReadUseCase', () => { let notificationRepository: NotificationRepositoryMock; let useCase: MarkAllNotificationsReadUseCase; beforeEach(() => { notificationRepository = { findById: vi.fn(), update: vi.fn(), markAllAsReadByRecipientId: vi.fn(), }; useCase = new MarkAllNotificationsReadUseCase( notificationRepository as unknown as NotificationRepository, ); }); it('marks all notifications as read', async () => { const input: MarkAllNotificationsReadInput = { recipientId: 'driver-1', }; const result = await useCase.execute(input); expect(notificationRepository.markAllAsReadByRecipientId).toHaveBeenCalledWith('driver-1'); expect(result.isOk()).toBe(true); const successResult = result.unwrap(); expect(successResult.recipientId).toBe('driver-1'); }); it('handles repository errors', async () => { notificationRepository.markAllAsReadByRecipientId.mockRejectedValue(new Error('DB error')); const input: MarkAllNotificationsReadInput = { recipientId: 'driver-1', }; const result = await useCase.execute(input); expect(result.isErr()).toBe(true); const err = result.unwrapErr() as ApplicationErrorCode<'REPOSITORY_ERROR', { message: string }>; expect(err.code).toBe('REPOSITORY_ERROR'); }); }); describe('DismissNotificationUseCase', () => { let notificationRepository: NotificationRepositoryMock; let useCase: DismissNotificationUseCase; beforeEach(() => { notificationRepository = { findById: vi.fn(), update: vi.fn(), markAllAsReadByRecipientId: vi.fn(), }; useCase = new DismissNotificationUseCase( notificationRepository as unknown as NotificationRepository, ); }); it('returns NOTIFICATION_NOT_FOUND when notification is not found', async () => { notificationRepository.findById.mockResolvedValue(null); const command: DismissNotificationCommand = { notificationId: 'n1', recipientId: 'driver-1', }; const result = await useCase.execute(command); expect(result.isErr()).toBe(true); const err = result.unwrapErr() as ApplicationErrorCode<'NOTIFICATION_NOT_FOUND', { message: string }>; expect(err.code).toBe('NOTIFICATION_NOT_FOUND'); }); it('returns RECIPIENT_MISMATCH when recipientId does not match', async () => { const notification = Notification.create({ id: 'n1', recipientId: 'driver-2', type: 'system_announcement', title: 'Test', body: 'Body', channel: 'in_app', }); notificationRepository.findById.mockResolvedValue(notification); const command: DismissNotificationCommand = { notificationId: 'n1', recipientId: 'driver-1', }; const result = await useCase.execute(command); expect(result.isErr()).toBe(true); const err = result.unwrapErr() as ApplicationErrorCode<'RECIPIENT_MISMATCH', { message: string }>; expect(err.code).toBe('RECIPIENT_MISMATCH'); }); it('dismisses notification and returns success result', async () => { const notification = Notification.create({ id: 'n1', recipientId: 'driver-1', type: 'system_announcement', title: 'Test', body: 'Body', channel: 'in_app', }); notificationRepository.findById.mockResolvedValue(notification); const command: DismissNotificationCommand = { notificationId: 'n1', recipientId: 'driver-1', }; const result = await useCase.execute(command); expect(result.isOk()).toBe(true); expect(notificationRepository.update).toHaveBeenCalled(); const successResult = result.unwrap(); expect(successResult.notificationId).toBe('n1'); expect(successResult.recipientId).toBe('driver-1'); expect(successResult.wasAlreadyDismissed).toBe(false); }); it('returns already dismissed when notification is already dismissed', async () => { const notification = Notification.create({ id: 'n1', recipientId: 'driver-1', type: 'system_announcement', title: 'Test', body: 'Body', channel: 'in_app', }); // Dismiss it const dismissedNotification = notification.dismiss(); notificationRepository.findById.mockResolvedValue(dismissedNotification); const command: DismissNotificationCommand = { notificationId: 'n1', recipientId: 'driver-1', }; const result = await useCase.execute(command); expect(result.isOk()).toBe(true); expect(notificationRepository.update).not.toHaveBeenCalled(); const successResult = result.unwrap(); expect(successResult.wasAlreadyDismissed).toBe(true); }); it('returns CANNOT_DISMISS_REQUIRING_RESPONSE when notification requires response', async () => { const notification = Notification.create({ id: 'n1', recipientId: 'driver-1', type: 'system_announcement', title: 'Test', body: 'Body', channel: 'in_app', requiresResponse: true, }); notificationRepository.findById.mockResolvedValue(notification); const command: DismissNotificationCommand = { notificationId: 'n1', recipientId: 'driver-1', }; const result = await useCase.execute(command); expect(result.isErr()).toBe(true); const err = result.unwrapErr() as ApplicationErrorCode<'CANNOT_DISMISS_REQUIRING_RESPONSE', { message: string }>; expect(err.code).toBe('CANNOT_DISMISS_REQUIRING_RESPONSE'); }); });