import { Logger } from '@nestjs/common'; import { EmailAdapter } from './email.adapter'; /** * Tous les emails doivent partir de l'adresse SMTP_FROM, la seule validee chez * le relais SMTP (Brevo). Les invitations et les demandes aux transporteurs * partaient d'adresses codees en dur et etaient refusees. */ function buildAdapter(smtpFrom = 'noreply@xpeditis.com') { const settings: Record = { SMTP_FROM: smtpFrom, APP_URL: 'https://app.preprod.xpeditis.com', }; const config = { get: jest.fn((key: string, fallback?: unknown) => settings[key] ?? fallback) }; const templates = { renderInvitationWithToken: jest.fn(async () => '

invitation

'), renderCsvBookingRequest: jest.fn(async () => '

demande

'), renderUserInvitation: jest.fn(async () => '

compte

'), renderPasswordResetEmail: jest.fn(async () => '

reset

'), }; const adapter = new EmailAdapter(config as never, templates as never); // Parametre type : sans lui, Jest infere un appel sans argument et // `mock.calls[0][0]` ne compile pas. const sendMail = jest.fn(async (_mail: { from: string; to: string }) => ({ messageId: 'm-1', accepted: ['x'], rejected: [], })); (adapter as unknown as { transporter: { sendMail: typeof sendMail } }).transporter = { sendMail }; return { adapter, sendMail }; } const sentFrom = (sendMail: jest.Mock) => (sendMail.mock.calls[0][0] as { from: string }).from; describe('EmailAdapter — expediteur', () => { beforeAll(() => { jest.spyOn(Logger.prototype, 'log').mockImplementation(() => undefined); }); it("envoie l'invitation depuis SMTP_FROM, avec le nom de l'equipe", async () => { const { adapter, sendMail } = buildAdapter(); await adapter.sendInvitationWithToken( 'nouveau@client.test', 'Marie', 'Dupont', 'Acme', 'Paul Martin', 'https://app/register?token=t', new Date('2026-09-21T10:00:00Z') ); expect(sentFrom(sendMail)).toBe('"Équipe Xpeditis" '); }); it('envoie la demande au transporteur depuis SMTP_FROM', async () => { const { adapter, sendMail } = buildAdapter(); await adapter.sendCsvBookingRequest('booking@ssc.test', { bookingId: 'b-1', bookingNumber: 'XPD-2026-AAAAAA', origin: 'FRLEH', destination: 'EGEDK', volumeCBM: 2.4, weightKG: 850, palletCount: 2, priceUSD: 200, priceEUR: 180, primaryCurrency: 'EUR', transitDays: 11, containerType: 'LCL', documents: [], confirmationToken: 'token', }); expect(sentFrom(sendMail)).toBe('"Xpeditis Bookings" '); expect((sendMail.mock.calls[0][0] as { to: string }).to).toBe('booking@ssc.test'); }); it("suit l'adresse configuree, pour tous les types d'email", async () => { const { adapter, sendMail } = buildAdapter('contact@mondomaine.fr'); await adapter.sendUserInvitation('a@b.test', 'Acme', 'Paul', 'Temp-1234'); await adapter.sendPasswordResetEmail('a@b.test', 'token'); await adapter.send({ to: 'a@b.test', subject: 'Test', html: '

t

' }); const froms = sendMail.mock.calls.map(call => (call[0] as { from: string }).from); expect(froms).toEqual([ '"Équipe Xpeditis" ', '"Xpeditis Sécurité" ', '"Xpeditis" ', ]); }); });