import type { Logger, UseCaseOutputPort } from '@core/shared/application'; import { Result } from '@core/shared/application/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import { describe, expect, it, vi, type Mock } from 'vitest'; import type { ChannelPreference, NotificationPreference, TypePreference } from '../../domain/entities/NotificationPreference'; import type { INotificationPreferenceRepository } from '../../domain/repositories/INotificationPreferenceRepository'; import type { NotificationChannel, NotificationType } from '../../domain/types/NotificationTypes'; import { GetNotificationPreferencesQuery, SetDigestModeUseCase, UpdateChannelPreferenceUseCase, UpdateQuietHoursUseCase, UpdateTypePreferenceUseCase, type GetNotificationPreferencesInput, type GetNotificationPreferencesResult, type SetDigestModeCommand, type SetDigestModeResult, type UpdateChannelPreferenceCommand, type UpdateChannelPreferenceResult, type UpdateQuietHoursCommand, type UpdateQuietHoursResult, type UpdateTypePreferenceCommand, type UpdateTypePreferenceResult, } 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 output: UseCaseOutputPort & { present: Mock } = { present: vi.fn(), } as unknown as UseCaseOutputPort & { present: Mock }; const useCase = new GetNotificationPreferencesQuery( preferenceRepository as unknown as INotificationPreferenceRepository, output, 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); expect(output.present).toHaveBeenCalledWith({ preference }); }); it('UpdateChannelPreferenceUseCase updates channel preference', async () => { const preference = { updateChannel: vi.fn().mockReturnThis(), } as unknown as NotificationPreference; preferenceRepository.getOrCreateDefault.mockResolvedValue(preference); const output: UseCaseOutputPort & { present: Mock } = { present: vi.fn(), } as unknown as UseCaseOutputPort & { present: Mock }; const useCase = new UpdateChannelPreferenceUseCase( preferenceRepository as unknown as INotificationPreferenceRepository, output, 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); expect(output.present).toHaveBeenCalledWith({ driverId: 'driver-1', channel: 'email' }); }); it('UpdateTypePreferenceUseCase updates type preference', async () => { const preference = { updateTypePreference: vi.fn().mockReturnThis(), } as unknown as NotificationPreference; preferenceRepository.getOrCreateDefault.mockResolvedValue(preference); const output: UseCaseOutputPort & { present: Mock } = { present: vi.fn(), } as unknown as UseCaseOutputPort & { present: Mock }; const useCase = new UpdateTypePreferenceUseCase( preferenceRepository as unknown as INotificationPreferenceRepository, output, 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); expect(output.present).toHaveBeenCalledWith({ driverId: 'driver-1', type: '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 output: UseCaseOutputPort & { present: Mock } = { present: vi.fn(), } as unknown as UseCaseOutputPort & { present: Mock }; const useCase = new UpdateQuietHoursUseCase( preferenceRepository as unknown as INotificationPreferenceRepository, output, 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); expect(output.present).toHaveBeenCalledWith({ driverId: 'driver-1', startHour: 22, endHour: 7, }); }); it('UpdateQuietHoursUseCase returns error on invalid hours', async () => { const output: UseCaseOutputPort & { present: Mock } = { present: vi.fn(), } as unknown as UseCaseOutputPort & { present: Mock }; const useCase = new UpdateQuietHoursUseCase( preferenceRepository as unknown as INotificationPreferenceRepository, output, 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 output: UseCaseOutputPort & { present: Mock } = { present: vi.fn(), } as unknown as UseCaseOutputPort & { present: Mock }; const useCase = new SetDigestModeUseCase( preferenceRepository as unknown as INotificationPreferenceRepository, output, ); 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); expect(output.present).toHaveBeenCalledWith({ driverId: 'driver-1', enabled: true, frequencyHours: 4, }); }); it('SetDigestModeUseCase returns error on invalid frequency', async () => { const output: UseCaseOutputPort & { present: Mock } = { present: vi.fn(), } as unknown as UseCaseOutputPort & { present: Mock }; const useCase = new SetDigestModeUseCase( preferenceRepository as unknown as INotificationPreferenceRepository, output, ); 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'); }); });