backend unit tests
This commit is contained in:
684
backend/tests/unit/services/refundService.test.js
Normal file
684
backend/tests/unit/services/refundService.test.js
Normal file
@@ -0,0 +1,684 @@
|
||||
// Mock dependencies
|
||||
const mockRentalFindByPk = jest.fn();
|
||||
const mockRentalUpdate = jest.fn();
|
||||
const mockCreateRefund = jest.fn();
|
||||
|
||||
jest.mock('../../../models', () => ({
|
||||
Rental: {
|
||||
findByPk: mockRentalFindByPk
|
||||
}
|
||||
}));
|
||||
|
||||
jest.mock('../../../services/stripeService', () => ({
|
||||
createRefund: mockCreateRefund
|
||||
}));
|
||||
|
||||
const RefundService = require('../../../services/refundService');
|
||||
|
||||
describe('RefundService', () => {
|
||||
let consoleSpy, consoleErrorSpy, consoleWarnSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
// Set up console spies
|
||||
consoleSpy = jest.spyOn(console, 'log').mockImplementation();
|
||||
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
||||
consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
consoleSpy.mockRestore();
|
||||
consoleErrorSpy.mockRestore();
|
||||
consoleWarnSpy.mockRestore();
|
||||
});
|
||||
|
||||
describe('calculateRefundAmount', () => {
|
||||
const baseRental = {
|
||||
totalAmount: 100.00,
|
||||
startDateTime: new Date('2023-12-01T10:00:00Z')
|
||||
};
|
||||
|
||||
describe('Owner cancellation', () => {
|
||||
it('should return 100% refund when cancelled by owner', () => {
|
||||
const result = RefundService.calculateRefundAmount(baseRental, 'owner');
|
||||
|
||||
expect(result).toEqual({
|
||||
refundAmount: 100.00,
|
||||
refundPercentage: 1.0,
|
||||
reason: 'Full refund - cancelled by owner'
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle decimal amounts correctly for owner cancellation', () => {
|
||||
const rental = { ...baseRental, totalAmount: 125.75 };
|
||||
const result = RefundService.calculateRefundAmount(rental, 'owner');
|
||||
|
||||
expect(result).toEqual({
|
||||
refundAmount: 125.75,
|
||||
refundPercentage: 1.0,
|
||||
reason: 'Full refund - cancelled by owner'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Renter cancellation', () => {
|
||||
it('should return 0% refund when cancelled within 24 hours', () => {
|
||||
// Use fake timers to set the current time
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(new Date('2023-11-30T15:00:00Z')); // 19 hours before start
|
||||
|
||||
const result = RefundService.calculateRefundAmount(baseRental, 'renter');
|
||||
|
||||
expect(result).toEqual({
|
||||
refundAmount: 0.00,
|
||||
refundPercentage: 0.0,
|
||||
reason: 'No refund - cancelled within 24 hours of start time'
|
||||
});
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should return 50% refund when cancelled between 24-48 hours', () => {
|
||||
// Use fake timers to set the current time
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(new Date('2023-11-29T15:00:00Z')); // 43 hours before start
|
||||
|
||||
const result = RefundService.calculateRefundAmount(baseRental, 'renter');
|
||||
|
||||
expect(result).toEqual({
|
||||
refundAmount: 50.00,
|
||||
refundPercentage: 0.5,
|
||||
reason: '50% refund - cancelled between 24-48 hours of start time'
|
||||
});
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should return 100% refund when cancelled more than 48 hours before', () => {
|
||||
// Use fake timers to set the current time
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(new Date('2023-11-28T15:00:00Z')); // 67 hours before start
|
||||
|
||||
const result = RefundService.calculateRefundAmount(baseRental, 'renter');
|
||||
|
||||
expect(result).toEqual({
|
||||
refundAmount: 100.00,
|
||||
refundPercentage: 1.0,
|
||||
reason: 'Full refund - cancelled more than 48 hours before start time'
|
||||
});
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should handle decimal calculations correctly for 50% refund', () => {
|
||||
const rental = { ...baseRental, totalAmount: 127.33 };
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(new Date('2023-11-29T15:00:00Z')); // 43 hours before start
|
||||
|
||||
const result = RefundService.calculateRefundAmount(rental, 'renter');
|
||||
|
||||
expect(result).toEqual({
|
||||
refundAmount: 63.66, // 127.33 * 0.5 = 63.665, rounded to 63.66
|
||||
refundPercentage: 0.5,
|
||||
reason: '50% refund - cancelled between 24-48 hours of start time'
|
||||
});
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should handle edge case exactly at 24 hours', () => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(new Date('2023-11-30T10:00:00Z')); // exactly 24 hours before start
|
||||
|
||||
const result = RefundService.calculateRefundAmount(baseRental, 'renter');
|
||||
|
||||
expect(result).toEqual({
|
||||
refundAmount: 50.00,
|
||||
refundPercentage: 0.5,
|
||||
reason: '50% refund - cancelled between 24-48 hours of start time'
|
||||
});
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should handle edge case exactly at 48 hours', () => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(new Date('2023-11-29T10:00:00Z')); // exactly 48 hours before start
|
||||
|
||||
const result = RefundService.calculateRefundAmount(baseRental, 'renter');
|
||||
|
||||
expect(result).toEqual({
|
||||
refundAmount: 100.00,
|
||||
refundPercentage: 1.0,
|
||||
reason: 'Full refund - cancelled more than 48 hours before start time'
|
||||
});
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases', () => {
|
||||
it('should handle zero total amount', () => {
|
||||
const rental = { ...baseRental, totalAmount: 0 };
|
||||
const result = RefundService.calculateRefundAmount(rental, 'owner');
|
||||
|
||||
expect(result).toEqual({
|
||||
refundAmount: 0.00,
|
||||
refundPercentage: 1.0,
|
||||
reason: 'Full refund - cancelled by owner'
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle unknown cancelledBy value', () => {
|
||||
const result = RefundService.calculateRefundAmount(baseRental, 'unknown');
|
||||
|
||||
expect(result).toEqual({
|
||||
refundAmount: 0.00,
|
||||
refundPercentage: 0,
|
||||
reason: ''
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle past rental start time for renter cancellation', () => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(new Date('2023-12-02T10:00:00Z')); // 24 hours after start
|
||||
|
||||
const result = RefundService.calculateRefundAmount(baseRental, 'renter');
|
||||
|
||||
expect(result).toEqual({
|
||||
refundAmount: 0.00,
|
||||
refundPercentage: 0.0,
|
||||
reason: 'No refund - cancelled within 24 hours of start time'
|
||||
});
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateCancellationEligibility', () => {
|
||||
const baseRental = {
|
||||
id: 1,
|
||||
renterId: 100,
|
||||
ownerId: 200,
|
||||
status: 'pending',
|
||||
paymentStatus: 'paid'
|
||||
};
|
||||
|
||||
describe('Status validation', () => {
|
||||
it('should reject cancellation for already cancelled rental', () => {
|
||||
const rental = { ...baseRental, status: 'cancelled' };
|
||||
const result = RefundService.validateCancellationEligibility(rental, 100);
|
||||
|
||||
expect(result).toEqual({
|
||||
canCancel: false,
|
||||
reason: 'Rental is already cancelled',
|
||||
cancelledBy: null
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject cancellation for completed rental', () => {
|
||||
const rental = { ...baseRental, status: 'completed' };
|
||||
const result = RefundService.validateCancellationEligibility(rental, 100);
|
||||
|
||||
expect(result).toEqual({
|
||||
canCancel: false,
|
||||
reason: 'Cannot cancel completed rental',
|
||||
cancelledBy: null
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject cancellation for active rental', () => {
|
||||
const rental = { ...baseRental, status: 'active' };
|
||||
const result = RefundService.validateCancellationEligibility(rental, 100);
|
||||
|
||||
expect(result).toEqual({
|
||||
canCancel: false,
|
||||
reason: 'Cannot cancel active rental',
|
||||
cancelledBy: null
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Authorization validation', () => {
|
||||
it('should allow renter to cancel', () => {
|
||||
const result = RefundService.validateCancellationEligibility(baseRental, 100);
|
||||
|
||||
expect(result).toEqual({
|
||||
canCancel: true,
|
||||
reason: 'Cancellation allowed',
|
||||
cancelledBy: 'renter'
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow owner to cancel', () => {
|
||||
const result = RefundService.validateCancellationEligibility(baseRental, 200);
|
||||
|
||||
expect(result).toEqual({
|
||||
canCancel: true,
|
||||
reason: 'Cancellation allowed',
|
||||
cancelledBy: 'owner'
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject unauthorized user', () => {
|
||||
const result = RefundService.validateCancellationEligibility(baseRental, 999);
|
||||
|
||||
expect(result).toEqual({
|
||||
canCancel: false,
|
||||
reason: 'You are not authorized to cancel this rental',
|
||||
cancelledBy: null
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Payment status validation', () => {
|
||||
it('should reject cancellation for unpaid rental', () => {
|
||||
const rental = { ...baseRental, paymentStatus: 'pending' };
|
||||
const result = RefundService.validateCancellationEligibility(rental, 100);
|
||||
|
||||
expect(result).toEqual({
|
||||
canCancel: false,
|
||||
reason: 'Cannot cancel rental that hasn\'t been paid',
|
||||
cancelledBy: null
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject cancellation for failed payment', () => {
|
||||
const rental = { ...baseRental, paymentStatus: 'failed' };
|
||||
const result = RefundService.validateCancellationEligibility(rental, 100);
|
||||
|
||||
expect(result).toEqual({
|
||||
canCancel: false,
|
||||
reason: 'Cannot cancel rental that hasn\'t been paid',
|
||||
cancelledBy: null
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases', () => {
|
||||
it('should handle string user IDs that don\'t match', () => {
|
||||
const result = RefundService.validateCancellationEligibility(baseRental, '100');
|
||||
|
||||
expect(result).toEqual({
|
||||
canCancel: false,
|
||||
reason: 'You are not authorized to cancel this rental',
|
||||
cancelledBy: null
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle null user ID', () => {
|
||||
const result = RefundService.validateCancellationEligibility(baseRental, null);
|
||||
|
||||
expect(result).toEqual({
|
||||
canCancel: false,
|
||||
reason: 'You are not authorized to cancel this rental',
|
||||
cancelledBy: null
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('processCancellation', () => {
|
||||
let mockRental;
|
||||
|
||||
beforeEach(() => {
|
||||
mockRental = {
|
||||
id: 1,
|
||||
renterId: 100,
|
||||
ownerId: 200,
|
||||
status: 'pending',
|
||||
paymentStatus: 'paid',
|
||||
totalAmount: 100.00,
|
||||
stripePaymentIntentId: 'pi_123456789',
|
||||
startDateTime: new Date('2023-12-01T10:00:00Z'),
|
||||
update: mockRentalUpdate
|
||||
};
|
||||
|
||||
mockRentalFindByPk.mockResolvedValue(mockRental);
|
||||
mockRentalUpdate.mockResolvedValue(mockRental);
|
||||
});
|
||||
|
||||
describe('Rental not found', () => {
|
||||
it('should throw error when rental not found', async () => {
|
||||
mockRentalFindByPk.mockResolvedValue(null);
|
||||
|
||||
await expect(RefundService.processCancellation('999', 100))
|
||||
.rejects.toThrow('Rental not found');
|
||||
|
||||
expect(mockRentalFindByPk).toHaveBeenCalledWith('999');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Validation failures', () => {
|
||||
it('should throw error for invalid cancellation', async () => {
|
||||
mockRental.status = 'cancelled';
|
||||
|
||||
await expect(RefundService.processCancellation(1, 100))
|
||||
.rejects.toThrow('Rental is already cancelled');
|
||||
});
|
||||
|
||||
it('should throw error for unauthorized user', async () => {
|
||||
await expect(RefundService.processCancellation(1, 999))
|
||||
.rejects.toThrow('You are not authorized to cancel this rental');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Successful cancellation with refund', () => {
|
||||
beforeEach(() => {
|
||||
// Set time to more than 48 hours before start for full refund
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(new Date('2023-11-28T10:00:00Z'));
|
||||
|
||||
mockCreateRefund.mockResolvedValue({
|
||||
id: 're_123456789',
|
||||
amount: 10000 // Stripe uses cents
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should process owner cancellation with full refund', async () => {
|
||||
const result = await RefundService.processCancellation(1, 200, 'Owner needs to cancel');
|
||||
|
||||
// Verify Stripe refund was created
|
||||
expect(mockCreateRefund).toHaveBeenCalledWith({
|
||||
paymentIntentId: 'pi_123456789',
|
||||
amount: 100.00,
|
||||
metadata: {
|
||||
rentalId: 1,
|
||||
cancelledBy: 'owner',
|
||||
refundReason: 'Full refund - cancelled by owner'
|
||||
}
|
||||
});
|
||||
|
||||
// Verify rental was updated
|
||||
expect(mockRentalUpdate).toHaveBeenCalledWith({
|
||||
status: 'cancelled',
|
||||
cancelledBy: 'owner',
|
||||
cancelledAt: expect.any(Date),
|
||||
refundAmount: 100.00,
|
||||
refundProcessedAt: expect.any(Date),
|
||||
refundReason: 'Owner needs to cancel',
|
||||
stripeRefundId: 're_123456789',
|
||||
payoutStatus: 'pending'
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
rental: mockRental,
|
||||
refund: {
|
||||
amount: 100.00,
|
||||
percentage: 1.0,
|
||||
reason: 'Full refund - cancelled by owner',
|
||||
processed: true,
|
||||
stripeRefundId: 're_123456789'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should process renter cancellation with partial refund', async () => {
|
||||
// Set time to 36 hours before start for 50% refund
|
||||
jest.useRealTimers(); // Reset timers first
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(new Date('2023-11-29T22:00:00Z')); // 36 hours before start
|
||||
|
||||
mockCreateRefund.mockResolvedValue({
|
||||
id: 're_partial',
|
||||
amount: 5000 // 50% in cents
|
||||
});
|
||||
|
||||
const result = await RefundService.processCancellation(1, 100);
|
||||
|
||||
expect(mockCreateRefund).toHaveBeenCalledWith({
|
||||
paymentIntentId: 'pi_123456789',
|
||||
amount: 50.00,
|
||||
metadata: {
|
||||
rentalId: 1,
|
||||
cancelledBy: 'renter',
|
||||
refundReason: '50% refund - cancelled between 24-48 hours of start time'
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.refund).toEqual({
|
||||
amount: 50.00,
|
||||
percentage: 0.5,
|
||||
reason: '50% refund - cancelled between 24-48 hours of start time',
|
||||
processed: true,
|
||||
stripeRefundId: 're_partial'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('No refund scenarios', () => {
|
||||
beforeEach(() => {
|
||||
// Set time to within 24 hours for no refund
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(new Date('2023-11-30T15:00:00Z'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should handle cancellation with no refund', async () => {
|
||||
const result = await RefundService.processCancellation(1, 100);
|
||||
|
||||
// Verify no Stripe refund was attempted
|
||||
expect(mockCreateRefund).not.toHaveBeenCalled();
|
||||
|
||||
// Verify rental was updated
|
||||
expect(mockRentalUpdate).toHaveBeenCalledWith({
|
||||
status: 'cancelled',
|
||||
cancelledBy: 'renter',
|
||||
cancelledAt: expect.any(Date),
|
||||
refundAmount: 0.00,
|
||||
refundProcessedAt: null,
|
||||
refundReason: 'No refund - cancelled within 24 hours of start time',
|
||||
stripeRefundId: null,
|
||||
payoutStatus: 'pending'
|
||||
});
|
||||
|
||||
expect(result.refund).toEqual({
|
||||
amount: 0.00,
|
||||
percentage: 0.0,
|
||||
reason: 'No refund - cancelled within 24 hours of start time',
|
||||
processed: false,
|
||||
stripeRefundId: null
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle refund without payment intent ID', async () => {
|
||||
mockRental.stripePaymentIntentId = null;
|
||||
// Set to full refund scenario
|
||||
jest.useRealTimers();
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(new Date('2023-11-28T10:00:00Z'));
|
||||
|
||||
const result = await RefundService.processCancellation(1, 200);
|
||||
|
||||
expect(mockCreateRefund).not.toHaveBeenCalled();
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
||||
'Refund amount calculated but no payment intent ID for rental 1'
|
||||
);
|
||||
|
||||
expect(result.refund).toEqual({
|
||||
amount: 100.00,
|
||||
percentage: 1.0,
|
||||
reason: 'Full refund - cancelled by owner',
|
||||
processed: false,
|
||||
stripeRefundId: null
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error handling', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(new Date('2023-11-28T10:00:00Z'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should handle Stripe refund errors', async () => {
|
||||
const stripeError = new Error('Refund failed');
|
||||
mockCreateRefund.mockRejectedValue(stripeError);
|
||||
|
||||
await expect(RefundService.processCancellation(1, 200))
|
||||
.rejects.toThrow('Failed to process refund: Refund failed');
|
||||
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
'Error processing Stripe refund:',
|
||||
stripeError
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle database update errors', async () => {
|
||||
const dbError = new Error('Database update failed');
|
||||
mockRentalUpdate.mockRejectedValue(dbError);
|
||||
|
||||
mockCreateRefund.mockResolvedValue({
|
||||
id: 're_123456789'
|
||||
});
|
||||
|
||||
await expect(RefundService.processCancellation(1, 200))
|
||||
.rejects.toThrow('Database update failed');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRefundPreview', () => {
|
||||
let mockRental;
|
||||
|
||||
beforeEach(() => {
|
||||
mockRental = {
|
||||
id: 1,
|
||||
renterId: 100,
|
||||
ownerId: 200,
|
||||
status: 'pending',
|
||||
paymentStatus: 'paid',
|
||||
totalAmount: 150.00,
|
||||
startDateTime: new Date('2023-12-01T10:00:00Z')
|
||||
};
|
||||
|
||||
mockRentalFindByPk.mockResolvedValue(mockRental);
|
||||
});
|
||||
|
||||
describe('Successful preview', () => {
|
||||
it('should return owner cancellation preview', async () => {
|
||||
const result = await RefundService.getRefundPreview(1, 200);
|
||||
|
||||
expect(result).toEqual({
|
||||
canCancel: true,
|
||||
cancelledBy: 'owner',
|
||||
refundAmount: 150.00,
|
||||
refundPercentage: 1.0,
|
||||
reason: 'Full refund - cancelled by owner',
|
||||
totalAmount: 150.00
|
||||
});
|
||||
});
|
||||
|
||||
it('should return renter cancellation preview with partial refund', async () => {
|
||||
// Set time for 50% refund
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(new Date('2023-11-29T22:00:00Z')); // 36 hours before start
|
||||
|
||||
const result = await RefundService.getRefundPreview(1, 100);
|
||||
|
||||
expect(result).toEqual({
|
||||
canCancel: true,
|
||||
cancelledBy: 'renter',
|
||||
refundAmount: 75.00,
|
||||
refundPercentage: 0.5,
|
||||
reason: '50% refund - cancelled between 24-48 hours of start time',
|
||||
totalAmount: 150.00
|
||||
});
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should return renter cancellation preview with no refund', async () => {
|
||||
// Set time for no refund
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(new Date('2023-11-30T15:00:00Z'));
|
||||
|
||||
const result = await RefundService.getRefundPreview(1, 100);
|
||||
|
||||
expect(result).toEqual({
|
||||
canCancel: true,
|
||||
cancelledBy: 'renter',
|
||||
refundAmount: 0.00,
|
||||
refundPercentage: 0.0,
|
||||
reason: 'No refund - cancelled within 24 hours of start time',
|
||||
totalAmount: 150.00
|
||||
});
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error cases', () => {
|
||||
it('should throw error when rental not found', async () => {
|
||||
mockRentalFindByPk.mockResolvedValue(null);
|
||||
|
||||
await expect(RefundService.getRefundPreview('999', 100))
|
||||
.rejects.toThrow('Rental not found');
|
||||
});
|
||||
|
||||
it('should throw error for invalid cancellation', async () => {
|
||||
mockRental.status = 'cancelled';
|
||||
|
||||
await expect(RefundService.getRefundPreview(1, 100))
|
||||
.rejects.toThrow('Rental is already cancelled');
|
||||
});
|
||||
|
||||
it('should throw error for unauthorized user', async () => {
|
||||
await expect(RefundService.getRefundPreview(1, 999))
|
||||
.rejects.toThrow('You are not authorized to cancel this rental');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases and error scenarios', () => {
|
||||
it('should handle invalid rental IDs in processCancellation', async () => {
|
||||
mockRentalFindByPk.mockResolvedValue(null);
|
||||
|
||||
await expect(RefundService.processCancellation('invalid', 100))
|
||||
.rejects.toThrow('Rental not found');
|
||||
});
|
||||
|
||||
it('should handle very large refund amounts', async () => {
|
||||
const rental = {
|
||||
totalAmount: 999999.99,
|
||||
startDateTime: new Date('2023-12-01T10:00:00Z')
|
||||
};
|
||||
|
||||
const result = RefundService.calculateRefundAmount(rental, 'owner');
|
||||
|
||||
expect(result.refundAmount).toBe(999999.99);
|
||||
expect(result.refundPercentage).toBe(1.0);
|
||||
});
|
||||
|
||||
it('should handle refund amount rounding edge cases', async () => {
|
||||
const rental = {
|
||||
totalAmount: 33.333,
|
||||
startDateTime: new Date('2023-12-01T10:00:00Z')
|
||||
};
|
||||
|
||||
// Set time for 50% refund
|
||||
jest.useFakeTimers();
|
||||
jest.setSystemTime(new Date('2023-11-29T22:00:00Z')); // 36 hours before start
|
||||
|
||||
const result = RefundService.calculateRefundAmount(rental, 'renter');
|
||||
|
||||
expect(result.refundAmount).toBe(16.67); // 33.333 * 0.5 = 16.6665, rounded to 16.67
|
||||
expect(result.refundPercentage).toBe(0.5);
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user