98 lines
3.4 KiB
TypeScript
98 lines
3.4 KiB
TypeScript
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<string, string> = {
|
|
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 () => '<p>invitation</p>'),
|
|
renderCsvBookingRequest: jest.fn(async () => '<p>demande</p>'),
|
|
renderUserInvitation: jest.fn(async () => '<p>compte</p>'),
|
|
renderPasswordResetEmail: jest.fn(async () => '<p>reset</p>'),
|
|
};
|
|
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" <noreply@xpeditis.com>');
|
|
});
|
|
|
|
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" <noreply@xpeditis.com>');
|
|
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: '<p>t</p>' });
|
|
|
|
const froms = sendMail.mock.calls.map(call => (call[0] as { from: string }).from);
|
|
expect(froms).toEqual([
|
|
'"Équipe Xpeditis" <contact@mondomaine.fr>',
|
|
'"Xpeditis Sécurité" <contact@mondomaine.fr>',
|
|
'"Xpeditis" <contact@mondomaine.fr>',
|
|
]);
|
|
});
|
|
});
|