// common.mail relays one email through the Salesforce org (see // src/common/common.mail.ts). These tests mock the shared Salesforce client so // no network is touched, and check: the payload shape, base64 attachment // encoding, the attachment size cap, the retry-once-on-transient-failure // behaviour, and that a delivery failure is swallowed (returns false, never // throws). jest.mock('../../src/common/salesforce.client'); jest.mock('../../src/middleware/logger', () => ({ __esModule: true, default: {info: jest.fn(), warn: jest.fn(), error: jest.fn()} })); import {MailService} from '../../src/common/common.mail'; import {salesforceApexRestPost, salesforceEnabled} from '../../src/common/salesforce.client'; const mockPost = salesforceApexRestPost as jest.Mock; const mockEnabled = salesforceEnabled as jest.Mock; const httpError = (status: number, body?: any): any => { const err: any = new Error('request failed with ' + status); err.response = {status, data: body}; return err; }; beforeEach(() => { jest.clearAllMocks(); mockEnabled.mockReturnValue(true); mockPost.mockResolvedValue({status: 'SENT'}); }); describe('MailService.sendMail', () => { it('returns false without a callout when Salesforce is disabled', async () => { mockEnabled.mockReturnValue(false); const result = await MailService.sendMail('guest@example.com', 'Hi', 'Hallo'); expect(result).toBe(false); expect(mockPost).not.toHaveBeenCalled(); }); it('posts the email to the Apex REST endpoint and returns true on success', async () => { const result = await MailService.sendMail('guest@example.com', 'Bestätigung', 'Hallo', {html: '

Hallo

'}); expect(result).toBe(true); expect(mockPost).toHaveBeenCalledWith('/services/apexrest/email/send', { to: 'guest@example.com', subject: 'Bestätigung', textBody: 'Hallo', htmlBody: '

Hallo

', attachments: [] }); }); it('sends htmlBody as null when no HTML is given', async () => { await MailService.sendMail('guest@example.com', 'Hi', 'Hallo'); expect(mockPost).toHaveBeenCalledWith('/services/apexrest/email/send', expect.objectContaining({htmlBody: null})); }); it('base64-encodes attachments', async () => { await MailService.sendMail('guest@example.com', 'Hi', 'Hallo', { attachments: [{filename: 'konzert.ics', content: 'BEGIN:VCALENDAR', contentType: 'text/calendar'}] }); expect(mockPost).toHaveBeenCalledWith( '/services/apexrest/email/send', expect.objectContaining({ attachments: [{ filename: 'konzert.ics', contentType: 'text/calendar', contentBase64: Buffer.from('BEGIN:VCALENDAR', 'utf-8').toString('base64') }] }) ); }); it('rejects an attachment over the size cap without sending', async () => { const huge = Buffer.alloc(3 * 1024 * 1024 + 1); const result = await MailService.sendMail('guest@example.com', 'Hi', 'Hallo', { attachments: [{filename: 'big.pdf', content: huge}] }); expect(result).toBe(false); expect(mockPost).not.toHaveBeenCalled(); }); it('retries once on a 5xx and returns false when the retry also fails', async () => { mockPost.mockRejectedValue(httpError(503)); const result = await MailService.sendMail('guest@example.com', 'Hi', 'Hallo'); expect(result).toBe(false); expect(mockPost).toHaveBeenCalledTimes(2); }); it('retries once on a network error (no response) then succeeds', async () => { mockPost.mockRejectedValueOnce(new Error('socket hang up')).mockResolvedValueOnce({status: 'SENT'}); const result = await MailService.sendMail('guest@example.com', 'Hi', 'Hallo'); expect(result).toBe(true); expect(mockPost).toHaveBeenCalledTimes(2); }); it('does not retry on a 4xx (e.g. the 429 limit response) and returns false', async () => { mockPost.mockRejectedValue(httpError(429, {errorCode: 'LIMIT_REACHED'})); const result = await MailService.sendMail('guest@example.com', 'Hi', 'Hallo'); expect(result).toBe(false); expect(mockPost).toHaveBeenCalledTimes(1); }); });