const request = require('supertest'); const express = require('express'); // Mock dependencies jest.mock('../../../middleware/auth', () => ({ authenticateToken: (req, res, next) => { req.user = { id: 'user-123' }; next(); }, })); jest.mock('../../../services/conditionCheckService', () => ({ submitConditionCheck: jest.fn(), getConditionChecks: jest.fn(), getConditionCheckTimeline: jest.fn(), getAvailableChecks: jest.fn(), })); jest.mock('../../../utils/logger', () => ({ info: jest.fn(), error: jest.fn(), warn: jest.fn(), withRequestId: jest.fn(() => ({ info: jest.fn(), error: jest.fn(), warn: jest.fn(), })), })); jest.mock('../../../utils/s3KeyValidator', () => ({ validateS3Keys: jest.fn().mockReturnValue({ valid: true }), })); jest.mock('../../../config/imageLimits', () => ({ IMAGE_LIMITS: { conditionChecks: 10 }, })); const ConditionCheckService = require('../../../services/conditionCheckService'); const { validateS3Keys } = require('../../../utils/s3KeyValidator'); const conditionCheckRoutes = require('../../../routes/conditionChecks'); const app = express(); app.use(express.json()); app.use('/condition-checks', conditionCheckRoutes); // Error handler app.use((err, req, res, next) => { res.status(500).json({ error: err.message }); }); describe('Condition Check Routes', () => { beforeEach(() => { jest.clearAllMocks(); }); describe('POST /condition-checks/:rentalId', () => { const validConditionCheck = { checkType: 'pre_rental', notes: 'Item in good condition', imageFilenames: ['condition-checks/uuid1.jpg', 'condition-checks/uuid2.jpg'], }; it('should submit a condition check successfully', async () => { const mockConditionCheck = { id: 'check-1', rentalId: 'rental-123', checkType: 'pre_rental', notes: 'Item in good condition', imageFilenames: validConditionCheck.imageFilenames, submittedBy: 'user-123', createdAt: new Date().toISOString(), }; ConditionCheckService.submitConditionCheck.mockResolvedValue(mockConditionCheck); const response = await request(app) .post('/condition-checks/rental-123') .send(validConditionCheck); expect(response.status).toBe(201); expect(response.body.success).toBe(true); expect(response.body.conditionCheck).toMatchObject({ id: 'check-1', checkType: 'pre_rental', }); expect(ConditionCheckService.submitConditionCheck).toHaveBeenCalledWith( 'rental-123', 'pre_rental', 'user-123', validConditionCheck.imageFilenames, 'Item in good condition' ); }); it('should handle empty image array', async () => { const mockConditionCheck = { id: 'check-1', rentalId: 'rental-123', checkType: 'post_rental', imageFilenames: [], }; ConditionCheckService.submitConditionCheck.mockResolvedValue(mockConditionCheck); const response = await request(app) .post('/condition-checks/rental-123') .send({ checkType: 'post_rental', notes: 'No photos', }); expect(response.status).toBe(201); expect(response.body.success).toBe(true); expect(ConditionCheckService.submitConditionCheck).toHaveBeenCalledWith( 'rental-123', 'post_rental', 'user-123', [], 'No photos' ); }); it('should reject invalid S3 keys', async () => { validateS3Keys.mockReturnValueOnce({ valid: false, error: 'Invalid S3 key format', invalidKeys: ['invalid-key'], }); const response = await request(app) .post('/condition-checks/rental-123') .send({ checkType: 'pre_rental', imageFilenames: ['invalid-key'], }); expect(response.status).toBe(400); expect(response.body.success).toBe(false); expect(response.body.error).toBe('Invalid S3 key format'); expect(response.body.details).toContain('invalid-key'); }); it('should handle service errors', async () => { ConditionCheckService.submitConditionCheck.mockRejectedValue( new Error('Rental not found') ); const response = await request(app) .post('/condition-checks/rental-123') .send(validConditionCheck); expect(response.status).toBe(400); expect(response.body.success).toBe(false); expect(response.body.error).toBe('Rental not found'); }); it('should handle non-array imageFilenames gracefully', async () => { const mockConditionCheck = { id: 'check-1', rentalId: 'rental-123', checkType: 'pre_rental', imageFilenames: [], }; ConditionCheckService.submitConditionCheck.mockResolvedValue(mockConditionCheck); const response = await request(app) .post('/condition-checks/rental-123') .send({ checkType: 'pre_rental', imageFilenames: 'not-an-array', }); expect(response.status).toBe(201); // Should convert to empty array expect(ConditionCheckService.submitConditionCheck).toHaveBeenCalledWith( 'rental-123', 'pre_rental', 'user-123', [], undefined ); }); }); describe('GET /condition-checks/:rentalId', () => { it('should return condition checks for a rental', async () => { const mockChecks = [ { id: 'check-1', checkType: 'pre_rental', notes: 'Good condition', createdAt: '2024-01-01T00:00:00Z', }, { id: 'check-2', checkType: 'post_rental', notes: 'Minor wear', createdAt: '2024-01-15T00:00:00Z', }, ]; ConditionCheckService.getConditionChecks.mockResolvedValue(mockChecks); const response = await request(app) .get('/condition-checks/rental-123'); expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.conditionChecks).toHaveLength(2); expect(response.body.conditionChecks[0].checkType).toBe('pre_rental'); expect(ConditionCheckService.getConditionChecks).toHaveBeenCalledWith('rental-123'); }); it('should return empty array when no checks exist', async () => { ConditionCheckService.getConditionChecks.mockResolvedValue([]); const response = await request(app) .get('/condition-checks/rental-456'); expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.conditionChecks).toHaveLength(0); }); it('should handle service errors', async () => { ConditionCheckService.getConditionChecks.mockRejectedValue( new Error('Database error') ); const response = await request(app) .get('/condition-checks/rental-123'); expect(response.status).toBe(500); expect(response.body.success).toBe(false); expect(response.body.error).toBe('Failed to fetch condition checks'); }); }); describe('GET /condition-checks/:rentalId/timeline', () => { it('should return condition check timeline', async () => { const mockTimeline = { rental: { id: 'rental-123', status: 'completed' }, checks: [ { type: 'pre_rental', status: 'completed', completedAt: '2024-01-01' }, { type: 'post_rental', status: 'pending', completedAt: null }, ], }; ConditionCheckService.getConditionCheckTimeline.mockResolvedValue(mockTimeline); const response = await request(app) .get('/condition-checks/rental-123/timeline'); expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.timeline).toMatchObject(mockTimeline); expect(ConditionCheckService.getConditionCheckTimeline).toHaveBeenCalledWith('rental-123'); }); it('should handle service errors', async () => { ConditionCheckService.getConditionCheckTimeline.mockRejectedValue( new Error('Rental not found') ); const response = await request(app) .get('/condition-checks/rental-123/timeline'); expect(response.status).toBe(500); expect(response.body.success).toBe(false); expect(response.body.error).toBe('Rental not found'); }); }); describe('GET /condition-checks', () => { it('should return available checks for current user', async () => { const mockAvailableChecks = [ { rentalId: 'rental-1', itemName: 'Camera', checkType: 'pre_rental', dueDate: '2024-01-10', }, { rentalId: 'rental-2', itemName: 'Laptop', checkType: 'post_rental', dueDate: '2024-01-15', }, ]; ConditionCheckService.getAvailableChecks.mockResolvedValue(mockAvailableChecks); const response = await request(app) .get('/condition-checks'); expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.availableChecks).toHaveLength(2); expect(response.body.availableChecks[0].itemName).toBe('Camera'); expect(ConditionCheckService.getAvailableChecks).toHaveBeenCalledWith('user-123'); }); it('should return empty array when no checks available', async () => { ConditionCheckService.getAvailableChecks.mockResolvedValue([]); const response = await request(app) .get('/condition-checks'); expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.availableChecks).toHaveLength(0); }); it('should handle service errors', async () => { ConditionCheckService.getAvailableChecks.mockRejectedValue( new Error('Database error') ); const response = await request(app) .get('/condition-checks'); expect(response.status).toBe(500); expect(response.body.success).toBe(false); expect(response.body.error).toBe('Failed to fetch available checks'); }); }); });