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