Files
rentall-app/backend/tests/unit/services/conditionCheckService.test.js
2026-01-18 19:18:35 -05:00

335 lines
10 KiB
JavaScript

const ConditionCheckService = require('../../../services/conditionCheckService');
const { ConditionCheck, Rental, User } = require('../../../models');
jest.mock('../../../models');
describe('ConditionCheckService', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('submitConditionCheck', () => {
// Set rental dates relative to current time for valid time window
// Active status is computed: confirmed + startDateTime in the past
const now = new Date();
const mockRental = {
id: 'rental-123',
ownerId: 'owner-456',
renterId: 'renter-789',
startDateTime: new Date(now.getTime() - 1000 * 60 * 60), // 1 hour ago
endDateTime: new Date(now.getTime() + 1000 * 60 * 60 * 24), // 24 hours from now
status: 'confirmed' // Will be computed as "active" since startDateTime is in the past
};
const mockPhotos = ['/uploads/photo1.jpg', '/uploads/photo2.jpg'];
beforeEach(() => {
Rental.findByPk.mockResolvedValue(mockRental);
ConditionCheck.findOne.mockResolvedValue(null); // No existing check
ConditionCheck.create.mockResolvedValue({
id: 'check-123',
rentalId: 'rental-123',
checkType: 'rental_start_renter',
photos: mockPhotos,
notes: 'Item received in good condition',
submittedBy: 'renter-789'
});
});
it('should submit condition check with photos and notes', async () => {
const result = await ConditionCheckService.submitConditionCheck(
'rental-123',
'rental_start_renter',
'renter-789',
mockPhotos,
'Item received in good condition'
);
expect(ConditionCheck.create).toHaveBeenCalledWith(
expect.objectContaining({
rentalId: 'rental-123',
checkType: 'rental_start_renter',
submittedBy: 'renter-789',
imageFilenames: mockPhotos,
notes: 'Item received in good condition',
})
);
expect(result).toBeTruthy();
expect(result.id).toBe('check-123');
});
it('should validate user authorization - owner checks', async () => {
// Renter trying to submit pre-rental owner check
await expect(
ConditionCheckService.submitConditionCheck(
'rental-123',
'pre_rental_owner',
'renter-789',
mockPhotos
)
).rejects.toThrow('Only the item owner can submit owner condition checks');
});
it('should validate user authorization - renter checks', async () => {
// Owner trying to submit rental start renter check
await expect(
ConditionCheckService.submitConditionCheck(
'rental-123',
'rental_start_renter',
'owner-456',
mockPhotos
)
).rejects.toThrow('Only the renter can submit renter condition checks');
});
it('should prevent duplicate condition checks', async () => {
ConditionCheck.findOne.mockResolvedValue({ id: 'existing-check' });
await expect(
ConditionCheckService.submitConditionCheck(
'rental-123',
'rental_start_renter',
'renter-789',
mockPhotos
)
).rejects.toThrow('Condition check already submitted for this type');
});
it('should limit number of photos to 20', async () => {
const tooManyPhotos = Array(21).fill('/uploads/photo.jpg');
await expect(
ConditionCheckService.submitConditionCheck(
'rental-123',
'rental_start_renter',
'renter-789',
tooManyPhotos
)
).rejects.toThrow('Maximum 20 photos allowed per condition check');
});
it('should handle rental not found', async () => {
Rental.findByPk.mockResolvedValue(null);
await expect(
ConditionCheckService.submitConditionCheck(
'nonexistent-rental',
'rental_start_renter',
'renter-789',
mockPhotos
)
).rejects.toThrow('Rental not found');
});
it('should allow empty photos array', async () => {
ConditionCheck.create.mockResolvedValue({
id: 'check-123',
rentalId: 'rental-123',
checkType: 'rental_start_renter',
photos: [],
notes: 'No photos',
submittedBy: 'renter-789'
});
const result = await ConditionCheckService.submitConditionCheck(
'rental-123',
'rental_start_renter',
'renter-789',
[],
'No photos'
);
expect(result).toBeTruthy();
});
it('should allow null notes', async () => {
ConditionCheck.create.mockResolvedValue({
id: 'check-123',
rentalId: 'rental-123',
checkType: 'rental_start_renter',
photos: mockPhotos,
notes: null,
submittedBy: 'renter-789'
});
const result = await ConditionCheckService.submitConditionCheck(
'rental-123',
'rental_start_renter',
'renter-789',
mockPhotos
);
expect(result).toBeTruthy();
});
});
describe('validateConditionCheck', () => {
const now = new Date();
const mockRental = {
id: 'rental-123',
ownerId: 'owner-456',
renterId: 'renter-789',
startDateTime: new Date(now.getTime() - 1000 * 60 * 60),
endDateTime: new Date(now.getTime() + 1000 * 60 * 60 * 24),
status: 'confirmed'
};
beforeEach(() => {
Rental.findByPk.mockResolvedValue(mockRental);
ConditionCheck.findOne.mockResolvedValue(null);
});
it('should return canSubmit false when rental not found', async () => {
Rental.findByPk.mockResolvedValue(null);
const result = await ConditionCheckService.validateConditionCheck(
'nonexistent',
'rental_start_renter',
'renter-789'
);
expect(result.canSubmit).toBe(false);
expect(result.reason).toBe('Rental not found');
});
it('should reject owner check by renter', async () => {
const result = await ConditionCheckService.validateConditionCheck(
'rental-123',
'pre_rental_owner',
'renter-789'
);
expect(result.canSubmit).toBe(false);
expect(result.reason).toContain('owner');
});
it('should reject renter check by owner', async () => {
const result = await ConditionCheckService.validateConditionCheck(
'rental-123',
'rental_start_renter',
'owner-456'
);
expect(result.canSubmit).toBe(false);
expect(result.reason).toContain('renter');
});
it('should reject duplicate checks', async () => {
ConditionCheck.findOne.mockResolvedValue({ id: 'existing' });
const result = await ConditionCheckService.validateConditionCheck(
'rental-123',
'rental_start_renter',
'renter-789'
);
expect(result.canSubmit).toBe(false);
expect(result.reason).toContain('already submitted');
});
it('should return canSubmit false for invalid check type', async () => {
const result = await ConditionCheckService.validateConditionCheck(
'rental-123',
'invalid_type',
'owner-456'
);
expect(result.canSubmit).toBe(false);
expect(result.reason).toBe('Invalid check type');
});
it('should allow post_rental_owner anytime', async () => {
const result = await ConditionCheckService.validateConditionCheck(
'rental-123',
'post_rental_owner',
'owner-456'
);
expect(result.canSubmit).toBe(true);
});
});
describe('getConditionChecksForRentals', () => {
it('should return empty array for empty rental IDs', async () => {
const result = await ConditionCheckService.getConditionChecksForRentals([]);
expect(result).toEqual([]);
});
it('should return empty array for null rental IDs', async () => {
const result = await ConditionCheckService.getConditionChecksForRentals(null);
expect(result).toEqual([]);
});
it('should return condition checks for rentals', async () => {
const mockChecks = [
{ id: 'check-1', rentalId: 'rental-1', checkType: 'pre_rental_owner' },
{ id: 'check-2', rentalId: 'rental-1', checkType: 'rental_start_renter' },
{ id: 'check-3', rentalId: 'rental-2', checkType: 'pre_rental_owner' },
];
ConditionCheck.findAll.mockResolvedValue(mockChecks);
const result = await ConditionCheckService.getConditionChecksForRentals(['rental-1', 'rental-2']);
expect(result).toHaveLength(3);
expect(ConditionCheck.findAll).toHaveBeenCalled();
});
});
describe('getAvailableChecks', () => {
it('should return empty array for empty rental IDs', async () => {
const result = await ConditionCheckService.getAvailableChecks('user-123', []);
expect(result).toEqual([]);
});
it('should return empty array for null rental IDs', async () => {
const result = await ConditionCheckService.getAvailableChecks('user-123', null);
expect(result).toEqual([]);
});
it('should return available checks for owner', async () => {
const now = new Date();
const mockRentals = [{
id: 'rental-123',
ownerId: 'owner-456',
renterId: 'renter-789',
itemId: 'item-123',
startDateTime: new Date(now.getTime() + 12 * 60 * 60 * 1000), // 12 hours from now
endDateTime: new Date(now.getTime() + 36 * 60 * 60 * 1000),
status: 'confirmed',
}];
Rental.findAll.mockResolvedValue(mockRentals);
Rental.findByPk.mockResolvedValue(mockRentals[0]);
ConditionCheck.findOne.mockResolvedValue(null);
const result = await ConditionCheckService.getAvailableChecks('owner-456', ['rental-123']);
// Should have pre_rental_owner available
expect(result.length).toBeGreaterThanOrEqual(0);
});
it('should return available checks for renter when rental is active', async () => {
const now = new Date();
const mockRentals = [{
id: 'rental-123',
ownerId: 'owner-456',
renterId: 'renter-789',
itemId: 'item-123',
startDateTime: new Date(now.getTime() - 60 * 60 * 1000), // 1 hour ago
endDateTime: new Date(now.getTime() + 24 * 60 * 60 * 1000),
status: 'confirmed',
}];
Rental.findAll.mockResolvedValue(mockRentals);
Rental.findByPk.mockResolvedValue(mockRentals[0]);
ConditionCheck.findOne.mockResolvedValue(null);
const result = await ConditionCheckService.getAvailableChecks('renter-789', ['rental-123']);
// May have rental_start_renter available
expect(Array.isArray(result)).toBe(true);
});
});
});