// Mock dependencies jest.mock('../../../../../services/email/core/EmailClient', () => { return jest.fn().mockImplementation(() => ({ initialize: jest.fn().mockResolvedValue(), sendEmail: jest.fn().mockResolvedValue({ success: true, messageId: 'msg-123' }), })); }); jest.mock('../../../../../services/email/core/TemplateManager', () => { return jest.fn().mockImplementation(() => ({ initialize: jest.fn().mockResolvedValue(), renderTemplate: jest.fn().mockResolvedValue('Test'), })); }); const AuthEmailService = require('../../../../../services/email/domain/AuthEmailService'); describe('AuthEmailService', () => { let service; const originalEnv = process.env; beforeEach(() => { jest.clearAllMocks(); process.env = { ...originalEnv, FRONTEND_URL: 'http://localhost:3000' }; service = new AuthEmailService(); }); afterEach(() => { process.env = originalEnv; }); describe('initialize', () => { it('should initialize only once', async () => { await service.initialize(); await service.initialize(); expect(service.emailClient.initialize).toHaveBeenCalledTimes(1); expect(service.templateManager.initialize).toHaveBeenCalledTimes(1); }); }); describe('sendVerificationEmail', () => { it('should send verification email with correct variables', async () => { const user = { firstName: 'John', email: 'john@example.com' }; const token = 'verify-token'; const result = await service.sendVerificationEmail(user, token); expect(result.success).toBe(true); expect(service.templateManager.renderTemplate).toHaveBeenCalledWith( 'emailVerificationToUser', expect.objectContaining({ recipientName: 'John', verificationUrl: 'http://localhost:3000/verify-email?token=verify-token', }) ); expect(service.emailClient.sendEmail).toHaveBeenCalledWith( 'john@example.com', 'Verify Your Email - Village Share', expect.any(String) ); }); it('should use default name when firstName is missing', async () => { const user = { email: 'john@example.com' }; const token = 'verify-token'; await service.sendVerificationEmail(user, token); expect(service.templateManager.renderTemplate).toHaveBeenCalledWith( 'emailVerificationToUser', expect.objectContaining({ recipientName: 'there' }) ); }); }); describe('sendPasswordResetEmail', () => { it('should send password reset email with reset URL', async () => { const user = { firstName: 'Jane', email: 'jane@example.com' }; const token = 'reset-token'; const result = await service.sendPasswordResetEmail(user, token); expect(result.success).toBe(true); expect(service.templateManager.renderTemplate).toHaveBeenCalledWith( 'passwordResetToUser', expect.objectContaining({ recipientName: 'Jane', resetUrl: 'http://localhost:3000/reset-password?token=reset-token', }) ); }); }); describe('sendPasswordChangedEmail', () => { it('should send password changed confirmation', async () => { const user = { firstName: 'John', email: 'john@example.com' }; const result = await service.sendPasswordChangedEmail(user); expect(result.success).toBe(true); expect(service.templateManager.renderTemplate).toHaveBeenCalledWith( 'passwordChangedToUser', expect.objectContaining({ recipientName: 'John', email: 'john@example.com', timestamp: expect.any(String), }) ); }); }); describe('sendPersonalInfoChangedEmail', () => { it('should send personal info changed notification', async () => { const user = { firstName: 'John', email: 'john@example.com' }; const result = await service.sendPersonalInfoChangedEmail(user); expect(result.success).toBe(true); expect(service.templateManager.renderTemplate).toHaveBeenCalledWith( 'personalInfoChangedToUser', expect.objectContaining({ recipientName: 'John' }) ); }); }); describe('sendTwoFactorOtpEmail', () => { it('should send OTP code email', async () => { const user = { firstName: 'John', email: 'john@example.com' }; const otpCode = '123456'; const result = await service.sendTwoFactorOtpEmail(user, otpCode); expect(result.success).toBe(true); expect(service.templateManager.renderTemplate).toHaveBeenCalledWith( 'twoFactorOtpToUser', expect.objectContaining({ recipientName: 'John', otpCode: '123456', }) ); }); }); describe('sendTwoFactorEnabledEmail', () => { it('should send 2FA enabled confirmation', async () => { const user = { firstName: 'John', email: 'john@example.com' }; const result = await service.sendTwoFactorEnabledEmail(user); expect(result.success).toBe(true); expect(service.templateManager.renderTemplate).toHaveBeenCalledWith( 'twoFactorEnabledToUser', expect.objectContaining({ recipientName: 'John' }) ); }); }); describe('sendTwoFactorDisabledEmail', () => { it('should send 2FA disabled notification', async () => { const user = { firstName: 'John', email: 'john@example.com' }; const result = await service.sendTwoFactorDisabledEmail(user); expect(result.success).toBe(true); expect(service.templateManager.renderTemplate).toHaveBeenCalledWith( 'twoFactorDisabledToUser', expect.objectContaining({ recipientName: 'John' }) ); }); }); describe('sendRecoveryCodeUsedEmail', () => { it('should send recovery code used notification with green color for many codes', async () => { const user = { firstName: 'John', email: 'john@example.com' }; const result = await service.sendRecoveryCodeUsedEmail(user, 8); expect(result.success).toBe(true); expect(service.templateManager.renderTemplate).toHaveBeenCalledWith( 'recoveryCodeUsedToUser', expect.objectContaining({ remainingCodes: 8, remainingCodesColor: '#28a745', lowCodesWarning: false, }) ); }); it('should use orange color for medium remaining codes', async () => { const user = { firstName: 'John', email: 'john@example.com' }; await service.sendRecoveryCodeUsedEmail(user, 4); expect(service.templateManager.renderTemplate).toHaveBeenCalledWith( 'recoveryCodeUsedToUser', expect.objectContaining({ remainingCodesColor: '#fd7e14', }) ); }); it('should use red color and warning for low remaining codes', async () => { const user = { firstName: 'John', email: 'john@example.com' }; await service.sendRecoveryCodeUsedEmail(user, 1); expect(service.templateManager.renderTemplate).toHaveBeenCalledWith( 'recoveryCodeUsedToUser', expect.objectContaining({ remainingCodesColor: '#dc3545', lowCodesWarning: true, }) ); }); }); });