petitspas/backend/src/modules/mail/mail.service.spec.ts
Julien Martin 9d54d9b19b feat(#112): email accusé resoumission aux parents
Après PATCH reprise-resoumettre (parent), envoi à chaque parent du
dossier : confirmation resoumission + rappel numero_dossier.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 17:26:23 +02:00

119 lines
4.2 KiB
TypeScript

import { Test, TestingModule } from '@nestjs/testing';
import { MailService, ValidationAccountEmailKind, isTransientSmtpError } from './mail.service';
import { AppConfigService } from '../config/config.service';
describe('isTransientSmtpError', () => {
it('détecte les erreurs SMTP temporaires (454, coupure auth, timeout)', () => {
expect(isTransientSmtpError(new Error('Invalid login: 454 4.7.0 Temporary authentication failure'))).toBe(true);
expect(isTransientSmtpError(new Error('Connection lost to authentication server'))).toBe(true);
expect(isTransientSmtpError(Object.assign(new Error('timeout'), { code: 'ETIMEDOUT' }))).toBe(true);
expect(isTransientSmtpError(Object.assign(new Error('auth failed'), { responseCode: 454 }))).toBe(true);
});
it('ignore les erreurs permanentes (mauvaise adresse, refus définitif)', () => {
expect(isTransientSmtpError(new Error('Invalid login: 535 Authentication failed'))).toBe(false);
expect(isTransientSmtpError(new Error('Mailbox unavailable'))).toBe(false);
});
});
describe('MailService (ticket #28)', () => {
let service: MailService;
let sendEmailSpy: jest.SpyInstance;
const mockConfigGet = jest.fn((key: string, defaultValue?: unknown) => {
const values: Record<string, unknown> = {
app_name: 'TestApp',
app_url: 'https://app.test/',
smtp_host: '127.0.0.1',
smtp_port: 1025,
smtp_secure: false,
smtp_auth_required: false,
smtp_user: '',
smtp_password: '',
email_from_name: 'Test',
email_from_address: 'noreply@test',
};
if (key in values) return values[key];
return defaultValue;
});
beforeEach(async () => {
jest.clearAllMocks();
const module: TestingModule = await Test.createTestingModule({
providers: [
MailService,
{
provide: AppConfigService,
useValue: { get: mockConfigGet },
},
],
}).compile();
service = module.get<MailService>(MailService);
sendEmailSpy = jest.spyOn(service, 'sendEmail').mockResolvedValue(undefined);
});
afterEach(() => {
sendEmailSpy.mockRestore();
});
it.each<[ValidationAccountEmailKind, string]>([
['parent', 'Compte parent validé'],
['am', 'Inscription validée'],
])('sendValidatedAccountPasswordSetupEmail (%s) utilise Handlebars + lien token', async (kind, subjectPart) => {
const token = '11111111-2222-3333-4444-555555555555';
await service.sendValidatedAccountPasswordSetupEmail(
{
email: 'user@test.fr',
prenom: 'Jean',
nom: 'Dupont',
token,
numeroDossier: '2025-000001',
},
kind,
);
expect(sendEmailSpy).toHaveBeenCalledTimes(1);
const [, subject, html] = sendEmailSpy.mock.calls[0];
expect(subject).toContain('TestApp');
expect(subject).toContain(subjectPart);
expect(html).toContain('Jean');
expect(html).toContain('Dupont');
expect(html).toContain('2025-000001');
expect(html).toContain(`https://app.test/create-password?token=${encodeURIComponent(token)}`);
});
it('sendPasswordResetEmail utilise Handlebars + lien /reset-password', async () => {
const token = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee';
await service.sendPasswordResetEmail({
email: 'u@test.fr',
prenom: 'Marie',
nom: 'Curie',
token,
});
expect(sendEmailSpy).toHaveBeenCalledTimes(1);
const [, subject, html] = sendEmailSpy.mock.calls[0];
expect(subject).toContain('Réinitialisation');
expect(html).toContain('Marie');
expect(html).toContain(`https://app.test/reset-password?token=${encodeURIComponent(token)}`);
expect(html).not.toContain('create-password');
});
it('sendResoumissionPendingEmail — accusé resoumission avec n° dossier', async () => {
await service.sendResoumissionPendingEmail(
'parent@test.fr',
'Claire',
'MARTIN',
'2026-000024',
);
expect(sendEmailSpy).toHaveBeenCalledTimes(1);
const [to, subject, html] = sendEmailSpy.mock.calls[0];
expect(to).toBe('parent@test.fr');
expect(subject).toContain('resoumis');
expect(subject).toContain('2026-000024');
expect(html).toContain('Claire');
expect(html).toContain('2026-000024');
expect(html).toContain('en attente de validation');
});
});