const EmailService = require('../../../src/application/services/EmailService'); const nodemailer = require('nodemailer'); // Mock nodemailer jest.mock('nodemailer'); describe('EmailService', () => { let emailService; let mockTransporter; beforeEach(() => { // Reset mocks jest.clearAllMocks(); // Mock transporter mockTransporter = { sendMail: jest.fn().mockResolvedValue({ messageId: 'test-message-id', response: '250 OK', }), }; // Mock nodemailer.createTransport nodemailer.createTransport.mockReturnValue(mockTransporter); // Create EmailService instance emailService = new EmailService(); }); describe('constructor', () => { test('should create transporter with correct configuration', () => { expect(nodemailer.createTransport).toHaveBeenCalled(); const config = nodemailer.createTransport.mock.calls[0][0]; expect(config).toHaveProperty('host'); expect(config).toHaveProperty('port'); expect(config).toHaveProperty('auth'); expect(config.auth).toHaveProperty('user'); expect(config.auth).toHaveProperty('pass'); }); test('should initialize transporter instance', () => { expect(emailService.transporter).toBeDefined(); expect(emailService.transporter).toBe(mockTransporter); }); }); describe('sendWelcomeEmail', () => { test('should send welcome email with correct parameters', async () => { const userEmail = 'test@example.com'; const userName = 'John Doe'; await emailService.sendWelcomeEmail(userEmail, userName); expect(mockTransporter.sendMail).toHaveBeenCalledTimes(1); const emailOptions = mockTransporter.sendMail.mock.calls[0][0]; expect(emailOptions.to).toBe(userEmail); expect(emailOptions.subject).toContain('Üdvözlünk'); expect(emailOptions).toHaveProperty('html'); }); test('should include user name in email content', async () => { const userEmail = 'jane@example.com'; const userName = 'Jane Smith'; await emailService.sendWelcomeEmail(userEmail, userName); const emailOptions = mockTransporter.sendMail.mock.calls[0][0]; expect(emailOptions.html).toContain(userName); }); test('should include user email in email content', async () => { const userEmail = 'contact@test.com'; const userName = 'Test User'; await emailService.sendWelcomeEmail(userEmail, userName); const emailOptions = mockTransporter.sendMail.mock.calls[0][0]; expect(emailOptions.html).toContain(userEmail); }); test('should return messageId on successful send', async () => { const result = await emailService.sendWelcomeEmail('user@test.com', 'User'); expect(result).toBeDefined(); expect(result.messageId).toBe('test-message-id'); }); test('should use correct from address', async () => { await emailService.sendWelcomeEmail('recipient@test.com', 'Recipient'); const emailOptions = mockTransporter.sendMail.mock.calls[0][0]; expect(emailOptions).toHaveProperty('from'); expect(emailOptions.from).toBeTruthy(); }); test('should handle multiple recipients', async () => { await emailService.sendWelcomeEmail('user1@test.com', 'User 1'); await emailService.sendWelcomeEmail('user2@test.com', 'User 2'); await emailService.sendWelcomeEmail('user3@test.com', 'User 3'); expect(mockTransporter.sendMail).toHaveBeenCalledTimes(3); }); }); describe('error handling', () => { test('should throw error when email sending fails', async () => { mockTransporter.sendMail.mockRejectedValue(new Error('SMTP connection failed')); await expect( emailService.sendWelcomeEmail('user@test.com', 'User') ).rejects.toThrow('SMTP connection failed'); }); test('should throw error for invalid email address', async () => { mockTransporter.sendMail.mockRejectedValue(new Error('Invalid recipient')); await expect( emailService.sendWelcomeEmail('invalid-email', 'User') ).rejects.toThrow(); }); test('should throw error when transporter is not configured', async () => { mockTransporter.sendMail.mockRejectedValue(new Error('Transporter not configured')); await expect( emailService.sendWelcomeEmail('user@test.com', 'User') ).rejects.toThrow('Transporter not configured'); }); }); describe('template rendering', () => { test('should render HTML template with Handlebars', async () => { await emailService.sendWelcomeEmail('template@test.com', 'Template User'); const emailOptions = mockTransporter.sendMail.mock.calls[0][0]; // Check that HTML is rendered (not raw Handlebars template) expect(emailOptions.html).not.toContain('{{userName}}'); expect(emailOptions.html).not.toContain('{{userEmail}}'); expect(emailOptions.html).toContain('Template User'); expect(emailOptions.html).toContain('template@test.com'); }); test('should handle special characters in user name', async () => { const specialName = "O'Reilly & Sons "; await emailService.sendWelcomeEmail('user@test.com', specialName); const emailOptions = mockTransporter.sendMail.mock.calls[0][0]; // Handlebars should escape HTML by default expect(emailOptions.html).toBeDefined(); }); test('should use correct template file path', async () => { await emailService.sendWelcomeEmail('user@test.com', 'User'); // Verify that email was sent (which means template was loaded successfully) expect(mockTransporter.sendMail).toHaveBeenCalled(); }); }); describe('real-world scenarios', () => { test('should send welcome email after user registration', async () => { const newUser = { email: 'newuser@example.com', name: 'New User', }; const result = await emailService.sendWelcomeEmail(newUser.email, newUser.name); expect(result).toBeDefined(); expect(mockTransporter.sendMail).toHaveBeenCalledWith( expect.objectContaining({ to: newUser.email, html: expect.stringContaining(newUser.name), }) ); }); test('should handle concurrent email sends', async () => { const users = [ { email: 'user1@test.com', name: 'User 1' }, { email: 'user2@test.com', name: 'User 2' }, { email: 'user3@test.com', name: 'User 3' }, ]; await Promise.all( users.map(user => emailService.sendWelcomeEmail(user.email, user.name)) ); expect(mockTransporter.sendMail).toHaveBeenCalledTimes(3); }); test('should work with Ethereal Email test account', async () => { // Simulate Ethereal Email configuration const etherealTransporter = { sendMail: jest.fn().mockResolvedValue({ messageId: '', response: '250 Accepted', }), }; nodemailer.createTransport.mockReturnValue(etherealTransporter); const etherealEmailService = new EmailService(); const result = await etherealEmailService.sendWelcomeEmail('test@ethereal.email', 'Test User'); expect(result.messageId).toContain('ethereal'); expect(etherealTransporter.sendMail).toHaveBeenCalled(); }); }); describe('email content validation', () => { test('should include welcome message', async () => { await emailService.sendWelcomeEmail('user@test.com', 'User'); const emailOptions = mockTransporter.sendMail.mock.calls[0][0]; expect(emailOptions.html.toLowerCase()).toMatch(/üdvözl|welcome/i); }); test('should be HTML formatted', async () => { await emailService.sendWelcomeEmail('user@test.com', 'User'); const emailOptions = mockTransporter.sendMail.mock.calls[0][0]; expect(emailOptions.html).toContain(''); }); test('should have valid subject line', async () => { await emailService.sendWelcomeEmail('user@test.com', 'User'); const emailOptions = mockTransporter.sendMail.mock.calls[0][0]; expect(emailOptions.subject).toBeTruthy(); expect(emailOptions.subject.length).toBeGreaterThan(0); }); }); });