import 'reflect-metadata'; import { NotificationType, type NotifyInput } from '@edr/types'; import type { ChatConfig } from '../../config/chat.config'; import { ChatBridgeService } from './chat-bridge.service'; import type { MatrixClient } from './matrix.client'; const config: ChatConfig = { enabled: true, baseUrl: 'https://matrix.test', publicBaseUrl: 'https://matrix.test', webUrl: 'https://chat.test', serverName: 'matrix.test', jwtSecret: 'secret', adminToken: 'syt_whatever', }; function harness(overrides: Partial = {}) { const matrix = { ensureRoom: jest.fn(async (alias: string) => `!${alias}:matrix.test`), sendMessage: jest.fn( async (_roomId: string, _body: string, _html?: string) => undefined, ), }; const service = new ChatBridgeService( { ...config, ...overrides }, matrix as unknown as MatrixClient, ); return { service, matrix }; } const notification = (type: NotificationType): NotifyInput => ({ type, title: 'Booking BK-1', body: 'needs review' }) as unknown as NotifyInput; describe('ChatBridgeService', () => { it('posts every notification type into #freight-alerts', async () => { // This used to route REQUEST_SUBMITTED and CLEARANCE_REVIEW to a hardcoded // `dept-operation` alias, but the reconcile derives dept aliases from the // IAM position key (`edr_freight_app/opn` shaped), so nothing it created // ever matched. The bridge made its own empty room and posted there, where // no employee was a member. const { service, matrix } = harness(); for (const type of [ NotificationType.REQUEST_SUBMITTED, NotificationType.CLEARANCE_REVIEW, NotificationType.GENERIC, ]) { await service.bridge(notification(type)); } expect(new Set(matrix.ensureRoom.mock.calls.map(([alias]) => alias))).toEqual( new Set(['freight-alerts']), ); expect(matrix.sendMessage).toHaveBeenCalledTimes(3); }); it('does nothing at all when chat is switched off', async () => { const { service, matrix } = harness({ enabled: false }); await service.bridge(notification(NotificationType.GENERIC)); expect(matrix.ensureRoom).not.toHaveBeenCalled(); expect(matrix.sendMessage).not.toHaveBeenCalled(); }); it('never lets a chat failure escape into the notification that triggered it', async () => { // Same contract as NotificationInboxService.notify(): bridging is // best-effort and must not roll back the caller's transaction. const { service, matrix } = harness(); matrix.ensureRoom.mockRejectedValueOnce(new Error('Matrix POST ... -> 429')); await expect( service.bridge(notification(NotificationType.GENERIC)), ).resolves.toBeUndefined(); }); });