From 2814d5520623efe5f48c26f639d3ed6cc5f0d8d2 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Mon, 15 Dec 2025 20:17:22 -0800 Subject: Add email integration --- test/integrations.test.ts | 144 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 143 insertions(+), 1 deletion(-) (limited to 'test/integrations.test.ts') diff --git a/test/integrations.test.ts b/test/integrations.test.ts index 310945a..72c653d 100644 --- a/test/integrations.test.ts +++ b/test/integrations.test.ts @@ -2,7 +2,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { verifyHCaptcha } from '../src/integrations/hcaptcha.js'; import { sendNtfyNotification } from '../src/integrations/ntfy.js'; -import type { NtfyConfig, StoredRequest } from '../src/types/index.js'; +import { sendEmailNotification } from '../src/integrations/email.js'; +import type { EmailConfig, NtfyConfig, StoredRequest } from '../src/types/index.js'; describe('verifyHCaptcha', () => { beforeEach(() => { @@ -126,3 +127,144 @@ describe('sendNtfyNotification', () => { expect(result.left().get().message).toBe('ntfy notification failed: Unauthorized'); }); }); + +describe('sendEmailNotification', () => { + beforeEach(() => { + vi.mock('nodemailer', () => ({ + default: { + createTransport: vi.fn(() => ({ + sendMail: vi.fn().mockResolvedValue(undefined), + })), + }, + })); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('is a no-op when not enabled or misconfigured', async () => { + const { default: nodemailer } = await import('nodemailer'); + + const config: EmailConfig = { enabled: false }; + const request: StoredRequest = { + timestamp: 1, + uuid: 'uuid', + routeName: 'route1', + method: 'POST', + headers: {}, + body: {}, + }; + + const result = await sendEmailNotification(config, request); + expect(result.left().present()).toBe(false); + expect(nodemailer.createTransport).not.toHaveBeenCalled(); + }); + + it('sends an email with the configured settings', async () => { + const { default: nodemailer } = await import('nodemailer'); + const sendMailMock = vi.fn().mockResolvedValue(undefined); + vi.mocked(nodemailer.createTransport).mockReturnValue({ + sendMail: sendMailMock, + } as any); + + const config: EmailConfig = { + enabled: true, + to: 'admin@example.com', + from: 'webhook@example.com', + host: 'smtp.example.com', + port: 587, + secure: true, + username: 'user', + password: 'pass', + subject: 'Test Subject', + includeBody: true, + includeHeaders: false, + }; + const request: StoredRequest = { + timestamp: Date.parse('2020-01-01T00:00:00.000Z'), + uuid: 'test-uuid', + routeName: 'test-route', + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: { test: 'data' }, + }; + + const result = await sendEmailNotification(config, request); + expect(result.left().present()).toBe(false); + + expect(nodemailer.createTransport).toHaveBeenCalledWith({ + host: 'smtp.example.com', + port: 587, + secure: true, + auth: { + user: 'user', + pass: 'pass', + }, + }); + + expect(sendMailMock).toHaveBeenCalledTimes(1); + const mailOptions = sendMailMock.mock.calls[0][0]; + expect(mailOptions.from).toBe('webhook@example.com'); + expect(mailOptions.to).toBe('admin@example.com'); + expect(mailOptions.subject).toBe('Test Subject'); + expect(mailOptions.html).toContain('test-route'); + expect(mailOptions.html).toContain('POST'); + expect(mailOptions.html).toContain('2020-01-01T00:00:00.000Z'); + expect(mailOptions.html).toContain('test-uuid'); + expect(mailOptions.html).toContain('"test": "data"'); + }); + + it('uses default subject when not configured', async () => { + const { default: nodemailer } = await import('nodemailer'); + const sendMailMock = vi.fn().mockResolvedValue(undefined); + vi.mocked(nodemailer.createTransport).mockReturnValue({ + sendMail: sendMailMock, + } as any); + + const config: EmailConfig = { + enabled: true, + to: 'admin@example.com', + from: 'webhook@example.com', + }; + const request: StoredRequest = { + timestamp: 1, + uuid: 'uuid', + routeName: 'my-route', + method: 'POST', + headers: {}, + body: {}, + }; + + await sendEmailNotification(config, request); + + const mailOptions = sendMailMock.mock.calls[0][0]; + expect(mailOptions.subject).toBe('Webhook received: my-route'); + }); + + it('returns an error when email sending fails', async () => { + const { default: nodemailer } = await import('nodemailer'); + const sendMailMock = vi.fn().mockRejectedValue(new Error('SMTP error')); + vi.mocked(nodemailer.createTransport).mockReturnValue({ + sendMail: sendMailMock, + } as any); + + const config: EmailConfig = { + enabled: true, + to: 'admin@example.com', + from: 'webhook@example.com', + }; + const request: StoredRequest = { + timestamp: 1, + uuid: 'uuid', + routeName: 'route1', + method: 'POST', + headers: {}, + body: {}, + }; + + const result = await sendEmailNotification(config, request); + expect(result.left().present()).toBe(true); + expect(result.left().get().message).toBe('SMTP error'); + }); +}); -- cgit v1.2.3-70-g09d2