- GET/PATCH reprise-dossier enrichis (parents, enfants, motivation, fiche AM) - Front: lien mail, modale identify login, wizards préremplis, PATCH complet - Email accusé resoumission aux parents avec n° de dossier - Fixes préremplissage AM (dates, places, ValueKey étape 2) Co-authored-by: Cursor <cursoragent@cursor.com>
119 lines
4.2 KiB
TypeScript
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');
|
|
});
|
|
});
|