329 lines
10 KiB
JavaScript
329 lines
10 KiB
JavaScript
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');
|
|
});
|
|
});
|
|
});
|