import { describe, it, expect, beforeEach, vi } from 'vitest'; import { TemplateService } from '../src/services/template.service'; import type { TemplateDefinition } from '../src/types/template.types'; describe('TemplateService', () => { let templateService: TemplateService; beforeEach(() => { templateService = TemplateService.getInstance(); templateService.clearCache(); }); describe('resolveTemplate', () => { it('resolves an English email template by ID', () => { const resolved = templateService.resolveTemplate({ templateId: 'welcome_email', locale: 'en', variables: { name: 'John', profile_url: 'https://shieldai.app/profile' }, }); expect(resolved).not.toBeNull(); expect(resolved!.id).toBe('welcome_email'); expect(resolved!.channel).toBe('email'); expect(resolved!.locale).toBe('en'); expect(resolved!.subject).toContain('John'); expect(resolved!.body).toContain('John'); expect(resolved!.body).toContain('https://shieldai.app/profile'); }); it('resolves a Spanish email template when locale is es', () => { const resolved = templateService.resolveTemplate({ templateId: 'welcome_email', locale: 'es', variables: { name: 'Juan', profile_url: 'https://shieldai.app/profile' }, }); expect(resolved).not.toBeNull(); expect(resolved!.locale).toBe('es'); expect(resolved!.subject).toContain('Juan'); expect(resolved!.body).toContain('Bienvenido'); }); it('falls back to English when locale is not found', () => { const resolved = templateService.resolveTemplate({ templateId: 'welcome_email', locale: 'fr', variables: { name: 'Pierre', profile_url: 'https://shieldai.app/profile' }, }); expect(resolved).not.toBeNull(); expect(resolved!.locale).toBe('en'); }); it('falls back to language code match', () => { const resolved = templateService.resolveTemplate({ templateId: 'welcome_email', locale: 'es-MX', variables: { name: 'Maria', profile_url: 'https://shieldai.app/profile' }, }); expect(resolved).not.toBeNull(); expect(resolved!.locale).toBe('es'); }); it('returns null for unknown template ID', () => { const resolved = templateService.resolveTemplate({ templateId: 'nonexistent_template', locale: 'en', }); expect(resolved).toBeNull(); }); it('substitutes all variables in the template', () => { const resolved = templateService.resolveTemplate({ templateId: 'alert_notification', locale: 'en', variables: { alert_type: 'Dark Web Exposure', alert_details: 'Email found in breach', alert_time: '2026-05-01', alert_url: 'https://shieldai.app/alerts/123', }, }); expect(resolved).not.toBeNull(); expect(resolved!.subject).toBe('ShieldAI Alert: Dark Web Exposure'); expect(resolved!.body).toContain('Dark Web Exposure'); expect(resolved!.body).toContain('Email found in breach'); expect(resolved!.body).toContain('2026-05-01'); expect(resolved!.body).toContain('https://shieldai.app/alerts/123'); }); it('uses default values for optional variables', () => { const resolved = templateService.resolveTemplate({ templateId: 'alert_notification', locale: 'en', variables: { alert_type: 'Test Alert', alert_details: 'Test details', }, }); expect(resolved).not.toBeNull(); expect(resolved!.body).toContain('Just now'); }); it('keeps unsubstituted variables as-is', () => { const resolved = templateService.resolveTemplate({ templateId: 'password_reset', locale: 'en', variables: { name: 'John', reset_url: 'https://shieldai.app/reset/abc', }, }); expect(resolved).not.toBeNull(); expect(resolved!.body).toContain('24'); }); it('resolves SMS templates', () => { const resolved = templateService.resolveTemplate({ templateId: 'verification_sms', locale: 'en', variables: { code: '123456' }, }); expect(resolved).not.toBeNull(); expect(resolved!.channel).toBe('sms'); expect(resolved!.body).toContain('123456'); expect(resolved!.body).toContain('10'); }); it('resolves push notification templates', () => { const resolved = templateService.resolveTemplate({ templateId: 'alert_push', locale: 'en', variables: { alert_type: 'Voice Clone', alert_details: 'Suspicious call detected', }, }); expect(resolved).not.toBeNull(); expect(resolved!.channel).toBe('push'); expect(resolved!.subject).toContain('Voice Clone'); expect(resolved!.body).toBe('Suspicious call detected'); }); }); describe('registerTemplate', () => { it('registers a custom template', () => { const customTemplate: TemplateDefinition = { id: 'custom_welcome', name: 'Custom Welcome', channel: 'email', locale: 'en', category: 'onboarding', subject: 'Welcome, {{name}}!', body: 'Custom welcome message for {{name}}.', variables: [ { name: 'name', type: 'string', required: true }, ], }; templateService.registerTemplate(customTemplate); const resolved = templateService.resolveTemplate({ templateId: 'custom_welcome', locale: 'en', variables: { name: 'Alice' }, }); expect(resolved).not.toBeNull(); expect(resolved!.subject).toBe('Welcome, Alice!'); expect(resolved!.body).toBe('Custom welcome message for Alice.'); }); it('registers multiple templates at once', () => { const templates: TemplateDefinition[] = [ { id: 'bulk_template', name: 'Bulk Template', channel: 'email', locale: 'en', category: 'test', subject: 'Bulk test', body: 'Bulk body {{var1}} {{var2}}', variables: [ { name: 'var1', type: 'string', required: true }, { name: 'var2', type: 'string', required: true }, ], }, { id: 'bulk_template', name: 'Bulk Template ES', channel: 'email', locale: 'es', category: 'test', subject: 'Prueba masiva', body: 'Cuerpo masivo {{var1}} {{var2}}', variables: [ { name: 'var1', type: 'string', required: true }, { name: 'var2', type: 'string', required: true }, ], }, ]; templateService.registerTemplates(templates); const enResolved = templateService.resolveTemplate({ templateId: 'bulk_template', locale: 'en', variables: { var1: 'A', var2: 'B' }, }); expect(enResolved!.locale).toBe('en'); expect(enResolved!.body).toBe('Bulk body A B'); const esResolved = templateService.resolveTemplate({ templateId: 'bulk_template', locale: 'es', variables: { var1: 'A', var2: 'B' }, }); expect(esResolved!.locale).toBe('es'); expect(esResolved!.body).toBe('Cuerpo masivo A B'); }); it('invalidates cache after registration', () => { const template: TemplateDefinition = { id: 'cache_test', name: 'Cache Test', channel: 'email', locale: 'en', category: 'test', subject: 'Original', body: 'Original body', variables: [], }; templateService.registerTemplate(template); templateService.resolveTemplate({ templateId: 'cache_test', locale: 'en', }); const statsBefore = templateService.getCacheStats(); expect(statsBefore.size).toBeGreaterThan(0); templateService.registerTemplate({ ...template, subject: 'Updated', body: 'Updated body', }); const resolved = templateService.resolveTemplate({ templateId: 'cache_test', locale: 'en', }); expect(resolved!.subject).toBe('Updated'); }); }); describe('caching', () => { it('caches resolved templates', () => { templateService.resolveTemplate({ templateId: 'welcome_email', locale: 'en', variables: { name: 'Test', profile_url: 'http://test.com' }, }); const stats = templateService.getCacheStats(); expect(stats.size).toBeGreaterThan(0); }); it('clears cache on clearCache call', () => { templateService.resolveTemplate({ templateId: 'welcome_email', locale: 'en', variables: { name: 'Test', profile_url: 'http://test.com' }, }); templateService.clearCache(); const stats = templateService.getCacheStats(); expect(stats.size).toBe(0); }); }); describe('getAvailableLocales', () => { it('returns available locales for a template', () => { const locales = templateService.getAvailableLocales('welcome_email'); expect(locales).toContain('en'); expect(locales).toContain('es'); }); it('returns empty array for unknown template', () => { const locales = templateService.getAvailableLocales('nonexistent'); expect(locales).toEqual([]); }); }); describe('getTemplateIds', () => { it('returns all registered template IDs', () => { const ids = templateService.getTemplateIds(); expect(ids).toContain('welcome_email'); expect(ids).toContain('alert_notification'); expect(ids).toContain('password_reset'); expect(ids).toContain('scan_complete'); expect(ids).toContain('alert_sms'); expect(ids).toContain('verification_sms'); expect(ids).toContain('alert_push'); }); }); describe('getTemplateInfo', () => { it('returns template metadata', () => { const info = templateService.getTemplateInfo('welcome_email'); expect(info).not.toBeNull(); expect(info!.id).toBe('welcome_email'); expect(info!.channel).toBe('email'); expect(info!.category).toBe('onboarding'); expect(info!.locales).toContain('en'); expect(info!.locales).toContain('es'); expect(info!.variables).toContain('name'); expect(info!.variables).toContain('profile_url'); }); it('returns null for unknown template', () => { const info = templateService.getTemplateInfo('nonexistent'); expect(info).toBeNull(); }); }); describe('locale normalization', () => { it('normalizes locale with underscore separator', () => { const resolved = templateService.resolveTemplate({ templateId: 'welcome_email', locale: 'es_ES', variables: { name: 'Test', profile_url: 'http://test.com' }, }); expect(resolved).not.toBeNull(); expect(resolved!.locale).toBe('es'); }); it('normalizes mixed case locale', () => { const resolved = templateService.resolveTemplate({ templateId: 'welcome_email', locale: 'EN-US', variables: { name: 'Test', profile_url: 'http://test.com' }, }); expect(resolved).not.toBeNull(); expect(resolved!.locale).toBe('en'); }); }); describe('variable substitution edge cases', () => { it('handles numeric variable values', () => { const resolved = templateService.resolveTemplate({ templateId: 'password_reset', locale: 'en', variables: { name: 'John', reset_url: 'https://shieldai.app/reset', expiry_hours: 48, }, }); expect(resolved).not.toBeNull(); expect(resolved!.body).toContain('48'); }); it('handles boolean variable values', () => { templateService.registerTemplate({ id: 'bool_test', name: 'Bool Test', channel: 'email', locale: 'en', category: 'test', subject: 'Test', body: 'Value is {{flag}}', variables: [ { name: 'flag', type: 'boolean', required: true }, ], }); const resolved = templateService.resolveTemplate({ templateId: 'bool_test', locale: 'en', variables: { flag: true }, }); expect(resolved!.body).toBe('Value is true'); }); it('preserves HTML in htmlBody', () => { const resolved = templateService.resolveTemplate({ templateId: 'welcome_email', locale: 'en', variables: { name: 'John', profile_url: 'https://shieldai.app/profile' }, }); expect(resolved!.htmlBody).toContain('

'); expect(resolved!.htmlBody).toContain(''); }); }); });