import { CalendarSyncService } from './calendar-sync.service'; import { CalendarSyncEventHandlers } from './calendar-sync.handlers'; import type { AnyDomainEventEnvelope, DomainEventType, } from '../domain-events/domain-event-types'; import { domainEventHandlerMetadata } from '../domain-events/on-domain-event.decorator'; import { Prisma } from '../../generated/prisma'; describe('CalendarSyncEventHandlers', () => { let handlers: CalendarSyncEventHandlers; let calendarSync: { enqueueIn: jest.Mock }; const tx = {} as Prisma.TransactionClient; const envelope = ( type: T, aggregateId: string, payload: Record, ) => ({ type, aggregateId, payload }) as unknown as AnyDomainEventEnvelope; beforeEach(() => { calendarSync = { enqueueIn: jest.fn().mockResolvedValue(undefined) }; handlers = new CalendarSyncEventHandlers( calendarSync as unknown as CalendarSyncService, ); }); it.each([ ['inspection', 'INSPECTION', 'InspectionRecorded'], ['inspection', 'INSPECTION', 'InspectionCorrected'], ['treatment', 'TREATMENT', 'TreatmentRecorded'], ['treatment', 'TREATMENT', 'TreatmentCorrected'], ['feeding', 'FEEDING', 'FeedingRecorded'], ['feeding', 'FEEDING', 'FeedingCorrected'], ['harvest', 'HARVEST', 'HarvestRecorded'], ['harvest', 'HARVEST', 'HarvestCorrected'], ] as const)( '%s handler enqueues %s on %s through tx', async (method, sourceType, type) => { await handlers[method](envelope(type, 'src-1', {}) as never, tx); expect(calendarSync.enqueueIn).toHaveBeenCalledWith( tx, sourceType, 'src-1', ); }, ); it.each(['TaskCreated', 'TaskUpdated', 'TaskCompleted'] as const)( 'task handler enqueues the task on %s', async (type) => { await handlers.task( envelope(type, 'task-1', { dueDate: '2027-05-01' }), tx, ); expect(calendarSync.enqueueIn).toHaveBeenCalledWith(tx, 'TASK', 'task-1'); }, ); it('QueenAssigned enqueues QUEEN_ASSIGNMENT with the history id', async () => { await handlers.queenAssignment( envelope('QueenAssigned', 'queen-1', { colonyId: 'colony-1', historyId: 'history-1', assignedOn: '2026-10-14', previousColonyId: null, }), tx, ); expect(calendarSync.enqueueIn).toHaveBeenCalledWith( tx, 'QUEEN_ASSIGNMENT', 'history-1', ); }); it('TaskCreated without a due date enqueues nothing', async () => { await handlers.task( envelope('TaskCreated', 'task-1', { dueDate: null }), tx, ); expect(calendarSync.enqueueIn).not.toHaveBeenCalled(); }); it('TaskUpdated without a due date still enqueues, so the worker removes the event', async () => { await handlers.task( envelope('TaskUpdated', 'task-1', { dueDate: null, status: 'OPEN' }), tx, ); expect(calendarSync.enqueueIn).toHaveBeenCalledWith(tx, 'TASK', 'task-1'); }); it('lets an enqueue failure propagate so the delivery is retried', async () => { calendarSync.enqueueIn.mockRejectedValue(new Error('db down')); await expect( handlers.inspection(envelope('InspectionRecorded', 'i-1', {}), tx), ).rejects.toThrow('db down'); }); it('registers the stable handler ids and event types', () => { const registered = (method: keyof CalendarSyncEventHandlers) => domainEventHandlerMetadata( Object.getOwnPropertyDescriptor( CalendarSyncEventHandlers.prototype, method, )?.value as object, ); expect(registered('inspection')).toEqual([ { types: ['InspectionRecorded', 'InspectionCorrected'], handlerId: 'calendar-sync.inspection', }, ]); expect(registered('treatment')[0].handlerId).toBe( 'calendar-sync.treatment', ); expect(registered('feeding')[0].handlerId).toBe('calendar-sync.feeding'); expect(registered('harvest')[0].handlerId).toBe('calendar-sync.harvest'); expect(registered('queenAssignment')).toEqual([ { types: ['QueenAssigned'], handlerId: 'calendar-sync.queen-assignment', }, ]); expect(registered('task')).toEqual([ { types: ['TaskCreated', 'TaskUpdated', 'TaskCompleted'], handlerId: 'calendar-sync.task', }, ]); }); });