import { EmailService, EmailOptions } from '../../../src/Application/Services/EmailService'; import * as nodemailer from 'nodemailer'; import * as fs from 'fs'; // Mock nodemailer jest.mock('nodemailer'); jest.mock('fs'); // Mock logger jest.mock('../../../src/Application/Services/Logger', () => ({ logError: jest.fn(), logAuth: jest.fn(), logStartup: jest.fn(), })); describe('EmailService', () => { let emailService: EmailService; let mockTransporter: jest.Mocked; let mockCreateTransporter: jest.MockedFunction; beforeEach(() => { jest.clearAllMocks(); // Mock nodemailer.createTransporter mockTransporter = { sendMail: jest.fn(), } as any; mockCreateTransporter = nodemailer.createTransport as jest.MockedFunction; mockCreateTransporter.mockReturnValue(mockTransporter); // Mock fs (fs.readFileSync as jest.Mock).mockImplementation((filePath: string) => { if (filePath.includes('html')) { return 'HTML template: {{name}}'; } return 'Text template: {{name}}'; }); (fs.existsSync as jest.Mock).mockReturnValue(true); emailService = new EmailService(); }); describe('sendEmail', () => { it('should send email successfully', async () => { // Arrange const emailOptions: EmailOptions = { to: 'test@example.com', subject: 'Test Subject', html: '

Test HTML

', text: 'Test Text', }; mockTransporter.sendMail.mockResolvedValue({ messageId: 'test-id' }); // Act const result = await emailService.sendEmail(emailOptions); // Assert expect(result).toBe(true); expect(mockTransporter.sendMail).toHaveBeenCalledWith({ from: process.env.EMAIL_FROM || 'noreply@serpentrace.com', to: emailOptions.to, subject: emailOptions.subject, html: emailOptions.html, text: emailOptions.text, attachments: expect.any(Array), }); }); it('should send email with template', async () => { // Arrange const emailOptions: EmailOptions = { to: 'test@example.com', subject: 'Test Subject', template: 'verification', templateData: { name: 'John', token: 'abc123' }, }; mockTransporter.sendMail.mockResolvedValue({ messageId: 'test-id' }); // Act const result = await emailService.sendEmail(emailOptions); // Assert expect(result).toBe(true); expect(mockTransporter.sendMail).toHaveBeenCalledWith({ from: process.env.EMAIL_FROM || 'noreply@serpentrace.com', to: emailOptions.to, subject: emailOptions.subject, html: expect.any(String), text: expect.any(String), attachments: expect.any(Array), }); }); it('should handle email send failure', async () => { // Arrange const emailOptions: EmailOptions = { to: 'test@example.com', subject: 'Test Subject', text: 'Test Text', }; mockTransporter.sendMail.mockRejectedValue(new Error('SMTP Error')); // Act const result = await emailService.sendEmail(emailOptions); // Assert expect(result).toBe(false); }); it('should handle missing template files', async () => { // Arrange const emailOptions: EmailOptions = { to: 'test@example.com', subject: 'Test Subject', template: 'nonexistent', templateData: { name: 'John' }, }; (fs.existsSync as jest.Mock).mockReturnValue(false); // Act const result = await emailService.sendEmail(emailOptions); // Assert expect(result).toBe(false); }); it('should handle template processing errors', async () => { // Arrange const emailOptions: EmailOptions = { to: 'test@example.com', subject: 'Test Subject', template: 'verification', templateData: { name: 'John' }, }; (fs.readFileSync as jest.Mock).mockImplementation(() => { throw new Error('File read error'); }); // Act const result = await emailService.sendEmail(emailOptions); // Assert expect(result).toBe(false); }); it('should use fallback content when template data is missing', async () => { // Arrange const emailOptions: EmailOptions = { to: 'test@example.com', subject: 'Test Subject', template: 'verification', }; mockTransporter.sendMail.mockResolvedValue({ messageId: 'test-id' }); // Act const result = await emailService.sendEmail(emailOptions); // Assert expect(result).toBe(true); }); }); describe('constructor', () => { it('should initialize with environment variables', () => { // Arrange const originalEnv = process.env; process.env = { ...originalEnv, EMAIL_HOST: 'test-smtp.com', EMAIL_PORT: '465', EMAIL_SECURE: 'true', EMAIL_USER: 'test@example.com', EMAIL_PASS: 'testpass', EMAIL_FROM: 'sender@example.com', }; // Act const service = new EmailService(); // Assert expect(mockCreateTransporter).toHaveBeenCalledWith({ host: 'test-smtp.com', port: 465, secure: true, auth: { user: 'test@example.com', pass: 'testpass', }, }); // Restore environment process.env = originalEnv; }); it('should use default values when environment variables are missing', () => { // Arrange const originalEnv = process.env; process.env = {}; // Act const service = new EmailService(); // Assert expect(mockCreateTransporter).toHaveBeenCalledWith({ host: 'smtp.gmail.com', port: 587, secure: false, auth: { user: '', pass: '', }, }); // Restore environment process.env = originalEnv; }); }); });