import { beforeEach, describe, expect, it, vi } from 'vitest'; // Boundaries the sender depends on — mock the I/O edges, exercise the real // wiring (recipient resolution → template → per-recipient send). vi.mock('@/lib/email', () => ({ sendEmail: vi.fn().mockResolvedValue(undefined) })); vi.mock('@/lib/email/branding-resolver', () => ({ getBrandingShell: vi.fn().mockResolvedValue(null), })); vi.mock('@/lib/services/notification-recipients', () => ({ resolveNotificationRecipients: vi.fn(), })); import { sendEmail } from '@/lib/email'; import { resolveNotificationRecipients } from '@/lib/services/notification-recipients'; import { sendSigningStatusNotification } from '@/lib/services/document-signing-emails.service'; const mockSendEmail = vi.mocked(sendEmail); const mockResolve = vi.mocked(resolveNotificationRecipients); const baseArgs = { portId: 'port-1', portName: 'Port Nimara', event: 'signed' as const, documentLabel: 'Expression of Interest', clientName: 'Jane Doe', crmUrl: 'https://crm.portnimara.com/port-nimara/documents/abc', signerName: 'Jane Doe', signerRole: 'client' as const, signedCount: 1, totalCount: 3, }; describe('sendSigningStatusNotification', () => { beforeEach(() => { mockSendEmail.mockClear(); mockResolve.mockReset(); }); it('emails every configured recipient when a signer signs', async () => { mockResolve.mockResolvedValue(['admin@portnimara.com', 'sales@portnimara.com']); await sendSigningStatusNotification(baseArgs); // Resolves from the signing list, falling back to the reply-to address. expect(mockResolve).toHaveBeenCalledWith( 'port-1', 'signing_notification_recipients', 'email_reply_to', ); expect(mockSendEmail).toHaveBeenCalledTimes(2); const recipients = mockSendEmail.mock.calls.map((c) => c[0]); expect(recipients).toContain('admin@portnimara.com'); expect(recipients).toContain('sales@portnimara.com'); // Subject reflects who signed. const subject = mockSendEmail.mock.calls[0]?.[1] as string; expect(subject).toContain('Jane Doe'); // portId threaded through so per-port From + redirect apply. expect(mockSendEmail.mock.calls[0]?.[5]).toBe('port-1'); }); it('sends nothing when no recipients are configured or resolvable', async () => { mockResolve.mockResolvedValue([]); await sendSigningStatusNotification(baseArgs); expect(mockSendEmail).not.toHaveBeenCalled(); }); it('uses the completion subject for the completed event', async () => { mockResolve.mockResolvedValue(['sales@portnimara.com']); await sendSigningStatusNotification({ ...baseArgs, event: 'completed', signerName: null, }); const subject = mockSendEmail.mock.calls[0]?.[1] as string; expect(subject.toLowerCase()).toContain('fully signed'); expect(subject).toContain('Jane Doe'); }); });