"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const testing_1 = require("@nestjs/testing"); const config_1 = require("@nestjs/config"); const email_service_1 = require("../services/email.service"); const mockFetch = jest.fn(); global.fetch = mockFetch; describe('EmailService', () => { let service; let configService; const defaultEmailConfig = { provider: 'sendgrid', from: 'noreply@example.com', fromName: 'Test App', replyTo: 'support@example.com', sendgridApiKey: 'SG.test-api-key', sesRegion: 'us-east-1', sesAccessKeyId: '', sesSecretAccessKey: '', smtpHost: '', smtpPort: 587, smtpUser: '', smtpPassword: '', smtpSecure: false, }; beforeEach(async () => { mockFetch.mockReset(); const mockConfigService = { get: jest.fn().mockReturnValue(defaultEmailConfig), }; const module = await testing_1.Test.createTestingModule({ providers: [ email_service_1.EmailService, { provide: config_1.ConfigService, useValue: mockConfigService }, ], }).compile(); service = module.get(email_service_1.EmailService); configService = module.get(config_1.ConfigService); service.onModuleInit(); }); afterEach(() => { jest.clearAllMocks(); }); describe('initialization', () => { it('should initialize with sendgrid provider', () => { expect(service.getProvider()).toBe('sendgrid'); expect(service.isEnabled()).toBe(true); }); it('should not be enabled when no API key is configured', async () => { configService.get.mockReturnValue({ ...defaultEmailConfig, sendgridApiKey: '', }); const module = await testing_1.Test.createTestingModule({ providers: [ email_service_1.EmailService, { provide: config_1.ConfigService, useValue: configService }, ], }).compile(); const unconfiguredService = module.get(email_service_1.EmailService); unconfiguredService.onModuleInit(); expect(unconfiguredService.isEnabled()).toBe(false); }); }); describe('sendEmail', () => { const sendEmailDto = { to: { email: 'user@example.com', name: 'Test User' }, subject: 'Test Subject', html: '

Hello

