Files
events-hub/backend/tests/settings.test.js
joshuaandClaude Opus 5.5 613a4450cb SMTP settings on the Settings page, with a test (1.2.0)
SMTP server, port, user, password and from address are now Hub settings in
the database instead of SMTP_* in .env. The password is encrypted with
HUB_ENCRYPTION_KEY and never sent to the browser; a blank field keeps it.
POST /api/admin/settings/smtp-test logs in with the form's values (saved or
not) and sends a test email, returning the server's reason on failure; it is
rate limited and audited. On first start an existing SMTP_* setup is copied
into Settings once. The audit log no longer records the SMTP password.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ls1vMaqhrehXcHJe32jLL9
2026-10-28 01:26:41 +02:00

57 lines
2.9 KiB
JavaScript

// SMTP settings in the database: the password is encrypted, never sent to the page, and the
// old SMTP_* in .env are copied over once.
const mockRows = new Map();
jest.mock('../src/config/db', () => ({
setting: {
findMany: async () => [...mockRows].map(([key, value]) => ({ key, value })),
upsert: async ({ where, update }) => { mockRows.set(where.key, update.value); },
},
}));
process.env.HUB_ENCRYPTION_KEY = 'test-encryption-key-that-is-long-enough-123';
const { getSettings, setSettings, smtpSettings, importSmtpFromEnv } = require('../src/utils/settings');
const { testSmtp } = require('../src/utils/mail');
beforeEach(() => {
mockRows.clear();
for (const k of ['SMTP_HOST', 'SMTP_PORT', 'SMTP_USER', 'SMTP_PASS', 'SMTP_FROM']) delete process.env[k];
});
test('the password is stored encrypted and only shown as saved', async () => {
const shown = await setSettings({ smtp_host: 'mail.example.com', smtp_user: 'hub', smtp_pass: 'p@ss' });
expect(mockRows.get('smtp_pass')).toMatch(/^v1:/);
expect(shown.smtp_pass).toBeUndefined();
expect(shown.smtp_pass_set).toBe('true');
expect((await smtpSettings()).pass).toBe('p@ss');
});
test('a blank password keeps the saved one; clearing removes it', async () => {
await setSettings({ smtp_host: 'mail.example.com', smtp_pass: 'p@ss' });
await setSettings({ smtp_host: 'smtp.example.com', smtp_pass: '' });
expect((await smtpSettings()).pass).toBe('p@ss');
await setSettings({ smtp_pass_clear: true });
expect((await smtpSettings()).pass).toBe('');
expect((await getSettings()).smtp_pass_set).toBe('');
});
test('form values override the saved ones for a test, falling back to the saved password', async () => {
await setSettings({ smtp_host: 'mail.example.com', smtp_port: '587', smtp_pass: 'p@ss' });
const smtp = await smtpSettings({ smtp_host: ' other.example.com ', smtp_port: '465', smtp_pass: '' });
expect(smtp).toMatchObject({ host: 'other.example.com', port: 465, pass: 'p@ss' });
expect((await smtpSettings({ smtp_pass_clear: true })).pass).toBe('');
});
test('SMTP_* in .env are copied once, and never over saved settings', async () => {
Object.assign(process.env, { SMTP_HOST: 'env.example.com', SMTP_PORT: '465', SMTP_USER: 'u', SMTP_PASS: 'envpass', SMTP_FROM: 'hub@example.com' });
expect(await importSmtpFromEnv()).toBe(true);
expect(await smtpSettings()).toMatchObject({ host: 'env.example.com', port: 465, user: 'u', pass: 'envpass', from: 'hub@example.com' });
await setSettings({ smtp_host: 'changed.example.com' });
expect(await importSmtpFromEnv()).toBe(false);
expect((await smtpSettings()).host).toBe('changed.example.com');
});
test('the SMTP test reports why it failed', async () => {
await expect(testSmtp({ smtp_host: '' }, 'a@example.com')).rejects.toThrow('Enter the SMTP server first.');
await expect(testSmtp({ smtp_host: '127.0.0.1', smtp_port: '1' }, 'a@example.com')).rejects.toThrow(/ECONNREFUSED|connect/i);
});