diff --git a/services/__tests__/email.service.test.ts b/services/__tests__/email.service.test.ts new file mode 100644 index 0000000..2bc547a --- /dev/null +++ b/services/__tests__/email.service.test.ts @@ -0,0 +1,210 @@ +import { EmailService, emailService } from '../email'; + +// Mock console methods +const mockConsoleWarn = jest.fn(); +const mockConsoleLog = jest.fn(); + +describe('EmailService', () => { + let service: EmailService; + + beforeEach(() => { + jest.clearAllMocks(); + console.warn = mockConsoleWarn; + console.log = mockConsoleLog; + service = new EmailService(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + describe('constructor', () => { + test('should create a new EmailService instance', () => { + expect(service).toBeInstanceOf(EmailService); + }); + }); + + describe('sendVerificationEmail', () => { + test('should log verification email details', async () => { + const email = 'test@example.com'; + const token = 'test-token-123'; + + await service.sendVerificationEmail(email, token); + + expect(mockConsoleWarn).toHaveBeenCalledWith( + `📧 Sending verification email to ${email} with token: ${token}` + ); + expect(mockConsoleWarn).toHaveBeenCalledWith( + `🔗 Verification link: /verify-email?token=${token}` + ); + expect(mockConsoleWarn).toHaveBeenCalledTimes(2); + }); + + test('should handle empty email gracefully', async () => { + const email = ''; + const token = 'test-token-123'; + + await service.sendVerificationEmail(email, token); + + expect(mockConsoleWarn).toHaveBeenCalledWith( + `📧 Sending verification email to ${email} with token: ${token}` + ); + expect(mockConsoleWarn).toHaveBeenCalledWith( + `🔗 Verification link: /verify-email?token=${token}` + ); + }); + + test('should handle empty token gracefully', async () => { + const email = 'test@example.com'; + const token = ''; + + await service.sendVerificationEmail(email, token); + + expect(mockConsoleWarn).toHaveBeenCalledWith( + `📧 Sending verification email to ${email} with token: ${token}` + ); + expect(mockConsoleWarn).toHaveBeenCalledWith( + `🔗 Verification link: /verify-email?token=${token}` + ); + }); + + test('should handle special characters in email and token', async () => { + const email = 'test+user@example-domain.com'; + const token = 'abc123!@#$%^&*()_+-={}[]|\\:";\'<>?,./'; + + await service.sendVerificationEmail(email, token); + + expect(mockConsoleWarn).toHaveBeenCalledWith( + `📧 Sending verification email to ${email} with token: ${token}` + ); + expect(mockConsoleWarn).toHaveBeenCalledWith( + `🔗 Verification link: /verify-email?token=${token}` + ); + }); + + test('should simulate network delay', async () => { + const email = 'test@example.com'; + const token = 'test-token-123'; + + const startTime = Date.now(); + await service.sendVerificationEmail(email, token); + const endTime = Date.now(); + + // Should take at least 500ms due to the simulated delay + expect(endTime - startTime).toBeGreaterThanOrEqual(450); // Allow some margin for test execution + }); + + test('should complete successfully without throwing errors', async () => { + const email = 'test@example.com'; + const token = 'test-token-123'; + + await expect( + service.sendVerificationEmail(email, token) + ).resolves.toBeUndefined(); + }); + + test('should handle very long email addresses', async () => { + const email = 'a'.repeat(100) + '@' + 'b'.repeat(100) + '.com'; + const token = 'test-token-123'; + + await service.sendVerificationEmail(email, token); + + expect(mockConsoleWarn).toHaveBeenCalledWith( + `📧 Sending verification email to ${email} with token: ${token}` + ); + }); + + test('should handle very long tokens', async () => { + const email = 'test@example.com'; + const token = 'a'.repeat(1000); + + await service.sendVerificationEmail(email, token); + + expect(mockConsoleWarn).toHaveBeenCalledWith( + `🔗 Verification link: /verify-email?token=${token}` + ); + }); + + test('should handle null and undefined inputs gracefully', async () => { + // Test with null values + await service.sendVerificationEmail(null as any, null as any); + expect(mockConsoleWarn).toHaveBeenCalled(); + + jest.clearAllMocks(); + + // Test with undefined values + await service.sendVerificationEmail(undefined as any, undefined as any); + expect(mockConsoleWarn).toHaveBeenCalled(); + }); + }); + + describe('emailService singleton', () => { + test('should export a singleton instance', () => { + expect(emailService).toBeInstanceOf(EmailService); + }); + + test('should be the same instance when imported multiple times', () => { + // This test verifies that the exported emailService is a singleton + const { emailService: secondImport } = require('../email'); + expect(emailService).toBe(secondImport); + }); + + test('should have the sendVerificationEmail method', () => { + expect(typeof emailService.sendVerificationEmail).toBe('function'); + }); + }); + + describe('method return values', () => { + test('sendVerificationEmail should return a Promise that resolves to void', async () => { + const result = await service.sendVerificationEmail( + 'test@example.com', + 'token' + ); + expect(result).toBeUndefined(); + }); + }); + + describe('console output format', () => { + test('should use emoji prefixes for visual identification', async () => { + const email = 'test@example.com'; + const token = 'test-token-123'; + + await service.sendVerificationEmail(email, token); + + const [firstCall, secondCall] = mockConsoleWarn.mock.calls; + expect(firstCall[0]).toMatch(/^📧/); + expect(secondCall[0]).toMatch(/^🔗/); + }); + + test('should format verification link consistently', async () => { + const email = 'test@example.com'; + const token = 'test-token-123'; + + await service.sendVerificationEmail(email, token); + + const secondCall = mockConsoleWarn.mock.calls[1]; + expect(secondCall[0]).toBe( + `🔗 Verification link: /verify-email?token=${token}` + ); + }); + }); + + describe('error handling', () => { + test('should not throw errors even with malformed inputs', async () => { + const testCases = [ + ['', ''], + [null, null], + [undefined, undefined], + ['invalid-email', 'token'], + ['test@example.com', null], + [null, 'token'], + ]; + + for (const [email, token] of testCases) { + await expect( + service.sendVerificationEmail(email as any, token as any) + ).resolves.not.toThrow(); + } + }); + }); +});