', text: 'Hello', }; describe('SendGrid provider', () => { it('should send email successfully via SendGrid', async () => { mockFetch.mockResolvedValue({ ok: true, headers: { get: jest.fn().mockReturnValue('sg-msg-123'), }, }); const result = await service.sendEmail(sendEmailDto); expect(result.success).toBe(true); expect(result.provider).toBe('sendgrid'); expect(result.messageId).toBeDefined(); expect(mockFetch).toHaveBeenCalledWith('https://api.sendgrid.com/v3/mail/send', expect.objectContaining({ method: 'POST', headers: expect.objectContaining({ 'Authorization': 'Bearer SG.test-api-key', }), })); }); it('should handle SendGrid API error', async () => { mockFetch.mockResolvedValue({ ok: false, status: 401, text: jest.fn().mockResolvedValue('Unauthorized'), }); const result = await service.sendEmail(sendEmailDto); expect(result.success).toBe(false); expect(result.error).toContain('SendGrid error'); }); it('should include CC and BCC recipients', async () => { mockFetch.mockResolvedValue({ ok: true, headers: { get: jest.fn().mockReturnValue('msg-123') }, }); const dtoWithCcBcc = { ...sendEmailDto, cc: [{ email: 'cc@example.com' }], bcc: [{ email: 'bcc@example.com' }], }; await service.sendEmail(dtoWithCcBcc); expect(mockFetch).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({ body: expect.stringContaining('cc@example.com'), })); }); it('should include attachments', async () => { mockFetch.mockResolvedValue({ ok: true, headers: { get: jest.fn().mockReturnValue('msg-123') }, }); const dtoWithAttachment = { ...sendEmailDto, attachments: [{ filename: 'test.pdf', content: 'base64content', contentType: 'application/pdf', }], }; await service.sendEmail(dtoWithAttachment); expect(mockFetch).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({ body: expect.stringContaining('test.pdf'), })); }); }); describe('when not configured', () => { it('should log email instead of sending', async () => { configService.get.mockReturnValue({ ...defaultEmailConfig, sendgridApiKey: '', }); const module = await testing_1.Test.createTestingModule({ providers: [ email_service_1.EmailService, { provide: config_1.ConfigService, useValue: configService }, ], }).compile(); const unconfiguredService = module.get(email_service_1.EmailService); unconfiguredService.onModuleInit(); const result = await unconfiguredService.sendEmail(sendEmailDto); expect(result.success).toBe(true); expect(result.messageId).toMatch(/^mock-/); expect(mockFetch).not.toHaveBeenCalled(); }); }); }); describe('sendTemplateEmail', () => { it('should send email from welcome template', async () => { mockFetch.mockResolvedValue({ ok: true, headers: { get: jest.fn().mockReturnValue('msg-123') }, }); const result = await service.sendTemplateEmail({ to: { email: 'user@example.com' }, templateKey: 'welcome', variables: { userName: 'John', appName: 'Test App', }, }); expect(result.success).toBe(true); expect(mockFetch).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({ body: expect.stringContaining('Welcome'), })); }); it('should send email from password_reset template', async () => { mockFetch.mockResolvedValue({ ok: true, headers: { get: jest.fn().mockReturnValue('msg-123') }, }); const result = await service.sendTemplateEmail({ to: { email: 'user@example.com' }, templateKey: 'password_reset', variables: { userName: 'John', resetLink: 'https://app.com/reset?token=abc', expiresIn: '1 hour', }, }); expect(result.success).toBe(true); }); it('should send email from invitation template', async () => { mockFetch.mockResolvedValue({ ok: true, headers: { get: jest.fn().mockReturnValue('msg-123') }, }); const result = await service.sendTemplateEmail({ to: { email: 'user@example.com' }, templateKey: 'invitation', variables: { inviterName: 'Admin', tenantName: 'Acme Corp', appName: 'Test App', inviteLink: 'https://app.com/invite?code=xyz', expiresIn: '7 days', }, }); expect(result.success).toBe(true); }); it('should return error for unknown template', async () => { const result = await service.sendTemplateEmail({ to: { email: 'user@example.com' }, templateKey: 'unknown_template', variables: {}, }); expect(result.success).toBe(false); expect(result.error).toContain('Template not found'); }); it('should handle conditional blocks in templates', async () => { mockFetch.mockResolvedValue({ ok: true, headers: { get: jest.fn().mockReturnValue('msg-123') }, }); await service.sendTemplateEmail({ to: { email: 'user@example.com' }, templateKey: 'notification', variables: { title: 'New Message', message: 'You have a new message', actionUrl: 'https://app.com/messages', actionText: 'View Message', }, }); expect(mockFetch).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({ body: expect.stringContaining('View Message'), })); }); }); describe('sendBulkEmails', () => { it('should send multiple emails in batches', async () => { mockFetch.mockResolvedValue({ ok: true, headers: { get: jest.fn().mockReturnValue('msg-123') }, }); const emails = Array(15).fill(null).map((_, i) => ({ to: { email: `user${i}@example.com` }, subject: `Email ${i}`, text: `Content ${i}`, })); const results = await service.sendBulkEmails(emails); expect(results).toHaveLength(15); expect(results.every(r => r.success)).toBe(true); expect(mockFetch).toHaveBeenCalledTimes(15); }); it('should handle partial failures in bulk send', async () => { mockFetch .mockResolvedValueOnce({ ok: true, headers: { get: () => 'msg-1' } }) .mockResolvedValueOnce({ ok: false, status: 500, text: () => 'Error' }) .mockResolvedValueOnce({ ok: true, headers: { get: () => 'msg-3' } }); const emails = [ { to: { email: 'user1@example.com' }, subject: 'Test 1', text: 'Content' }, { to: { email: 'user2@example.com' }, subject: 'Test 2', text: 'Content' }, { to: { email: 'user3@example.com' }, subject: 'Test 3', text: 'Content' }, ]; const results = await service.sendBulkEmails(emails); expect(results[0].success).toBe(true); expect(results[1].success).toBe(false); expect(results[2].success).toBe(true); }); }); describe('AWS SES provider', () => { beforeEach(async () => { configService.get.mockReturnValue({ ...defaultEmailConfig, provider: 'ses', sendgridApiKey: '', sesAccessKeyId: 'AKIATEST', sesSecretAccessKey: 'secret', }); const module = await testing_1.Test.createTestingModule({ providers: [ email_service_1.EmailService, { provide: config_1.ConfigService, useValue: configService }, ], }).compile(); service = module.get(email_service_1.EmailService); service.onModuleInit(); }); it('should use SES provider when configured', () => { expect(service.getProvider()).toBe('ses'); expect(service.isEnabled()).toBe(true); }); }); describe('SMTP provider', () => { beforeEach(async () => { configService.get.mockReturnValue({ ...defaultEmailConfig, provider: 'smtp', sendgridApiKey: '', smtpHost: 'smtp.example.com', smtpUser: 'user', smtpPassword: 'password', }); const module = await testing_1.Test.createTestingModule({ providers: [ email_service_1.EmailService, { provide: config_1.ConfigService, useValue: configService }, ], }).compile(); service = module.get(email_service_1.EmailService); service.onModuleInit(); }); it('should use SMTP provider when configured', () => { expect(service.getProvider()).toBe('smtp'); expect(service.isEnabled()).toBe(true); }); it('should fallback to logging for SMTP (nodemailer not implemented)', async () => { const result = await service.sendEmail({ to: { email: 'user@example.com' }, subject: 'Test', text: 'Hello', }); expect(result.success).toBe(true); expect(result.messageId).toMatch(/^smtp-/); }); }); }); //# sourceMappingURL=email.service.spec.js.map