// Mock fs before requiring modules jest.mock('fs', () => ({ promises: { readFile: jest.fn(), }, })); // Clear singleton between tests beforeEach(() => { jest.clearAllMocks(); const TemplateManager = require('../../../../services/email/core/TemplateManager'); TemplateManager.instance = null; }); describe('TemplateManager', () => { const fs = require('fs').promises; describe('constructor', () => { it('should create a new instance', () => { const TemplateManager = require('../../../../services/email/core/TemplateManager'); TemplateManager.instance = null; const manager = new TemplateManager(); expect(manager).toBeDefined(); expect(manager.templates).toBeInstanceOf(Map); expect(manager.initialized).toBe(false); }); it('should return existing instance (singleton pattern)', () => { const TemplateManager = require('../../../../services/email/core/TemplateManager'); TemplateManager.instance = null; const manager1 = new TemplateManager(); const manager2 = new TemplateManager(); expect(manager1).toBe(manager2); }); }); describe('initialize', () => { it('should load all templates on initialization', async () => { // Mock fs.readFile to return template content fs.readFile.mockResolvedValue('{{content}}'); const TemplateManager = require('../../../../services/email/core/TemplateManager'); TemplateManager.instance = null; const manager = new TemplateManager(); await manager.initialize(); expect(manager.initialized).toBe(true); expect(fs.readFile).toHaveBeenCalled(); }); it('should not re-initialize if already initialized', async () => { fs.readFile.mockResolvedValue('{{content}}'); const TemplateManager = require('../../../../services/email/core/TemplateManager'); TemplateManager.instance = null; const manager = new TemplateManager(); await manager.initialize(); const callCount = fs.readFile.mock.calls.length; await manager.initialize(); expect(fs.readFile.mock.calls.length).toBe(callCount); }); it('should wait for existing initialization if in progress', async () => { fs.readFile.mockResolvedValue('{{content}}'); const TemplateManager = require('../../../../services/email/core/TemplateManager'); TemplateManager.instance = null; const manager = new TemplateManager(); // Start two initializations concurrently await Promise.all([manager.initialize(), manager.initialize()]); // Should only load templates once const uniquePaths = new Set(fs.readFile.mock.calls.map((call) => call[0])); expect(uniquePaths.size).toBeLessThanOrEqual(fs.readFile.mock.calls.length); }); it('should throw error if critical templates fail to load', async () => { // All template files fail to load fs.readFile.mockRejectedValue(new Error('File not found')); const TemplateManager = require('../../../../services/email/core/TemplateManager'); TemplateManager.instance = null; const manager = new TemplateManager(); await expect(manager.initialize()).rejects.toThrow('Critical email templates failed to load'); }); it('should succeed if critical templates load but non-critical fail', async () => { const criticalTemplates = [ 'emailVerificationToUser', 'passwordResetToUser', 'passwordChangedToUser', 'personalInfoChangedToUser', ]; fs.readFile.mockImplementation((path) => { const isCritical = criticalTemplates.some((t) => path.includes(t)); if (isCritical) { return Promise.resolve('Template content'); } return Promise.reject(new Error('File not found')); }); const TemplateManager = require('../../../../services/email/core/TemplateManager'); TemplateManager.instance = null; const manager = new TemplateManager(); // Should not throw since critical templates loaded await expect(manager.initialize()).resolves.not.toThrow(); expect(manager.initialized).toBe(true); }); }); describe('renderTemplate', () => { beforeEach(() => { fs.readFile.mockResolvedValue('Hello {{name}}, your email is {{email}}'); }); it('should render template with variables', async () => { const TemplateManager = require('../../../../services/email/core/TemplateManager'); TemplateManager.instance = null; const manager = new TemplateManager(); await manager.initialize(); // Manually set a template for testing manager.templates.set('testTemplate', 'Hello {{name}}, your email is {{email}}'); const result = await manager.renderTemplate('testTemplate', { name: 'John', email: 'john@example.com', }); expect(result).toBe('Hello John, your email is john@example.com'); }); it('should replace all occurrences of a variable', async () => { const TemplateManager = require('../../../../services/email/core/TemplateManager'); TemplateManager.instance = null; const manager = new TemplateManager(); await manager.initialize(); manager.templates.set('testTemplate', '{{name}} {{name}} {{name}}'); const result = await manager.renderTemplate('testTemplate', { name: 'John', }); expect(result).toBe('John John John'); }); it('should replace missing variables with empty string', async () => { const TemplateManager = require('../../../../services/email/core/TemplateManager'); TemplateManager.instance = null; const manager = new TemplateManager(); await manager.initialize(); manager.templates.set('testTemplate', 'Hello {{name}}, {{missing}}'); const result = await manager.renderTemplate('testTemplate', { name: 'John', }); expect(result).toBe('Hello John, {{missing}}'); }); it('should use fallback template when template not found', async () => { const TemplateManager = require('../../../../services/email/core/TemplateManager'); TemplateManager.instance = null; const manager = new TemplateManager(); await manager.initialize(); const result = await manager.renderTemplate('nonExistentTemplate', { title: 'Test Title', message: 'Test Message', }); // Should return fallback template content expect(result).toContain('Test Title'); expect(result).toContain('Test Message'); expect(result).toContain('Village Share'); }); it('should auto-initialize if not initialized', async () => { const TemplateManager = require('../../../../services/email/core/TemplateManager'); TemplateManager.instance = null; const manager = new TemplateManager(); expect(manager.initialized).toBe(false); await manager.renderTemplate('someTemplate', {}); expect(manager.initialized).toBe(true); }); it('should handle empty variables object', async () => { const TemplateManager = require('../../../../services/email/core/TemplateManager'); TemplateManager.instance = null; const manager = new TemplateManager(); await manager.initialize(); manager.templates.set('testTemplate', 'No variables'); const result = await manager.renderTemplate('testTemplate', {}); expect(result).toBe('No variables'); }); it('should handle null or undefined variable values', async () => { const TemplateManager = require('../../../../services/email/core/TemplateManager'); TemplateManager.instance = null; const manager = new TemplateManager(); await manager.initialize(); manager.templates.set('testTemplate', 'Hello {{name}}'); const result = await manager.renderTemplate('testTemplate', { name: null, }); expect(result).toBe('Hello '); }); }); describe('getFallbackTemplate', () => { it('should return specific fallback for known templates', async () => { const TemplateManager = require('../../../../services/email/core/TemplateManager'); TemplateManager.instance = null; const manager = new TemplateManager(); const fallback = manager.getFallbackTemplate('emailVerificationToUser'); expect(fallback).toContain('Verify Your Email'); expect(fallback).toContain('{{verificationUrl}}'); }); it('should return specific fallback for password reset', async () => { const TemplateManager = require('../../../../services/email/core/TemplateManager'); TemplateManager.instance = null; const manager = new TemplateManager(); const fallback = manager.getFallbackTemplate('passwordResetToUser'); expect(fallback).toContain('Reset Your Password'); expect(fallback).toContain('{{resetUrl}}'); }); it('should return specific fallback for rental request', async () => { const TemplateManager = require('../../../../services/email/core/TemplateManager'); TemplateManager.instance = null; const manager = new TemplateManager(); const fallback = manager.getFallbackTemplate('rentalRequestToOwner'); expect(fallback).toContain('New Rental Request'); expect(fallback).toContain('{{itemName}}'); }); it('should return generic fallback for unknown templates', async () => { const TemplateManager = require('../../../../services/email/core/TemplateManager'); TemplateManager.instance = null; const manager = new TemplateManager(); const fallback = manager.getFallbackTemplate('unknownTemplate'); expect(fallback).toContain('{{title}}'); expect(fallback).toContain('{{message}}'); expect(fallback).toContain('Village Share'); }); }); });