manicanldes-backend/test/recovery-mailer.spec.ts

67 lines
2.1 KiB
TypeScript

import { createTransport } from 'nodemailer';
import { RecoveryMailer } from '../src/identity/recovery-mailer';
import { parseEnvironment } from '../src/config/environment';
jest.mock('nodemailer', () => ({ createTransport: jest.fn() }));
describe('recovery SMTP adapter', () => {
const sendMail = jest.fn();
const close = jest.fn();
const env = parseEnvironment({
DATABASE_URL: 'postgresql://localhost/mani',
SMTP_HOST: 'smtp.example.com',
SMTP_USER: 'user',
SMTP_PASSWORD: 'secret',
SMTP_FROM: 'support@example.com',
RECOVERY_URL: 'https://shop.example.com/reset',
});
beforeEach(() => {
jest.clearAllMocks();
jest
.mocked(createTransport)
.mockReturnValue({ sendMail, close } as unknown as ReturnType<
typeof createTransport
>);
});
it('fails explicitly when recovery is not configured', () => {
expect(() =>
new RecoveryMailer(
parseEnvironment({ DATABASE_URL: 'postgresql://localhost/mani' }),
).assertConfigured(),
).toThrow('not configured');
});
it('requires TLS and delivers a fragment-based reset link', async () => {
sendMail.mockResolvedValue({});
await new RecoveryMailer(env).send('person@example.com', 'token');
expect(createTransport).toHaveBeenCalledWith(
expect.objectContaining({
requireTLS: true,
secure: false,
disableFileAccess: true,
disableUrlAccess: true,
}),
);
expect(sendMail).toHaveBeenCalledWith(
expect.objectContaining({
to: 'person@example.com',
text: expect.stringContaining(
'https://shop.example.com/reset#token=token',
),
}),
);
expect(close).toHaveBeenCalled();
});
it('closes transport even when sending fails and supports implicit TLS', async () => {
sendMail.mockRejectedValue(new Error('delivery failed'));
await expect(
new RecoveryMailer({ ...env, SMTP_PORT: 465 }).send(
'person@example.com',
'token',
),
).rejects.toThrow();
expect(createTransport).toHaveBeenCalledWith(
expect.objectContaining({ secure: true }),
);
expect(close).toHaveBeenCalled();
});
});