Files
rentall-app/backend/tests/unit/services/emailService.test.js
jackiettran 9a9e96d007 tests
2025-10-06 16:05:29 -04:00

251 lines
7.3 KiB
JavaScript

// Mock dependencies BEFORE requiring modules
jest.mock('@aws-sdk/client-ses');
jest.mock('../../../config/aws', () => ({
getAWSConfig: jest.fn(() => ({
region: 'us-east-1',
credentials: {
accessKeyId: 'test-key',
secretAccessKey: 'test-secret'
}
}))
}));
jest.mock('../../../models', () => ({
User: {
findByPk: jest.fn()
}
}));
const emailService = require('../../../services/emailService');
const { SESClient, SendEmailCommand } = require('@aws-sdk/client-ses');
const { getAWSConfig } = require('../../../config/aws');
describe('EmailService', () => {
let mockSESClient;
let mockSend;
beforeEach(() => {
mockSend = jest.fn();
mockSESClient = {
send: mockSend
};
SESClient.mockImplementation(() => mockSESClient);
// Reset environment variables
process.env.EMAIL_ENABLED = 'true';
process.env.AWS_REGION = 'us-east-1';
process.env.AWS_ACCESS_KEY_ID = 'test-key';
process.env.AWS_SECRET_ACCESS_KEY = 'test-secret';
process.env.SES_FROM_EMAIL = 'test@example.com';
process.env.SES_REPLY_TO_EMAIL = 'reply@example.com';
// Reset the service instance
emailService.initialized = false;
emailService.sesClient = null;
emailService.templates.clear();
});
afterEach(() => {
jest.clearAllMocks();
});
describe('initialization', () => {
it('should initialize SES client using AWS config', async () => {
await emailService.initialize();
expect(getAWSConfig).toHaveBeenCalled();
expect(SESClient).toHaveBeenCalledWith({
region: 'us-east-1',
credentials: {
accessKeyId: 'test-key',
secretAccessKey: 'test-secret'
}
});
expect(emailService.initialized).toBe(true);
});
it('should handle initialization errors', async () => {
SESClient.mockImplementationOnce(() => {
throw new Error('AWS credentials not found');
});
// Reset initialized state
emailService.initialized = false;
await expect(emailService.initialize()).rejects.toThrow('AWS credentials not found');
});
});
describe('sendEmail', () => {
beforeEach(async () => {
mockSend.mockResolvedValue({ MessageId: 'test-message-id' });
await emailService.initialize();
});
it('should send email successfully', async () => {
const result = await emailService.sendEmail(
'recipient@example.com',
'Test Subject',
'<h1>Test HTML</h1>',
'Test Text'
);
expect(result.success).toBe(true);
expect(result.messageId).toBe('test-message-id');
expect(mockSend).toHaveBeenCalledWith(expect.any(SendEmailCommand));
});
it('should handle single email address', async () => {
const result = await emailService.sendEmail('single@example.com', 'Subject', '<p>Content</p>');
expect(result.success).toBe(true);
expect(mockSend).toHaveBeenCalledWith(expect.any(SendEmailCommand));
});
it('should handle array of email addresses', async () => {
const result = await emailService.sendEmail(
['first@example.com', 'second@example.com'],
'Subject',
'<p>Content</p>'
);
expect(result.success).toBe(true);
expect(mockSend).toHaveBeenCalledWith(expect.any(SendEmailCommand));
});
it('should include reply-to address when configured', async () => {
const result = await emailService.sendEmail('test@example.com', 'Subject', '<p>Content</p>');
expect(result.success).toBe(true);
expect(mockSend).toHaveBeenCalledWith(expect.any(SendEmailCommand));
});
it('should handle SES errors', async () => {
mockSend.mockRejectedValue(new Error('SES Error'));
const result = await emailService.sendEmail('test@example.com', 'Subject', '<p>Content</p>');
expect(result.success).toBe(false);
expect(result.error).toBe('SES Error');
});
it('should skip sending when email is disabled', async () => {
process.env.EMAIL_ENABLED = 'false';
const result = await emailService.sendEmail('test@example.com', 'Subject', '<p>Content</p>');
expect(result.success).toBe(true);
expect(result.messageId).toBe('disabled');
expect(mockSend).not.toHaveBeenCalled();
});
});
describe('template rendering', () => {
it('should render template with variables', () => {
const template = '<h1>Hello {{name}}</h1><p>Your order {{orderId}} is ready.</p>';
emailService.templates.set('test', template);
const rendered = emailService.renderTemplate('test', {
name: 'John Doe',
orderId: '12345'
});
expect(rendered).toBe('<h1>Hello John Doe</h1><p>Your order 12345 is ready.</p>');
});
it('should handle missing variables by replacing with empty string', () => {
const template = '<h1>Hello {{name}}</h1><p>Your order {{orderId}} is ready.</p>';
emailService.templates.set('test', template);
const rendered = emailService.renderTemplate('test', {
name: 'John Doe',
orderId: '' // Explicitly provide empty string
});
expect(rendered).toContain('Hello John Doe');
expect(rendered).toContain('Your order');
});
it('should use fallback template when template not found', () => {
const rendered = emailService.renderTemplate('nonexistent', {
title: 'Test Title',
content: 'Test Content',
message: 'Test message'
});
expect(rendered).toContain('Test Title');
expect(rendered).toContain('Test message');
expect(rendered).toContain('RentAll');
});
});
describe('notification-specific senders', () => {
beforeEach(async () => {
mockSend.mockResolvedValue({ MessageId: 'test-message-id' });
await emailService.initialize();
});
it('should send condition check reminder', async () => {
const notification = {
title: 'Condition Check Required',
message: 'Please take photos of the item',
metadata: { deadline: '2024-01-15' }
};
const rental = { item: { name: 'Test Item' } };
const result = await emailService.sendConditionCheckReminder(
'test@example.com',
notification,
rental
);
expect(result.success).toBe(true);
expect(mockSend).toHaveBeenCalled();
});
it('should send rental confirmation', async () => {
const notification = {
title: 'Rental Confirmed',
message: 'Your rental has been confirmed'
};
const rental = {
item: { name: 'Test Item' },
startDateTime: '2024-01-15T10:00:00Z',
endDateTime: '2024-01-17T10:00:00Z'
};
const result = await emailService.sendRentalConfirmation(
'test@example.com',
notification,
rental
);
expect(result.success).toBe(true);
expect(mockSend).toHaveBeenCalled();
});
});
describe('error handling', () => {
beforeEach(async () => {
await emailService.initialize();
});
it('should handle missing rental data gracefully', async () => {
mockSend.mockResolvedValue({ MessageId: 'test-message-id' });
const notification = {
title: 'Test',
message: 'Test message',
metadata: {}
};
const result = await emailService.sendConditionCheckReminder(
'test@example.com',
notification,
null
);
expect(result.success).toBe(true);
});
});
});