import type { Logger } from '@core/shared/domain/Logger'; import { Result } from '@core/shared/domain/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest'; import type { ChannelPreference, NotificationPreference, TypePreference } from '../../domain/entities/NotificationPreference'; import { NotificationPreferenceRepository } from '../../domain/repositories/NotificationPreferenceRepository'; import type { NotificationChannel, NotificationType } from '../../domain/types/NotificationTypes'; import { GetNotificationPreferencesQuery, SetDigestModeUseCase, UpdateChannelPreferenceUseCase, UpdateQuietHoursUseCase, UpdateTypePreferenceUseCase, type GetNotificationPreferencesInput, type SetDigestModeCommand, type UpdateChannelPreferenceCommand, type UpdateQuietHoursCommand, type UpdateTypePreferenceCommand, } from './NotificationPreferencesUseCases'; describe('NotificationPreferencesUseCases', () => { let preferenceRepository: { getOrCreateDefault: Mock; save: Mock; }; let logger: Logger; beforeEach(() => { preferenceRepository = { getOrCreateDefault: vi.fn(), save: vi.fn(), }; logger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), } as unknown as Logger; }); it('GetNotificationPreferencesQuery returns preferences from repository', async () => { const preference = { id: 'pref-1', } as unknown as NotificationPreference; preferenceRepository.getOrCreateDefault.mockResolvedValue(preference); const useCase = new GetNotificationPreferencesQuery( preferenceRepository as unknown as NotificationPreferenceRepository, logger, ); const input: GetNotificationPreferencesInput = { driverId: 'driver-1' }; const result = await useCase.execute(input); expect(preferenceRepository.getOrCreateDefault).toHaveBeenCalledWith('driver-1'); expect(result).toBeInstanceOf(Result); expect(result.isOk()).toBe(true); const successResult = result.unwrap(); expect(successResult.preference).toEqual(preference); }); it('UpdateChannelPreferenceUseCase updates channel preference', async () => { const preference = { updateChannel: vi.fn().mockReturnThis(), } as unknown as NotificationPreference; preferenceRepository.getOrCreateDefault.mockResolvedValue(preference); const useCase = new UpdateChannelPreferenceUseCase( preferenceRepository as unknown as NotificationPreferenceRepository, logger, ); const command: UpdateChannelPreferenceCommand = { driverId: 'driver-1', channel: 'email' as NotificationChannel, preference: { enabled: true } as ChannelPreference, }; const result = await useCase.execute(command); expect(result.isOk()).toBe(true); expect(preference.updateChannel).toHaveBeenCalled(); expect(preferenceRepository.save).toHaveBeenCalledWith(preference); const successResult = result.unwrap(); expect(successResult.driverId).toBe('driver-1'); expect(successResult.channel).toBe('email'); }); it('UpdateTypePreferenceUseCase updates type preference', async () => { const preference = { updateTypePreference: vi.fn().mockReturnThis(), } as unknown as NotificationPreference; preferenceRepository.getOrCreateDefault.mockResolvedValue(preference); const useCase = new UpdateTypePreferenceUseCase( preferenceRepository as unknown as NotificationPreferenceRepository, logger, ); const command: UpdateTypePreferenceCommand = { driverId: 'driver-1', type: 'system_announcement' as NotificationType, preference: { enabled: true } as TypePreference, }; const result = await useCase.execute(command); expect(result.isOk()).toBe(true); expect(preference.updateTypePreference).toHaveBeenCalled(); expect(preferenceRepository.save).toHaveBeenCalledWith(preference); const successResult = result.unwrap(); expect(successResult.driverId).toBe('driver-1'); expect(successResult.type).toBe('system_announcement'); }); it('UpdateQuietHoursUseCase validates hours and updates preferences', async () => { const preference = { updateQuietHours: vi.fn().mockReturnThis(), } as unknown as NotificationPreference; preferenceRepository.getOrCreateDefault.mockResolvedValue(preference); const useCase = new UpdateQuietHoursUseCase( preferenceRepository as unknown as NotificationPreferenceRepository, logger, ); const command: UpdateQuietHoursCommand = { driverId: 'driver-1', startHour: 22, endHour: 7, }; const result = await useCase.execute(command); expect(result.isOk()).toBe(true); expect(preference.updateQuietHours).toHaveBeenCalledWith(22, 7); expect(preferenceRepository.save).toHaveBeenCalledWith(preference); const successResult = result.unwrap(); expect(successResult.driverId).toBe('driver-1'); expect(successResult.startHour).toBe(22); expect(successResult.endHour).toBe(7); }); it('UpdateQuietHoursUseCase returns error on invalid hours', async () => { const useCase = new UpdateQuietHoursUseCase( preferenceRepository as unknown as NotificationPreferenceRepository, logger, ); const badStart: UpdateQuietHoursCommand = { driverId: 'd1', startHour: -1, endHour: 10 }; const result1 = await useCase.execute(badStart); expect(result1.isErr()).toBe(true); const err1 = result1.unwrapErr() as ApplicationErrorCode<'INVALID_START_HOUR', { message: string }>; expect(err1.code).toBe('INVALID_START_HOUR'); const badEnd: UpdateQuietHoursCommand = { driverId: 'd1', startHour: 10, endHour: 24 }; const result2 = await useCase.execute(badEnd); expect(result2.isErr()).toBe(true); const err2 = result2.unwrapErr() as ApplicationErrorCode<'INVALID_END_HOUR', { message: string }>; expect(err2.code).toBe('INVALID_END_HOUR'); }); it('SetDigestModeUseCase sets digest mode with valid frequency', async () => { const preference = { setDigestMode: vi.fn().mockReturnThis(), } as unknown as NotificationPreference; preferenceRepository.getOrCreateDefault.mockResolvedValue(preference); const useCase = new SetDigestModeUseCase( preferenceRepository as unknown as NotificationPreferenceRepository, ); const command: SetDigestModeCommand = { driverId: 'driver-1', enabled: true, frequencyHours: 4, }; const result = await useCase.execute(command); expect(result.isOk()).toBe(true); expect(preference.setDigestMode).toHaveBeenCalledWith(true, 4); expect(preferenceRepository.save).toHaveBeenCalledWith(preference); const successResult = result.unwrap(); expect(successResult.driverId).toBe('driver-1'); expect(successResult.enabled).toBe(true); expect(successResult.frequencyHours).toBe(4); }); it('SetDigestModeUseCase returns error on invalid frequency', async () => { const useCase = new SetDigestModeUseCase( preferenceRepository as unknown as NotificationPreferenceRepository, ); const command: SetDigestModeCommand = { driverId: 'driver-1', enabled: true, frequencyHours: 0, }; const result = await useCase.execute(command); expect(result.isErr()).toBe(true); const err = result.unwrapErr() as ApplicationErrorCode<'INVALID_FREQUENCY', { message: string }>; expect(err.code).toBe('INVALID_FREQUENCY'); }); });