67 lines
2.1 KiB
TypeScript
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();
|
|
});
|
|
});
|