more backend unit test coverage
This commit is contained in:
@@ -989,5 +989,334 @@ describe('Rentals Routes', () => {
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body).toEqual({ error: 'Cannot cancel completed rental' });
|
||||
});
|
||||
|
||||
it('should return 400 when reason is not provided', async () => {
|
||||
const response = await request(app)
|
||||
.post('/rentals/1/cancel')
|
||||
.send({});
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(response.body).toEqual({ error: 'Cancellation reason is required' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /pending-requests-count', () => {
|
||||
it('should return count of pending requests for owner', async () => {
|
||||
Rental.count = jest.fn().mockResolvedValue(5);
|
||||
|
||||
const response = await request(app)
|
||||
.get('/rentals/pending-requests-count');
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual({ count: 5 });
|
||||
expect(Rental.count).toHaveBeenCalledWith({
|
||||
where: {
|
||||
ownerId: 1,
|
||||
status: 'pending',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle database errors', async () => {
|
||||
Rental.count = jest.fn().mockRejectedValue(new Error('Database error'));
|
||||
|
||||
const response = await request(app)
|
||||
.get('/rentals/pending-requests-count');
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.body).toEqual({ error: 'Failed to get pending rental count' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /:id/decline', () => {
|
||||
const mockRental = {
|
||||
id: 1,
|
||||
ownerId: 1,
|
||||
renterId: 2,
|
||||
status: 'pending',
|
||||
item: { id: 1, name: 'Test Item' },
|
||||
owner: { id: 1, firstName: 'John', lastName: 'Doe' },
|
||||
renter: { id: 2, firstName: 'Alice', lastName: 'Johnson', email: 'alice@example.com' },
|
||||
update: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockRentalFindByPk.mockResolvedValue(mockRental);
|
||||
});
|
||||
|
||||
it('should decline rental request with reason', async () => {
|
||||
mockRental.update.mockResolvedValue();
|
||||
mockRentalFindByPk
|
||||
.mockResolvedValueOnce(mockRental)
|
||||
.mockResolvedValueOnce({ ...mockRental, status: 'declined', declineReason: 'Item not available' });
|
||||
|
||||
const response = await request(app)
|
||||
.put('/rentals/1/decline')
|
||||
.send({ reason: 'Item not available' });
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(mockRental.update).toHaveBeenCalledWith({
|
||||
status: 'declined',
|
||||
declineReason: 'Item not available',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 400 when reason is not provided', async () => {
|
||||
const response = await request(app)
|
||||
.put('/rentals/1/decline')
|
||||
.send({});
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(response.body).toEqual({ error: 'A reason for declining is required' });
|
||||
});
|
||||
|
||||
it('should return 404 for non-existent rental', async () => {
|
||||
mockRentalFindByPk.mockResolvedValue(null);
|
||||
|
||||
const response = await request(app)
|
||||
.put('/rentals/1/decline')
|
||||
.send({ reason: 'Not available' });
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
expect(response.body).toEqual({ error: 'Rental not found' });
|
||||
});
|
||||
|
||||
it('should return 403 for non-owner', async () => {
|
||||
const nonOwnerRental = { ...mockRental, ownerId: 3 };
|
||||
mockRentalFindByPk.mockResolvedValue(nonOwnerRental);
|
||||
|
||||
const response = await request(app)
|
||||
.put('/rentals/1/decline')
|
||||
.send({ reason: 'Not available' });
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
expect(response.body).toEqual({ error: 'Only the item owner can decline rental requests' });
|
||||
});
|
||||
|
||||
it('should return 400 for non-pending rental', async () => {
|
||||
const confirmedRental = { ...mockRental, status: 'confirmed' };
|
||||
mockRentalFindByPk.mockResolvedValue(confirmedRental);
|
||||
|
||||
const response = await request(app)
|
||||
.put('/rentals/1/decline')
|
||||
.send({ reason: 'Not available' });
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(response.body).toEqual({ error: 'Can only decline pending rental requests' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /cost-preview', () => {
|
||||
it('should return 400 for missing required fields', async () => {
|
||||
const response = await request(app)
|
||||
.post('/rentals/cost-preview')
|
||||
.send({ itemId: 1 });
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(response.body).toEqual({ error: 'itemId, startDateTime, and endDateTime are required' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /:id/late-fee-preview', () => {
|
||||
const LateReturnService = require('../../../services/lateReturnService');
|
||||
|
||||
const mockRental = {
|
||||
id: 1,
|
||||
ownerId: 1,
|
||||
renterId: 2,
|
||||
endDateTime: new Date('2024-01-15T18:00:00.000Z'),
|
||||
item: { id: 1, name: 'Test Item' },
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockRentalFindByPk.mockResolvedValue(mockRental);
|
||||
});
|
||||
|
||||
it('should return late fee preview', async () => {
|
||||
LateReturnService.calculateLateFee.mockReturnValue({
|
||||
isLate: true,
|
||||
hoursLate: 5,
|
||||
lateFee: 50,
|
||||
});
|
||||
|
||||
const response = await request(app)
|
||||
.get('/rentals/1/late-fee-preview')
|
||||
.query({ actualReturnDateTime: '2024-01-15T23:00:00.000Z' });
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.isLate).toBe(true);
|
||||
expect(response.body.lateFee).toBe(50);
|
||||
});
|
||||
|
||||
it('should return 400 when actualReturnDateTime is missing', async () => {
|
||||
const response = await request(app)
|
||||
.get('/rentals/1/late-fee-preview');
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(response.body).toEqual({ error: 'actualReturnDateTime is required' });
|
||||
});
|
||||
|
||||
it('should return 404 for non-existent rental', async () => {
|
||||
mockRentalFindByPk.mockResolvedValue(null);
|
||||
|
||||
const response = await request(app)
|
||||
.get('/rentals/1/late-fee-preview')
|
||||
.query({ actualReturnDateTime: '2024-01-15T23:00:00.000Z' });
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
expect(response.body).toEqual({ error: 'Rental not found' });
|
||||
});
|
||||
|
||||
it('should return 403 for unauthorized user', async () => {
|
||||
const unauthorizedRental = { ...mockRental, ownerId: 3, renterId: 4 };
|
||||
mockRentalFindByPk.mockResolvedValue(unauthorizedRental);
|
||||
|
||||
const response = await request(app)
|
||||
.get('/rentals/1/late-fee-preview')
|
||||
.query({ actualReturnDateTime: '2024-01-15T23:00:00.000Z' });
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
expect(response.body).toEqual({ error: 'Unauthorized' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /:id/mark-return', () => {
|
||||
const mockRental = {
|
||||
id: 1,
|
||||
ownerId: 1,
|
||||
renterId: 2,
|
||||
status: 'confirmed',
|
||||
startDateTime: new Date('2024-01-10T10:00:00.000Z'),
|
||||
endDateTime: new Date('2024-01-15T18:00:00.000Z'),
|
||||
item: { id: 1, name: 'Test Item' },
|
||||
update: jest.fn().mockResolvedValue(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockRentalFindByPk.mockResolvedValue(mockRental);
|
||||
});
|
||||
|
||||
it('should return 404 for non-existent rental', async () => {
|
||||
mockRentalFindByPk.mockResolvedValue(null);
|
||||
|
||||
const response = await request(app)
|
||||
.post('/rentals/1/mark-return')
|
||||
.send({ status: 'returned' });
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
expect(response.body).toEqual({ error: 'Rental not found' });
|
||||
});
|
||||
|
||||
it('should return 403 for non-owner', async () => {
|
||||
const nonOwnerRental = { ...mockRental, ownerId: 3 };
|
||||
mockRentalFindByPk.mockResolvedValue(nonOwnerRental);
|
||||
|
||||
const response = await request(app)
|
||||
.post('/rentals/1/mark-return')
|
||||
.send({ status: 'returned' });
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
expect(response.body).toEqual({ error: 'Only the item owner can mark return status' });
|
||||
});
|
||||
|
||||
it('should return 400 for invalid status', async () => {
|
||||
const response = await request(app)
|
||||
.post('/rentals/1/mark-return')
|
||||
.send({ status: 'invalid_status' });
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(response.body.error).toContain('Invalid status');
|
||||
});
|
||||
|
||||
it('should return 400 for non-active rental', async () => {
|
||||
const completedRental = { ...mockRental, status: 'completed' };
|
||||
mockRentalFindByPk.mockResolvedValue(completedRental);
|
||||
|
||||
const response = await request(app)
|
||||
.post('/rentals/1/mark-return')
|
||||
.send({ status: 'returned' });
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(response.body.error).toContain('active rentals');
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /:id/payment-method', () => {
|
||||
const mockRental = {
|
||||
id: 1,
|
||||
ownerId: 2,
|
||||
renterId: 1,
|
||||
status: 'pending',
|
||||
paymentStatus: 'pending',
|
||||
stripePaymentMethodId: 'pm_old123',
|
||||
item: { id: 1, name: 'Test Item' },
|
||||
owner: { id: 2, firstName: 'John', email: 'john@example.com' },
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockRentalFindByPk.mockResolvedValue(mockRental);
|
||||
StripeService.getPaymentMethod = jest.fn().mockResolvedValue({
|
||||
id: 'pm_new123',
|
||||
customer: 'cus_test123',
|
||||
});
|
||||
User.findByPk = jest.fn().mockResolvedValue({
|
||||
id: 1,
|
||||
stripeCustomerId: 'cus_test123',
|
||||
});
|
||||
Rental.update = jest.fn().mockResolvedValue([1]);
|
||||
});
|
||||
|
||||
it('should update payment method successfully', async () => {
|
||||
const response = await request(app)
|
||||
.put('/rentals/1/payment-method')
|
||||
.send({ stripePaymentMethodId: 'pm_new123' });
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.success).toBe(true);
|
||||
});
|
||||
|
||||
it('should return 400 when payment method ID is missing', async () => {
|
||||
const response = await request(app)
|
||||
.put('/rentals/1/payment-method')
|
||||
.send({});
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(response.body).toEqual({ error: 'Payment method ID is required' });
|
||||
});
|
||||
|
||||
it('should return 404 for non-existent rental', async () => {
|
||||
mockRentalFindByPk.mockResolvedValue(null);
|
||||
|
||||
const response = await request(app)
|
||||
.put('/rentals/1/payment-method')
|
||||
.send({ stripePaymentMethodId: 'pm_new123' });
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
expect(response.body).toEqual({ error: 'Rental not found' });
|
||||
});
|
||||
|
||||
it('should return 403 for non-renter', async () => {
|
||||
const nonRenterRental = { ...mockRental, renterId: 3 };
|
||||
mockRentalFindByPk.mockResolvedValue(nonRenterRental);
|
||||
|
||||
const response = await request(app)
|
||||
.put('/rentals/1/payment-method')
|
||||
.send({ stripePaymentMethodId: 'pm_new123' });
|
||||
|
||||
expect(response.status).toBe(403);
|
||||
expect(response.body).toEqual({ error: 'Only the renter can update the payment method' });
|
||||
});
|
||||
|
||||
it('should return 400 for non-pending rental', async () => {
|
||||
const confirmedRental = { ...mockRental, status: 'confirmed' };
|
||||
mockRentalFindByPk.mockResolvedValue(confirmedRental);
|
||||
|
||||
const response = await request(app)
|
||||
.put('/rentals/1/payment-method')
|
||||
.send({ stripePaymentMethodId: 'pm_new123' });
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
expect(response.body).toEqual({ error: 'Can only update payment method for pending rentals' });
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user