more backend unit test coverage

This commit is contained in:
jackiettran
2026-01-18 19:18:35 -05:00
parent e6c56ae90f
commit 41d8cf4c04
18 changed files with 4961 additions and 1 deletions

View File

@@ -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' });
});
});
});