more unit tests
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
// Mock dependencies
|
||||
jest.mock('../../../../../services/email/core/EmailClient', () => {
|
||||
return jest.fn().mockImplementation(() => ({
|
||||
initialize: jest.fn().mockResolvedValue(),
|
||||
sendEmail: jest.fn().mockResolvedValue({ success: true, messageId: 'msg-123' }),
|
||||
}));
|
||||
});
|
||||
|
||||
jest.mock('../../../../../services/email/core/TemplateManager', () => {
|
||||
return jest.fn().mockImplementation(() => ({
|
||||
initialize: jest.fn().mockResolvedValue(),
|
||||
renderTemplate: jest.fn().mockResolvedValue('<html>Test</html>'),
|
||||
}));
|
||||
});
|
||||
|
||||
jest.mock('../../../../../utils/logger', () => ({
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
}));
|
||||
|
||||
const AlphaInvitationEmailService = require('../../../../../services/email/domain/AlphaInvitationEmailService');
|
||||
|
||||
describe('AlphaInvitationEmailService', () => {
|
||||
let service;
|
||||
const originalEnv = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
process.env = { ...originalEnv, FRONTEND_URL: 'http://localhost:3000' };
|
||||
service = new AlphaInvitationEmailService();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
});
|
||||
|
||||
describe('initialize', () => {
|
||||
it('should initialize only once', async () => {
|
||||
await service.initialize();
|
||||
await service.initialize();
|
||||
|
||||
expect(service.emailClient.initialize).toHaveBeenCalledTimes(1);
|
||||
expect(service.templateManager.initialize).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendAlphaInvitation', () => {
|
||||
const email = 'john@example.com';
|
||||
const code = 'ALPHA-123-XYZ';
|
||||
|
||||
it('should send alpha invitation email with correct variables', async () => {
|
||||
const result = await service.sendAlphaInvitation(email, code);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'alphaInvitationToUser',
|
||||
expect.objectContaining({
|
||||
code: 'ALPHA-123-XYZ',
|
||||
email: 'john@example.com',
|
||||
frontendUrl: 'http://localhost:3000',
|
||||
title: 'Welcome to Alpha Testing!',
|
||||
})
|
||||
);
|
||||
expect(service.emailClient.sendEmail).toHaveBeenCalledWith(
|
||||
'john@example.com',
|
||||
'Your Alpha Access Code - Village Share',
|
||||
expect.any(String)
|
||||
);
|
||||
});
|
||||
|
||||
it('should include code in message variable', async () => {
|
||||
await service.sendAlphaInvitation(email, code);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'alphaInvitationToUser',
|
||||
expect.objectContaining({
|
||||
message: expect.stringContaining('ALPHA-123-XYZ'),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
service.templateManager.renderTemplate.mockRejectedValueOnce(new Error('Template error'));
|
||||
|
||||
const result = await service.sendAlphaInvitation(email, code);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Template error');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,382 @@
|
||||
// Mock dependencies
|
||||
jest.mock('../../../../../services/email/core/EmailClient', () => {
|
||||
return jest.fn().mockImplementation(() => ({
|
||||
initialize: jest.fn().mockResolvedValue(),
|
||||
sendEmail: jest.fn().mockResolvedValue({ success: true, messageId: 'msg-123' }),
|
||||
}));
|
||||
});
|
||||
|
||||
jest.mock('../../../../../services/email/core/TemplateManager', () => {
|
||||
return jest.fn().mockImplementation(() => ({
|
||||
initialize: jest.fn().mockResolvedValue(),
|
||||
renderTemplate: jest.fn().mockResolvedValue('<html>Test</html>'),
|
||||
}));
|
||||
});
|
||||
|
||||
jest.mock('../../../../../utils/logger', () => ({
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
}));
|
||||
|
||||
const CustomerServiceEmailService = require('../../../../../services/email/domain/CustomerServiceEmailService');
|
||||
|
||||
describe('CustomerServiceEmailService', () => {
|
||||
let service;
|
||||
const originalEnv = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
process.env = {
|
||||
...originalEnv,
|
||||
CUSTOMER_SUPPORT_EMAIL: 'support@example.com',
|
||||
};
|
||||
service = new CustomerServiceEmailService();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
});
|
||||
|
||||
describe('initialize', () => {
|
||||
it('should initialize only once', async () => {
|
||||
await service.initialize();
|
||||
await service.initialize();
|
||||
|
||||
expect(service.emailClient.initialize).toHaveBeenCalledTimes(1);
|
||||
expect(service.templateManager.initialize).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendLateReturnToCustomerService', () => {
|
||||
const rental = {
|
||||
id: 123,
|
||||
endDateTime: '2024-01-16T10:00:00Z',
|
||||
actualReturnDateTime: '2024-01-16T15:00:00Z',
|
||||
item: { name: 'Power Drill' },
|
||||
};
|
||||
const owner = {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
email: 'john@example.com',
|
||||
};
|
||||
const renter = {
|
||||
firstName: 'Jane',
|
||||
lastName: 'Smith',
|
||||
email: 'jane@example.com',
|
||||
};
|
||||
const lateCalculation = {
|
||||
lateHours: 5,
|
||||
lateFee: 25.00,
|
||||
};
|
||||
|
||||
it('should send late return notification with correct variables', async () => {
|
||||
const result = await service.sendLateReturnToCustomerService(
|
||||
rental,
|
||||
owner,
|
||||
renter,
|
||||
lateCalculation
|
||||
);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'lateReturnToCS',
|
||||
expect.objectContaining({
|
||||
rentalId: 123,
|
||||
itemName: 'Power Drill',
|
||||
ownerName: 'John Doe',
|
||||
ownerEmail: 'john@example.com',
|
||||
renterName: 'Jane Smith',
|
||||
renterEmail: 'jane@example.com',
|
||||
hoursLate: '5.0',
|
||||
lateFee: '25.00',
|
||||
})
|
||||
);
|
||||
expect(service.emailClient.sendEmail).toHaveBeenCalledWith(
|
||||
'support@example.com',
|
||||
'Late Return Detected - Action Required',
|
||||
expect.any(String)
|
||||
);
|
||||
});
|
||||
|
||||
it('should return error when customer service email is not configured', async () => {
|
||||
delete process.env.CUSTOMER_SUPPORT_EMAIL;
|
||||
|
||||
const result = await service.sendLateReturnToCustomerService(
|
||||
rental,
|
||||
owner,
|
||||
renter,
|
||||
lateCalculation
|
||||
);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('No customer service email configured');
|
||||
expect(service.emailClient.sendEmail).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
service.templateManager.renderTemplate.mockRejectedValueOnce(new Error('Template error'));
|
||||
|
||||
const result = await service.sendLateReturnToCustomerService(
|
||||
rental,
|
||||
owner,
|
||||
renter,
|
||||
lateCalculation
|
||||
);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Template error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendDamageReportToCustomerService', () => {
|
||||
const rental = {
|
||||
id: 123,
|
||||
item: { name: 'Power Drill' },
|
||||
};
|
||||
const owner = {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
email: 'john@example.com',
|
||||
};
|
||||
const renter = {
|
||||
firstName: 'Jane',
|
||||
lastName: 'Smith',
|
||||
email: 'jane@example.com',
|
||||
};
|
||||
const damageAssessment = {
|
||||
description: 'Drill bit broken',
|
||||
canBeFixed: true,
|
||||
repairCost: 30,
|
||||
needsReplacement: false,
|
||||
replacementCost: null,
|
||||
feeCalculation: {
|
||||
type: 'repair',
|
||||
amount: 30,
|
||||
},
|
||||
proofOfOwnership: ['receipt.jpg'],
|
||||
};
|
||||
|
||||
it('should send damage report with correct variables', async () => {
|
||||
const result = await service.sendDamageReportToCustomerService(
|
||||
rental,
|
||||
owner,
|
||||
renter,
|
||||
damageAssessment
|
||||
);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'damageReportToCS',
|
||||
expect.objectContaining({
|
||||
rentalId: 123,
|
||||
itemName: 'Power Drill',
|
||||
ownerName: 'John Doe',
|
||||
ownerEmail: 'john@example.com',
|
||||
renterName: 'Jane Smith',
|
||||
renterEmail: 'jane@example.com',
|
||||
damageDescription: 'Drill bit broken',
|
||||
canBeFixed: 'Yes',
|
||||
repairCost: '30.00',
|
||||
needsReplacement: 'No',
|
||||
feeTypeDescription: 'Repair Cost',
|
||||
damageFee: '30.00',
|
||||
lateFee: '0.00',
|
||||
totalFees: '30.00',
|
||||
hasProofOfOwnership: 'Yes',
|
||||
})
|
||||
);
|
||||
expect(service.emailClient.sendEmail).toHaveBeenCalledWith(
|
||||
'support@example.com',
|
||||
'Damage Report Filed - Action Required',
|
||||
expect.any(String)
|
||||
);
|
||||
});
|
||||
|
||||
it('should include late fee when provided', async () => {
|
||||
const lateCalculation = { lateFee: 15 };
|
||||
|
||||
await service.sendDamageReportToCustomerService(
|
||||
rental,
|
||||
owner,
|
||||
renter,
|
||||
damageAssessment,
|
||||
lateCalculation
|
||||
);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'damageReportToCS',
|
||||
expect.objectContaining({
|
||||
lateFee: '15.00',
|
||||
totalFees: '45.00',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle replacement fee type', async () => {
|
||||
const replacementAssessment = {
|
||||
...damageAssessment,
|
||||
canBeFixed: false,
|
||||
needsReplacement: true,
|
||||
replacementCost: 150,
|
||||
feeCalculation: {
|
||||
type: 'replacement',
|
||||
amount: 150,
|
||||
},
|
||||
};
|
||||
|
||||
await service.sendDamageReportToCustomerService(
|
||||
rental,
|
||||
owner,
|
||||
renter,
|
||||
replacementAssessment
|
||||
);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'damageReportToCS',
|
||||
expect.objectContaining({
|
||||
feeTypeDescription: 'Replacement Cost',
|
||||
needsReplacement: 'Yes',
|
||||
replacementCost: '150.00',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle unknown fee type', async () => {
|
||||
const unknownTypeAssessment = {
|
||||
...damageAssessment,
|
||||
feeCalculation: {
|
||||
type: 'other',
|
||||
amount: 50,
|
||||
},
|
||||
};
|
||||
|
||||
await service.sendDamageReportToCustomerService(
|
||||
rental,
|
||||
owner,
|
||||
renter,
|
||||
unknownTypeAssessment
|
||||
);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'damageReportToCS',
|
||||
expect.objectContaining({
|
||||
feeTypeDescription: 'Damage Assessment Fee',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should indicate no proof of ownership when not provided', async () => {
|
||||
const noProofAssessment = {
|
||||
...damageAssessment,
|
||||
proofOfOwnership: [],
|
||||
};
|
||||
|
||||
await service.sendDamageReportToCustomerService(
|
||||
rental,
|
||||
owner,
|
||||
renter,
|
||||
noProofAssessment
|
||||
);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'damageReportToCS',
|
||||
expect.objectContaining({
|
||||
hasProofOfOwnership: 'No',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should return error when customer service email is not configured', async () => {
|
||||
delete process.env.CUSTOMER_SUPPORT_EMAIL;
|
||||
|
||||
const result = await service.sendDamageReportToCustomerService(
|
||||
rental,
|
||||
owner,
|
||||
renter,
|
||||
damageAssessment
|
||||
);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('No customer service email configured');
|
||||
});
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
service.emailClient.sendEmail.mockRejectedValueOnce(new Error('Send error'));
|
||||
|
||||
const result = await service.sendDamageReportToCustomerService(
|
||||
rental,
|
||||
owner,
|
||||
renter,
|
||||
damageAssessment
|
||||
);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Send error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendLostItemToCustomerService', () => {
|
||||
const rental = {
|
||||
id: 123,
|
||||
endDateTime: '2024-01-16T10:00:00Z',
|
||||
itemLostReportedAt: '2024-01-17T10:00:00Z',
|
||||
item: {
|
||||
name: 'Power Drill',
|
||||
replacementCost: 200,
|
||||
},
|
||||
};
|
||||
const owner = {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
email: 'john@example.com',
|
||||
};
|
||||
const renter = {
|
||||
firstName: 'Jane',
|
||||
lastName: 'Smith',
|
||||
email: 'jane@example.com',
|
||||
};
|
||||
|
||||
it('should send lost item notification with correct variables', async () => {
|
||||
const result = await service.sendLostItemToCustomerService(rental, owner, renter);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'lostItemToCS',
|
||||
expect.objectContaining({
|
||||
rentalId: 123,
|
||||
itemName: 'Power Drill',
|
||||
ownerName: 'John Doe',
|
||||
ownerEmail: 'john@example.com',
|
||||
renterName: 'Jane Smith',
|
||||
renterEmail: 'jane@example.com',
|
||||
replacementCost: '200.00',
|
||||
})
|
||||
);
|
||||
expect(service.emailClient.sendEmail).toHaveBeenCalledWith(
|
||||
'support@example.com',
|
||||
'Lost Item Claim Filed - Action Required',
|
||||
expect.any(String)
|
||||
);
|
||||
});
|
||||
|
||||
it('should return error when customer service email is not configured', async () => {
|
||||
delete process.env.CUSTOMER_SUPPORT_EMAIL;
|
||||
|
||||
const result = await service.sendLostItemToCustomerService(rental, owner, renter);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('No customer service email configured');
|
||||
expect(service.emailClient.sendEmail).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
service.templateManager.renderTemplate.mockRejectedValueOnce(new Error('Template error'));
|
||||
|
||||
const result = await service.sendLostItemToCustomerService(rental, owner, renter);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Template error');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,107 @@
|
||||
// Mock dependencies
|
||||
jest.mock('../../../../../services/email/core/EmailClient', () => {
|
||||
return jest.fn().mockImplementation(() => ({
|
||||
initialize: jest.fn().mockResolvedValue(),
|
||||
sendEmail: jest.fn().mockResolvedValue({ success: true, messageId: 'msg-123' }),
|
||||
}));
|
||||
});
|
||||
|
||||
jest.mock('../../../../../services/email/core/TemplateManager', () => {
|
||||
return jest.fn().mockImplementation(() => ({
|
||||
initialize: jest.fn().mockResolvedValue(),
|
||||
renderTemplate: jest.fn().mockResolvedValue('<html>Test</html>'),
|
||||
}));
|
||||
});
|
||||
|
||||
jest.mock('../../../../../utils/logger', () => ({
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
}));
|
||||
|
||||
const MessagingEmailService = require('../../../../../services/email/domain/MessagingEmailService');
|
||||
|
||||
describe('MessagingEmailService', () => {
|
||||
let service;
|
||||
const originalEnv = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
process.env = { ...originalEnv, FRONTEND_URL: 'http://localhost:3000' };
|
||||
service = new MessagingEmailService();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
});
|
||||
|
||||
describe('initialize', () => {
|
||||
it('should initialize only once', async () => {
|
||||
await service.initialize();
|
||||
await service.initialize();
|
||||
|
||||
expect(service.emailClient.initialize).toHaveBeenCalledTimes(1);
|
||||
expect(service.templateManager.initialize).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendNewMessageNotification', () => {
|
||||
const receiver = { firstName: 'John', email: 'john@example.com' };
|
||||
const sender = { id: 456, firstName: 'Jane', lastName: 'Smith' };
|
||||
const message = {
|
||||
content: 'Hello, is the power drill still available?',
|
||||
createdAt: '2024-01-15T10:00:00Z',
|
||||
};
|
||||
|
||||
it('should send new message notification with correct variables', async () => {
|
||||
const result = await service.sendNewMessageNotification(receiver, sender, message);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'newMessageToUser',
|
||||
expect.objectContaining({
|
||||
recipientName: 'John',
|
||||
senderName: 'Jane Smith',
|
||||
messageContent: 'Hello, is the power drill still available?',
|
||||
conversationUrl: 'http://localhost:3000/messages/conversations/456',
|
||||
})
|
||||
);
|
||||
expect(service.emailClient.sendEmail).toHaveBeenCalledWith(
|
||||
'john@example.com',
|
||||
'New message from Jane Smith',
|
||||
expect.any(String)
|
||||
);
|
||||
});
|
||||
|
||||
it('should use default name when receiver firstName is missing', async () => {
|
||||
const receiverNoName = { email: 'john@example.com' };
|
||||
|
||||
await service.sendNewMessageNotification(receiverNoName, sender, message);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'newMessageToUser',
|
||||
expect.objectContaining({ recipientName: 'there' })
|
||||
);
|
||||
});
|
||||
|
||||
it('should use A user fallback when sender names produce empty string', async () => {
|
||||
const senderEmptyNames = { id: 456, firstName: '', lastName: '' };
|
||||
|
||||
await service.sendNewMessageNotification(receiver, senderEmptyNames, message);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'newMessageToUser',
|
||||
expect.objectContaining({ senderName: 'A user' })
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
service.templateManager.renderTemplate.mockRejectedValueOnce(new Error('Template error'));
|
||||
|
||||
const result = await service.sendNewMessageNotification(receiver, sender, message);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Template error');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,986 @@
|
||||
// Mock dependencies
|
||||
jest.mock('../../../../../services/email/core/EmailClient', () => {
|
||||
return jest.fn().mockImplementation(() => ({
|
||||
initialize: jest.fn().mockResolvedValue(),
|
||||
sendEmail: jest.fn().mockResolvedValue({ success: true, messageId: 'msg-123' }),
|
||||
}));
|
||||
});
|
||||
|
||||
jest.mock('../../../../../services/email/core/TemplateManager', () => {
|
||||
return jest.fn().mockImplementation(() => ({
|
||||
initialize: jest.fn().mockResolvedValue(),
|
||||
renderTemplate: jest.fn().mockResolvedValue('<html>Test</html>'),
|
||||
}));
|
||||
});
|
||||
|
||||
jest.mock('../../../../../utils/logger', () => ({
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
}));
|
||||
|
||||
const RentalFlowEmailService = require('../../../../../services/email/domain/RentalFlowEmailService');
|
||||
|
||||
describe('RentalFlowEmailService', () => {
|
||||
let service;
|
||||
const originalEnv = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
process.env = { ...originalEnv, FRONTEND_URL: 'http://localhost:3000' };
|
||||
service = new RentalFlowEmailService();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
});
|
||||
|
||||
describe('initialize', () => {
|
||||
it('should initialize only once', async () => {
|
||||
await service.initialize();
|
||||
await service.initialize();
|
||||
|
||||
expect(service.emailClient.initialize).toHaveBeenCalledTimes(1);
|
||||
expect(service.templateManager.initialize).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendRentalRequestEmail', () => {
|
||||
const owner = { firstName: 'John', email: 'john@example.com' };
|
||||
const renter = { firstName: 'Jane', lastName: 'Smith' };
|
||||
const rental = {
|
||||
id: 123,
|
||||
item: { name: 'Power Drill' },
|
||||
startDateTime: '2024-01-15T10:00:00Z',
|
||||
endDateTime: '2024-01-16T10:00:00Z',
|
||||
totalAmount: '50.00',
|
||||
payoutAmount: '45.00',
|
||||
deliveryMethod: 'pickup',
|
||||
intendedUse: 'Home project',
|
||||
};
|
||||
|
||||
it('should send rental request email with correct variables', async () => {
|
||||
const result = await service.sendRentalRequestEmail(owner, renter, rental);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalRequestToOwner',
|
||||
expect.objectContaining({
|
||||
ownerName: 'John',
|
||||
renterName: 'Jane Smith',
|
||||
itemName: 'Power Drill',
|
||||
totalAmount: '50.00',
|
||||
payoutAmount: '45.00',
|
||||
deliveryMethod: 'pickup',
|
||||
intendedUse: 'Home project',
|
||||
approveUrl: 'http://localhost:3000/owning?rentalId=123',
|
||||
})
|
||||
);
|
||||
expect(service.emailClient.sendEmail).toHaveBeenCalledWith(
|
||||
'john@example.com',
|
||||
'Rental Request for Power Drill',
|
||||
expect.any(String)
|
||||
);
|
||||
});
|
||||
|
||||
it('should use default values when data is missing', async () => {
|
||||
const minimalRental = { id: 123, item: null };
|
||||
const minimalRenter = {};
|
||||
|
||||
await service.sendRentalRequestEmail(owner, minimalRenter, minimalRental);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalRequestToOwner',
|
||||
expect.objectContaining({
|
||||
itemName: 'your item',
|
||||
startDate: 'Not specified',
|
||||
endDate: 'Not specified',
|
||||
totalAmount: '0.00',
|
||||
payoutAmount: '0.00',
|
||||
deliveryMethod: 'Not specified',
|
||||
intendedUse: 'Not specified',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should use A renter fallback when names produce empty string', async () => {
|
||||
const minimalRental = { id: 123, item: null };
|
||||
const renterWithEmptyNames = { firstName: '', lastName: '' };
|
||||
|
||||
await service.sendRentalRequestEmail(owner, renterWithEmptyNames, minimalRental);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalRequestToOwner',
|
||||
expect.objectContaining({
|
||||
renterName: 'A renter',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
service.templateManager.renderTemplate.mockRejectedValueOnce(new Error('Template error'));
|
||||
|
||||
const result = await service.sendRentalRequestEmail(owner, renter, rental);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Template error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendRentalRequestConfirmationEmail', () => {
|
||||
const renter = { firstName: 'Jane', email: 'jane@example.com' };
|
||||
const rental = {
|
||||
item: { name: 'Power Drill' },
|
||||
startDateTime: '2024-01-15T10:00:00Z',
|
||||
endDateTime: '2024-01-16T10:00:00Z',
|
||||
totalAmount: '50.00',
|
||||
deliveryMethod: 'pickup',
|
||||
};
|
||||
|
||||
it('should send rental request confirmation with correct variables', async () => {
|
||||
const result = await service.sendRentalRequestConfirmationEmail(renter, rental);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalRequestConfirmationToRenter',
|
||||
expect.objectContaining({
|
||||
renterName: 'Jane',
|
||||
itemName: 'Power Drill',
|
||||
totalAmount: '50.00',
|
||||
deliveryMethod: 'pickup',
|
||||
viewRentalsUrl: 'http://localhost:3000/renting',
|
||||
})
|
||||
);
|
||||
expect(service.emailClient.sendEmail).toHaveBeenCalledWith(
|
||||
'jane@example.com',
|
||||
'Rental Request Submitted - Power Drill',
|
||||
expect.any(String)
|
||||
);
|
||||
});
|
||||
|
||||
it('should use default name when firstName is missing', async () => {
|
||||
const renterNoName = { email: 'jane@example.com' };
|
||||
|
||||
await service.sendRentalRequestConfirmationEmail(renterNoName, rental);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalRequestConfirmationToRenter',
|
||||
expect.objectContaining({ renterName: 'there' })
|
||||
);
|
||||
});
|
||||
|
||||
it('should show different payment message for free rentals', async () => {
|
||||
const freeRental = { ...rental, totalAmount: '0' };
|
||||
|
||||
await service.sendRentalRequestConfirmationEmail(renter, freeRental);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalRequestConfirmationToRenter',
|
||||
expect.objectContaining({
|
||||
paymentMessage: 'The owner will review your request and respond soon.',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should show payment pending message for paid rentals', async () => {
|
||||
await service.sendRentalRequestConfirmationEmail(renter, rental);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalRequestConfirmationToRenter',
|
||||
expect.objectContaining({
|
||||
paymentMessage: "The owner will review your request. You'll only be charged if they approve it.",
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
service.emailClient.sendEmail.mockRejectedValueOnce(new Error('Send error'));
|
||||
|
||||
const result = await service.sendRentalRequestConfirmationEmail(renter, rental);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Send error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendRentalApprovalConfirmationEmail', () => {
|
||||
const owner = {
|
||||
firstName: 'John',
|
||||
email: 'john@example.com',
|
||||
stripeConnectedAccountId: 'acct_123',
|
||||
};
|
||||
const renter = { firstName: 'Jane', lastName: 'Smith' };
|
||||
const rental = {
|
||||
id: 123,
|
||||
item: { name: 'Power Drill' },
|
||||
startDateTime: '2024-01-15T10:00:00Z',
|
||||
endDateTime: '2024-01-16T10:00:00Z',
|
||||
deliveryMethod: 'delivery',
|
||||
totalAmount: '50.00',
|
||||
payoutAmount: '45.00',
|
||||
platformFee: '5.00',
|
||||
};
|
||||
|
||||
it('should send approval confirmation with correct variables', async () => {
|
||||
const result = await service.sendRentalApprovalConfirmationEmail(owner, renter, rental);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalApprovalConfirmationToOwner',
|
||||
expect.objectContaining({
|
||||
ownerName: 'John',
|
||||
itemName: 'Power Drill',
|
||||
renterName: 'Jane Smith',
|
||||
deliveryMethod: 'Delivery',
|
||||
paymentMessage: 'their payment has been processed successfully.',
|
||||
rentalDetailsUrl: 'http://localhost:3000/owning?rentalId=123',
|
||||
})
|
||||
);
|
||||
expect(service.emailClient.sendEmail).toHaveBeenCalledWith(
|
||||
'john@example.com',
|
||||
'Rental Approved - Power Drill',
|
||||
expect.any(String)
|
||||
);
|
||||
});
|
||||
|
||||
it('should show stripe setup reminder when no stripe account for paid rental', async () => {
|
||||
const ownerNoStripe = { firstName: 'John', email: 'john@example.com' };
|
||||
|
||||
await service.sendRentalApprovalConfirmationEmail(ownerNoStripe, renter, rental);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalApprovalConfirmationToOwner',
|
||||
expect.objectContaining({
|
||||
stripeSection: expect.stringContaining('Action Required'),
|
||||
stripeSection: expect.stringContaining('Set Up Your Earnings Account'),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should show earnings active message when stripe account exists for paid rental', async () => {
|
||||
await service.sendRentalApprovalConfirmationEmail(owner, renter, rental);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalApprovalConfirmationToOwner',
|
||||
expect.objectContaining({
|
||||
stripeSection: expect.stringContaining('Earnings Account Active'),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle free rentals correctly', async () => {
|
||||
const freeRental = { ...rental, totalAmount: '0', payoutAmount: '0', platformFee: '0' };
|
||||
|
||||
await service.sendRentalApprovalConfirmationEmail(owner, renter, freeRental);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalApprovalConfirmationToOwner',
|
||||
expect.objectContaining({
|
||||
paymentMessage: 'this is a free rental (no payment required).',
|
||||
earningsSection: '',
|
||||
stripeSection: '',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should use default name when firstName is missing', async () => {
|
||||
const ownerNoName = { email: 'john@example.com', stripeConnectedAccountId: 'acct_123' };
|
||||
|
||||
await service.sendRentalApprovalConfirmationEmail(ownerNoName, renter, rental);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalApprovalConfirmationToOwner',
|
||||
expect.objectContaining({ ownerName: 'there' })
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
service.templateManager.renderTemplate.mockRejectedValueOnce(new Error('Template error'));
|
||||
|
||||
const result = await service.sendRentalApprovalConfirmationEmail(owner, renter, rental);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Template error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendRentalDeclinedEmail', () => {
|
||||
const renter = { firstName: 'Jane', email: 'jane@example.com' };
|
||||
const rental = {
|
||||
item: { name: 'Power Drill' },
|
||||
startDateTime: '2024-01-15T10:00:00Z',
|
||||
endDateTime: '2024-01-16T10:00:00Z',
|
||||
totalAmount: '50.00',
|
||||
payoutAmount: '45.00',
|
||||
deliveryMethod: 'pickup',
|
||||
};
|
||||
|
||||
it('should send declined email with correct variables', async () => {
|
||||
const result = await service.sendRentalDeclinedEmail(renter, rental, 'Item unavailable');
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalDeclinedToRenter',
|
||||
expect.objectContaining({
|
||||
renterName: 'Jane',
|
||||
itemName: 'Power Drill',
|
||||
deliveryMethod: 'pickup',
|
||||
browseItemsUrl: 'http://localhost:3000/',
|
||||
})
|
||||
);
|
||||
expect(service.emailClient.sendEmail).toHaveBeenCalledWith(
|
||||
'jane@example.com',
|
||||
'Rental Request Declined - Power Drill',
|
||||
expect.any(String)
|
||||
);
|
||||
});
|
||||
|
||||
it('should include owner message when decline reason is provided', async () => {
|
||||
await service.sendRentalDeclinedEmail(renter, rental, 'Item is broken');
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalDeclinedToRenter',
|
||||
expect.objectContaining({
|
||||
ownerMessage: expect.stringContaining('Item is broken'),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should not include owner message when no decline reason', async () => {
|
||||
await service.sendRentalDeclinedEmail(renter, rental, null);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalDeclinedToRenter',
|
||||
expect.objectContaining({
|
||||
ownerMessage: '',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should show no charge message for paid rentals', async () => {
|
||||
await service.sendRentalDeclinedEmail(renter, rental, null);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalDeclinedToRenter',
|
||||
expect.objectContaining({
|
||||
paymentMessage: 'Since your request was declined before payment was processed, you will not be charged.',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should show no payment message for free rentals', async () => {
|
||||
const freeRental = { ...rental, totalAmount: '0' };
|
||||
|
||||
await service.sendRentalDeclinedEmail(renter, freeRental, null);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalDeclinedToRenter',
|
||||
expect.objectContaining({
|
||||
paymentMessage: 'No payment was required for this rental request.',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should use default name when firstName is missing', async () => {
|
||||
const renterNoName = { email: 'jane@example.com' };
|
||||
|
||||
await service.sendRentalDeclinedEmail(renterNoName, rental, null);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalDeclinedToRenter',
|
||||
expect.objectContaining({ renterName: 'there' })
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
service.emailClient.sendEmail.mockRejectedValueOnce(new Error('Send error'));
|
||||
|
||||
const result = await service.sendRentalDeclinedEmail(renter, rental, null);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Send error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendRentalConfirmation', () => {
|
||||
const userEmail = 'jane@example.com';
|
||||
const notification = {
|
||||
title: 'Rental Confirmed',
|
||||
message: 'Your rental has been confirmed.',
|
||||
};
|
||||
const rental = {
|
||||
item: { name: 'Power Drill' },
|
||||
startDateTime: '2024-01-15T10:00:00Z',
|
||||
endDateTime: '2024-01-16T10:00:00Z',
|
||||
totalAmount: '50.00',
|
||||
paymentStatus: 'paid',
|
||||
paymentMethodBrand: 'visa',
|
||||
paymentMethodLast4: '4242',
|
||||
stripePaymentIntentId: 'pi_123',
|
||||
chargedAt: '2024-01-15T09:00:00Z',
|
||||
};
|
||||
|
||||
it('should send rental confirmation with correct variables', async () => {
|
||||
const result = await service.sendRentalConfirmation(
|
||||
userEmail,
|
||||
notification,
|
||||
rental,
|
||||
'Jane',
|
||||
true
|
||||
);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalConfirmationToUser',
|
||||
expect.objectContaining({
|
||||
recipientName: 'Jane',
|
||||
title: 'Rental Confirmed',
|
||||
message: 'Your rental has been confirmed.',
|
||||
itemName: 'Power Drill',
|
||||
isRenter: true,
|
||||
})
|
||||
);
|
||||
expect(service.emailClient.sendEmail).toHaveBeenCalledWith(
|
||||
'jane@example.com',
|
||||
'Rental Confirmation - Power Drill',
|
||||
expect.any(String)
|
||||
);
|
||||
});
|
||||
|
||||
it('should include payment receipt for renter with paid rental', async () => {
|
||||
await service.sendRentalConfirmation(userEmail, notification, rental, 'Jane', true);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalConfirmationToUser',
|
||||
expect.objectContaining({
|
||||
paymentSection: expect.stringContaining('Payment Receipt'),
|
||||
paymentSection: expect.stringContaining('Visa ending in 4242'),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should show free rental message for zero amount', async () => {
|
||||
const freeRental = { ...rental, totalAmount: '0' };
|
||||
|
||||
await service.sendRentalConfirmation(userEmail, notification, freeRental, 'Jane', true);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalConfirmationToUser',
|
||||
expect.objectContaining({
|
||||
paymentSection: expect.stringContaining('No Payment Required'),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should not include payment section for owner', async () => {
|
||||
await service.sendRentalConfirmation(userEmail, notification, rental, 'John', false);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalConfirmationToUser',
|
||||
expect.objectContaining({
|
||||
paymentSection: '',
|
||||
isRenter: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should use default name when recipientName is null', async () => {
|
||||
await service.sendRentalConfirmation(userEmail, notification, rental, null, true);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalConfirmationToUser',
|
||||
expect.objectContaining({ recipientName: 'there' })
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle missing rental data', async () => {
|
||||
const minimalRental = {};
|
||||
|
||||
await service.sendRentalConfirmation(userEmail, notification, minimalRental, 'Jane', true);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalConfirmationToUser',
|
||||
expect.objectContaining({
|
||||
itemName: 'Unknown Item',
|
||||
startDate: 'Not specified',
|
||||
endDate: 'Not specified',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
service.templateManager.renderTemplate.mockRejectedValueOnce(new Error('Template error'));
|
||||
|
||||
const result = await service.sendRentalConfirmation(
|
||||
userEmail,
|
||||
notification,
|
||||
rental,
|
||||
'Jane',
|
||||
true
|
||||
);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Template error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendRentalConfirmationEmails', () => {
|
||||
const owner = { firstName: 'John', email: 'john@example.com' };
|
||||
const renter = { firstName: 'Jane', email: 'jane@example.com' };
|
||||
const rental = {
|
||||
id: 123,
|
||||
ownerId: 1,
|
||||
renterId: 2,
|
||||
item: { name: 'Power Drill' },
|
||||
startDateTime: '2024-01-15T10:00:00Z',
|
||||
totalAmount: '50.00',
|
||||
paymentStatus: 'paid',
|
||||
};
|
||||
|
||||
it('should send confirmation emails to both owner and renter', async () => {
|
||||
const result = await service.sendRentalConfirmationEmails(owner, renter, rental);
|
||||
|
||||
expect(result.ownerEmailSent).toBe(true);
|
||||
expect(result.renterEmailSent).toBe(true);
|
||||
expect(service.emailClient.sendEmail).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should send owner email with isRenter false', async () => {
|
||||
await service.sendRentalConfirmationEmails(owner, renter, rental);
|
||||
|
||||
// First call is for owner
|
||||
const firstCall = service.templateManager.renderTemplate.mock.calls[0];
|
||||
expect(firstCall[1]).toMatchObject({ isRenter: false });
|
||||
});
|
||||
|
||||
it('should send renter email with isRenter true', async () => {
|
||||
await service.sendRentalConfirmationEmails(owner, renter, rental);
|
||||
|
||||
// Second call is for renter
|
||||
const secondCall = service.templateManager.renderTemplate.mock.calls[1];
|
||||
expect(secondCall[1]).toMatchObject({ isRenter: true });
|
||||
});
|
||||
|
||||
it('should handle missing owner email', async () => {
|
||||
const ownerNoEmail = { firstName: 'John' };
|
||||
|
||||
const result = await service.sendRentalConfirmationEmails(ownerNoEmail, renter, rental);
|
||||
|
||||
expect(result.ownerEmailSent).toBe(false);
|
||||
expect(result.renterEmailSent).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle missing renter email', async () => {
|
||||
const renterNoEmail = { firstName: 'Jane' };
|
||||
|
||||
const result = await service.sendRentalConfirmationEmails(owner, renterNoEmail, rental);
|
||||
|
||||
expect(result.ownerEmailSent).toBe(true);
|
||||
expect(result.renterEmailSent).toBe(false);
|
||||
});
|
||||
|
||||
it('should continue sending renter email if owner email fails', async () => {
|
||||
service.emailClient.sendEmail
|
||||
.mockResolvedValueOnce({ success: false, error: 'Owner send failed' })
|
||||
.mockResolvedValueOnce({ success: true, messageId: 'msg-456' });
|
||||
|
||||
const result = await service.sendRentalConfirmationEmails(owner, renter, rental);
|
||||
|
||||
expect(result.ownerEmailSent).toBe(false);
|
||||
expect(result.renterEmailSent).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle errors in owner email without crashing', async () => {
|
||||
service.emailClient.sendEmail
|
||||
.mockRejectedValueOnce(new Error('Network error'))
|
||||
.mockResolvedValueOnce({ success: true, messageId: 'msg-456' });
|
||||
|
||||
const result = await service.sendRentalConfirmationEmails(owner, renter, rental);
|
||||
|
||||
expect(result.ownerEmailSent).toBe(false);
|
||||
expect(result.renterEmailSent).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendRentalCancellationEmails', () => {
|
||||
const owner = { firstName: 'John', email: 'john@example.com' };
|
||||
const renter = { firstName: 'Jane', email: 'jane@example.com' };
|
||||
const rental = {
|
||||
item: { name: 'Power Drill' },
|
||||
startDateTime: '2024-01-15T10:00:00Z',
|
||||
endDateTime: '2024-01-16T10:00:00Z',
|
||||
cancelledBy: 'renter',
|
||||
cancelledAt: '2024-01-14T10:00:00Z',
|
||||
totalAmount: '50.00',
|
||||
};
|
||||
const refundInfo = {
|
||||
amount: 50,
|
||||
percentage: 1,
|
||||
reason: 'Full refund - cancelled before rental start',
|
||||
};
|
||||
|
||||
it('should send cancellation emails when renter cancels', async () => {
|
||||
const result = await service.sendRentalCancellationEmails(owner, renter, rental, refundInfo);
|
||||
|
||||
expect(result.confirmationEmailSent).toBe(true);
|
||||
expect(result.notificationEmailSent).toBe(true);
|
||||
expect(service.emailClient.sendEmail).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should send confirmation to renter and notification to owner when renter cancels', async () => {
|
||||
await service.sendRentalCancellationEmails(owner, renter, rental, refundInfo);
|
||||
|
||||
// Confirmation to renter (canceller)
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalCancellationConfirmationToUser',
|
||||
expect.objectContaining({ recipientName: 'Jane' })
|
||||
);
|
||||
// Notification to owner
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalCancellationNotificationToUser',
|
||||
expect.objectContaining({ recipientName: 'John' })
|
||||
);
|
||||
});
|
||||
|
||||
it('should send confirmation to owner and notification to renter when owner cancels', async () => {
|
||||
const ownerCancelledRental = { ...rental, cancelledBy: 'owner' };
|
||||
|
||||
await service.sendRentalCancellationEmails(owner, renter, ownerCancelledRental, refundInfo);
|
||||
|
||||
// Confirmation to owner (canceller)
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalCancellationConfirmationToUser',
|
||||
expect.objectContaining({ recipientName: 'John' })
|
||||
);
|
||||
// Notification to renter
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalCancellationNotificationToUser',
|
||||
expect.objectContaining({ recipientName: 'Jane' })
|
||||
);
|
||||
});
|
||||
|
||||
it('should include refund section for paid rentals', async () => {
|
||||
await service.sendRentalCancellationEmails(owner, renter, rental, refundInfo);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalCancellationConfirmationToUser',
|
||||
expect.objectContaining({
|
||||
refundSection: expect.stringContaining('Refund Information'),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should show no refund message when refund amount is zero', async () => {
|
||||
const noRefundInfo = { amount: 0, percentage: 0, reason: 'Cancelled after rental period' };
|
||||
|
||||
await service.sendRentalCancellationEmails(owner, renter, rental, noRefundInfo);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalCancellationConfirmationToUser',
|
||||
expect.objectContaining({
|
||||
refundSection: expect.stringContaining('No Refund Available'),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should not include refund section for free rentals', async () => {
|
||||
const freeRental = { ...rental, totalAmount: '0' };
|
||||
|
||||
await service.sendRentalCancellationEmails(owner, renter, freeRental, refundInfo);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalCancellationConfirmationToUser',
|
||||
expect.objectContaining({
|
||||
refundSection: '',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should use default name when firstName is missing', async () => {
|
||||
const ownerNoName = { email: 'john@example.com' };
|
||||
const renterNoName = { email: 'jane@example.com' };
|
||||
|
||||
await service.sendRentalCancellationEmails(ownerNoName, renterNoName, rental, refundInfo);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalCancellationConfirmationToUser',
|
||||
expect.objectContaining({ recipientName: 'there' })
|
||||
);
|
||||
});
|
||||
|
||||
it('should continue if confirmation email fails', async () => {
|
||||
service.emailClient.sendEmail
|
||||
.mockResolvedValueOnce({ success: false, error: 'Send failed' })
|
||||
.mockResolvedValueOnce({ success: true, messageId: 'msg-456' });
|
||||
|
||||
const result = await service.sendRentalCancellationEmails(owner, renter, rental, refundInfo);
|
||||
|
||||
expect(result.confirmationEmailSent).toBe(false);
|
||||
expect(result.notificationEmailSent).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendRentalCompletionEmails', () => {
|
||||
const owner = {
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
email: 'john@example.com',
|
||||
stripeConnectedAccountId: 'acct_123',
|
||||
};
|
||||
const renter = { firstName: 'Jane', lastName: 'Smith', email: 'jane@example.com' };
|
||||
const rental = {
|
||||
id: 123,
|
||||
item: { name: 'Power Drill' },
|
||||
startDateTime: '2024-01-15T10:00:00Z',
|
||||
endDateTime: '2024-01-16T10:00:00Z',
|
||||
actualReturnDateTime: '2024-01-16T09:30:00Z',
|
||||
itemReviewSubmittedAt: null,
|
||||
totalAmount: '50.00',
|
||||
payoutAmount: '45.00',
|
||||
platformFee: '5.00',
|
||||
};
|
||||
|
||||
it('should send completion emails to both owner and renter', async () => {
|
||||
const result = await service.sendRentalCompletionEmails(owner, renter, rental);
|
||||
|
||||
expect(result.ownerEmailSent).toBe(true);
|
||||
expect(result.renterEmailSent).toBe(true);
|
||||
expect(service.emailClient.sendEmail).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should send renter email with review prompt when not yet reviewed', async () => {
|
||||
await service.sendRentalCompletionEmails(owner, renter, rental);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalCompletionThankYouToRenter',
|
||||
expect.objectContaining({
|
||||
reviewSection: expect.stringContaining('Share Your Experience'),
|
||||
reviewSection: expect.stringContaining('Leave a Review'),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should show thank you message when review already submitted', async () => {
|
||||
const reviewedRental = { ...rental, itemReviewSubmittedAt: '2024-01-16T12:00:00Z' };
|
||||
|
||||
await service.sendRentalCompletionEmails(owner, renter, reviewedRental);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalCompletionThankYouToRenter',
|
||||
expect.objectContaining({
|
||||
reviewSection: expect.stringContaining('Thank You for Your Review'),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should send owner email with earnings for paid rental', async () => {
|
||||
await service.sendRentalCompletionEmails(owner, renter, rental);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalCompletionCongratsToOwner',
|
||||
expect.objectContaining({
|
||||
earningsSection: expect.stringContaining('Your Earnings'),
|
||||
earningsSection: expect.stringContaining('$45.00'),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should show payout initiated message when owner has stripe account', async () => {
|
||||
await service.sendRentalCompletionEmails(owner, renter, rental);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalCompletionCongratsToOwner',
|
||||
expect.objectContaining({
|
||||
stripeSection: expect.stringContaining('Payout Initiated'),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should show stripe setup reminder when owner has no stripe account for paid rental', async () => {
|
||||
const ownerNoStripe = { firstName: 'John', lastName: 'Doe', email: 'john@example.com' };
|
||||
|
||||
await service.sendRentalCompletionEmails(ownerNoStripe, renter, rental);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalCompletionCongratsToOwner',
|
||||
expect.objectContaining({
|
||||
stripeSection: expect.stringContaining('Action Required'),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should not show earnings section for free rentals', async () => {
|
||||
const freeRental = { ...rental, totalAmount: '0', payoutAmount: '0', platformFee: '0' };
|
||||
|
||||
await service.sendRentalCompletionEmails(owner, renter, freeRental);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalCompletionCongratsToOwner',
|
||||
expect.objectContaining({
|
||||
earningsSection: '',
|
||||
stripeSection: '',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should use default name when firstName is missing', async () => {
|
||||
const ownerNoName = { email: 'john@example.com', stripeConnectedAccountId: 'acct_123' };
|
||||
const renterNoName = { email: 'jane@example.com' };
|
||||
|
||||
await service.sendRentalCompletionEmails(ownerNoName, renterNoName, rental);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalCompletionThankYouToRenter',
|
||||
expect.objectContaining({ renterName: 'there' })
|
||||
);
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'rentalCompletionCongratsToOwner',
|
||||
expect.objectContaining({ ownerName: 'there' })
|
||||
);
|
||||
});
|
||||
|
||||
it('should continue if renter email fails', async () => {
|
||||
service.emailClient.sendEmail
|
||||
.mockResolvedValueOnce({ success: false, error: 'Send failed' })
|
||||
.mockResolvedValueOnce({ success: true, messageId: 'msg-456' });
|
||||
|
||||
const result = await service.sendRentalCompletionEmails(owner, renter, rental);
|
||||
|
||||
expect(result.renterEmailSent).toBe(false);
|
||||
expect(result.ownerEmailSent).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle errors in renter email without crashing', async () => {
|
||||
service.emailClient.sendEmail
|
||||
.mockRejectedValueOnce(new Error('Network error'))
|
||||
.mockResolvedValueOnce({ success: true, messageId: 'msg-456' });
|
||||
|
||||
const result = await service.sendRentalCompletionEmails(owner, renter, rental);
|
||||
|
||||
expect(result.renterEmailSent).toBe(false);
|
||||
expect(result.ownerEmailSent).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendPayoutReceivedEmail', () => {
|
||||
const owner = { firstName: 'John', email: 'john@example.com' };
|
||||
const rental = {
|
||||
item: { name: 'Power Drill' },
|
||||
startDateTime: '2024-01-15T10:00:00Z',
|
||||
endDateTime: '2024-01-16T10:00:00Z',
|
||||
totalAmount: '50.00',
|
||||
platformFee: '5.00',
|
||||
payoutAmount: '45.00',
|
||||
stripeTransferId: 'tr_123',
|
||||
};
|
||||
|
||||
it('should send payout email with correct variables', async () => {
|
||||
const result = await service.sendPayoutReceivedEmail(owner, rental);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'payoutReceivedToOwner',
|
||||
expect.objectContaining({
|
||||
ownerName: 'John',
|
||||
itemName: 'Power Drill',
|
||||
totalAmount: '50.00',
|
||||
platformFee: '5.00',
|
||||
payoutAmount: '45.00',
|
||||
stripeTransferId: 'tr_123',
|
||||
earningsDashboardUrl: 'http://localhost:3000/earnings',
|
||||
})
|
||||
);
|
||||
expect(service.emailClient.sendEmail).toHaveBeenCalledWith(
|
||||
'john@example.com',
|
||||
'Earnings Received - $45.00 for Power Drill',
|
||||
expect.any(String)
|
||||
);
|
||||
});
|
||||
|
||||
it('should use default name when firstName is missing', async () => {
|
||||
const ownerNoName = { email: 'john@example.com' };
|
||||
|
||||
await service.sendPayoutReceivedEmail(ownerNoName, rental);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'payoutReceivedToOwner',
|
||||
expect.objectContaining({ ownerName: 'there' })
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle missing item name', async () => {
|
||||
const rentalNoItem = { ...rental, item: null };
|
||||
|
||||
await service.sendPayoutReceivedEmail(owner, rentalNoItem);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'payoutReceivedToOwner',
|
||||
expect.objectContaining({ itemName: 'your item' })
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
service.templateManager.renderTemplate.mockRejectedValueOnce(new Error('Template error'));
|
||||
|
||||
const result = await service.sendPayoutReceivedEmail(owner, rental);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Template error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendAuthenticationRequiredEmail', () => {
|
||||
const email = 'jane@example.com';
|
||||
const data = {
|
||||
renterName: 'Jane',
|
||||
itemName: 'Power Drill',
|
||||
ownerName: 'John',
|
||||
amount: 50,
|
||||
};
|
||||
|
||||
it('should send authentication required email with correct variables', async () => {
|
||||
const result = await service.sendAuthenticationRequiredEmail(email, data);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'authenticationRequiredToRenter',
|
||||
expect.objectContaining({
|
||||
renterName: 'Jane',
|
||||
itemName: 'Power Drill',
|
||||
ownerName: 'John',
|
||||
amount: '50.00',
|
||||
})
|
||||
);
|
||||
expect(service.emailClient.sendEmail).toHaveBeenCalledWith(
|
||||
'jane@example.com',
|
||||
'Action Required: Complete payment for Power Drill',
|
||||
expect.any(String)
|
||||
);
|
||||
});
|
||||
|
||||
it('should use default values when data is missing', async () => {
|
||||
const minimalData = {};
|
||||
|
||||
await service.sendAuthenticationRequiredEmail(email, minimalData);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'authenticationRequiredToRenter',
|
||||
expect.objectContaining({
|
||||
renterName: 'there',
|
||||
itemName: 'the item',
|
||||
ownerName: 'The owner',
|
||||
amount: '0.00',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
service.emailClient.sendEmail.mockRejectedValueOnce(new Error('Send error'));
|
||||
|
||||
const result = await service.sendAuthenticationRequiredEmail(email, data);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Send error');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,114 @@
|
||||
// Mock dependencies
|
||||
jest.mock('../../../../../services/email/core/EmailClient', () => {
|
||||
return jest.fn().mockImplementation(() => ({
|
||||
initialize: jest.fn().mockResolvedValue(),
|
||||
sendEmail: jest.fn().mockResolvedValue({ success: true, messageId: 'msg-123' }),
|
||||
}));
|
||||
});
|
||||
|
||||
jest.mock('../../../../../services/email/core/TemplateManager', () => {
|
||||
return jest.fn().mockImplementation(() => ({
|
||||
initialize: jest.fn().mockResolvedValue(),
|
||||
renderTemplate: jest.fn().mockResolvedValue('<html>Test</html>'),
|
||||
}));
|
||||
});
|
||||
|
||||
jest.mock('../../../../../utils/logger', () => ({
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
}));
|
||||
|
||||
const RentalReminderEmailService = require('../../../../../services/email/domain/RentalReminderEmailService');
|
||||
|
||||
describe('RentalReminderEmailService', () => {
|
||||
let service;
|
||||
const originalEnv = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
process.env = { ...originalEnv, FRONTEND_URL: 'http://localhost:3000' };
|
||||
service = new RentalReminderEmailService();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
});
|
||||
|
||||
describe('initialize', () => {
|
||||
it('should initialize only once', async () => {
|
||||
await service.initialize();
|
||||
await service.initialize();
|
||||
|
||||
expect(service.emailClient.initialize).toHaveBeenCalledTimes(1);
|
||||
expect(service.templateManager.initialize).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendConditionCheckReminder', () => {
|
||||
const userEmail = 'john@example.com';
|
||||
const notification = {
|
||||
title: 'Condition Check Required',
|
||||
message: 'Please complete the condition check for your rental.',
|
||||
metadata: {
|
||||
deadline: '2024-01-16T10:00:00Z',
|
||||
},
|
||||
};
|
||||
const rental = {
|
||||
item: { name: 'Power Drill' },
|
||||
};
|
||||
|
||||
it('should send condition check reminder with correct variables', async () => {
|
||||
const result = await service.sendConditionCheckReminder(userEmail, notification, rental);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'conditionCheckReminderToUser',
|
||||
expect.objectContaining({
|
||||
title: 'Condition Check Required',
|
||||
message: 'Please complete the condition check for your rental.',
|
||||
itemName: 'Power Drill',
|
||||
})
|
||||
);
|
||||
expect(service.emailClient.sendEmail).toHaveBeenCalledWith(
|
||||
'john@example.com',
|
||||
'Village Share: Condition Check Required',
|
||||
expect.any(String)
|
||||
);
|
||||
});
|
||||
|
||||
it('should use default item name when rental item is missing', async () => {
|
||||
const rentalNoItem = {};
|
||||
|
||||
await service.sendConditionCheckReminder(userEmail, notification, rentalNoItem);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'conditionCheckReminderToUser',
|
||||
expect.objectContaining({ itemName: 'Unknown Item' })
|
||||
);
|
||||
});
|
||||
|
||||
it('should use default deadline when metadata is missing', async () => {
|
||||
const notificationNoDeadline = {
|
||||
...notification,
|
||||
metadata: {},
|
||||
};
|
||||
|
||||
await service.sendConditionCheckReminder(userEmail, notificationNoDeadline, rental);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'conditionCheckReminderToUser',
|
||||
expect.objectContaining({ deadline: 'Not specified' })
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
service.templateManager.renderTemplate.mockRejectedValueOnce(new Error('Template error'));
|
||||
|
||||
const result = await service.sendConditionCheckReminder(userEmail, notification, rental);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Template error');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,196 @@
|
||||
// Mock dependencies
|
||||
jest.mock('../../../../../services/email/core/EmailClient', () => {
|
||||
return jest.fn().mockImplementation(() => ({
|
||||
initialize: jest.fn().mockResolvedValue(),
|
||||
sendEmail: jest.fn().mockResolvedValue({ success: true, messageId: 'msg-123' }),
|
||||
}));
|
||||
});
|
||||
|
||||
jest.mock('../../../../../services/email/core/TemplateManager', () => {
|
||||
return jest.fn().mockImplementation(() => ({
|
||||
initialize: jest.fn().mockResolvedValue(),
|
||||
renderTemplate: jest.fn().mockResolvedValue('<html>Test</html>'),
|
||||
}));
|
||||
});
|
||||
|
||||
jest.mock('../../../../../utils/logger', () => ({
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
}));
|
||||
|
||||
const UserEngagementEmailService = require('../../../../../services/email/domain/UserEngagementEmailService');
|
||||
|
||||
describe('UserEngagementEmailService', () => {
|
||||
let service;
|
||||
const originalEnv = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
process.env = {
|
||||
...originalEnv,
|
||||
FRONTEND_URL: 'http://localhost:3000',
|
||||
SUPPORT_EMAIL: 'support@villageshare.com',
|
||||
};
|
||||
service = new UserEngagementEmailService();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
});
|
||||
|
||||
describe('initialize', () => {
|
||||
it('should initialize only once', async () => {
|
||||
await service.initialize();
|
||||
await service.initialize();
|
||||
|
||||
expect(service.emailClient.initialize).toHaveBeenCalledTimes(1);
|
||||
expect(service.templateManager.initialize).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendFirstListingCelebrationEmail', () => {
|
||||
const owner = { firstName: 'John', email: 'john@example.com' };
|
||||
const item = { id: 123, name: 'Power Drill' };
|
||||
|
||||
it('should send first listing celebration email with correct variables', async () => {
|
||||
const result = await service.sendFirstListingCelebrationEmail(owner, item);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'firstListingCelebrationToOwner',
|
||||
expect.objectContaining({
|
||||
ownerName: 'John',
|
||||
itemName: 'Power Drill',
|
||||
itemId: 123,
|
||||
viewItemUrl: 'http://localhost:3000/items/123',
|
||||
})
|
||||
);
|
||||
expect(service.emailClient.sendEmail).toHaveBeenCalledWith(
|
||||
'john@example.com',
|
||||
'Congratulations! Your first item is live on Village Share',
|
||||
expect.any(String)
|
||||
);
|
||||
});
|
||||
|
||||
it('should use default name when firstName is missing', async () => {
|
||||
const ownerNoName = { email: 'john@example.com' };
|
||||
|
||||
await service.sendFirstListingCelebrationEmail(ownerNoName, item);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'firstListingCelebrationToOwner',
|
||||
expect.objectContaining({ ownerName: 'there' })
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
service.templateManager.renderTemplate.mockRejectedValueOnce(new Error('Template error'));
|
||||
|
||||
const result = await service.sendFirstListingCelebrationEmail(owner, item);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Template error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendItemDeletionNotificationToOwner', () => {
|
||||
const owner = { firstName: 'John', email: 'john@example.com' };
|
||||
const item = { id: 123, name: 'Power Drill' };
|
||||
const deletionReason = 'Violated community guidelines';
|
||||
|
||||
it('should send item deletion notification with correct variables', async () => {
|
||||
const result = await service.sendItemDeletionNotificationToOwner(
|
||||
owner,
|
||||
item,
|
||||
deletionReason
|
||||
);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'itemDeletionToOwner',
|
||||
expect.objectContaining({
|
||||
ownerName: 'John',
|
||||
itemName: 'Power Drill',
|
||||
deletionReason: 'Violated community guidelines',
|
||||
supportEmail: 'support@villageshare.com',
|
||||
dashboardUrl: 'http://localhost:3000/owning',
|
||||
})
|
||||
);
|
||||
expect(service.emailClient.sendEmail).toHaveBeenCalledWith(
|
||||
'john@example.com',
|
||||
'Important: Your listing "Power Drill" has been removed',
|
||||
expect.any(String)
|
||||
);
|
||||
});
|
||||
|
||||
it('should use default name when firstName is missing', async () => {
|
||||
const ownerNoName = { email: 'john@example.com' };
|
||||
|
||||
await service.sendItemDeletionNotificationToOwner(ownerNoName, item, deletionReason);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'itemDeletionToOwner',
|
||||
expect.objectContaining({ ownerName: 'there' })
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
service.emailClient.sendEmail.mockRejectedValueOnce(new Error('Send error'));
|
||||
|
||||
const result = await service.sendItemDeletionNotificationToOwner(
|
||||
owner,
|
||||
item,
|
||||
deletionReason
|
||||
);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Send error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendUserBannedNotification', () => {
|
||||
const bannedUser = { firstName: 'John', email: 'john@example.com' };
|
||||
const admin = { firstName: 'Admin', lastName: 'User' };
|
||||
const banReason = 'Multiple policy violations';
|
||||
|
||||
it('should send user banned notification with correct variables', async () => {
|
||||
const result = await service.sendUserBannedNotification(bannedUser, admin, banReason);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'userBannedNotification',
|
||||
expect.objectContaining({
|
||||
userName: 'John',
|
||||
banReason: 'Multiple policy violations',
|
||||
supportEmail: 'support@villageshare.com',
|
||||
})
|
||||
);
|
||||
expect(service.emailClient.sendEmail).toHaveBeenCalledWith(
|
||||
'john@example.com',
|
||||
'Important: Your Village Share Account Has Been Suspended',
|
||||
expect.any(String)
|
||||
);
|
||||
});
|
||||
|
||||
it('should use default name when firstName is missing', async () => {
|
||||
const bannedUserNoName = { email: 'john@example.com' };
|
||||
|
||||
await service.sendUserBannedNotification(bannedUserNoName, admin, banReason);
|
||||
|
||||
expect(service.templateManager.renderTemplate).toHaveBeenCalledWith(
|
||||
'userBannedNotification',
|
||||
expect.objectContaining({ userName: 'there' })
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
service.templateManager.renderTemplate.mockRejectedValueOnce(new Error('Template error'));
|
||||
|
||||
const result = await service.sendUserBannedNotification(bannedUser, admin, banReason);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBe('Template error');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user