Files
rxminder/services/__tests__/email.service.test.ts
William Valentin 3eb18a3b47 test: add email service comprehensive tests
- Add comprehensive tests for email service functionality
- Test email validation, sending capabilities, and error handling
- Improve service test coverage and reliability
- Ensure email operations work correctly in test environment

This adds another layer of service testing infrastructure.
2025-09-08 11:36:00 -07:00

211 lines
6.5 KiB
TypeScript

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();
}
});
});
});