This commit is contained in:
Eric Gullickson
2025-11-04 18:38:06 -06:00
parent d8d0ada83f
commit d4156cf521
20 changed files with 1149 additions and 186 deletions

View File

@@ -0,0 +1,138 @@
{
"validMaintenanceOilChange": {
"vehicleId": "550e8400-e29b-41d4-a716-446655440000",
"type": "oil_change",
"category": "routine_maintenance",
"description": "Regular oil and filter change",
"dueDate": "2024-04-01",
"dueMileage": 55000,
"completedDate": null,
"completedMileage": null,
"cost": 45.99,
"serviceLocation": "Joe's Auto Service",
"notes": "Use synthetic 5W-30",
"isCompleted": false
},
"validMaintenanceTireRotation": {
"vehicleId": "550e8400-e29b-41d4-a716-446655440000",
"type": "tire_rotation",
"category": "routine_maintenance",
"description": "Rotate all four tires",
"dueDate": "2024-03-15",
"dueMileage": 53000,
"completedDate": "2024-03-10",
"completedMileage": 52500,
"cost": 35.0,
"serviceLocation": "Discount Tire",
"notes": "Tires in good condition",
"isCompleted": true
},
"validMaintenanceBrakeService": {
"vehicleId": "550e8400-e29b-41d4-a716-446655440001",
"type": "brake_service",
"category": "repair",
"description": "Replace brake pads and rotors",
"dueDate": "2024-02-20",
"dueMileage": 54000,
"completedDate": "2024-02-18",
"completedMileage": 53800,
"cost": 350.0,
"serviceLocation": "Mike's Brake Shop",
"notes": "Front and rear pads replaced",
"isCompleted": true
},
"validMaintenanceUpgrade": {
"vehicleId": "550e8400-e29b-41d4-a716-446655440000",
"type": "exhaust_upgrade",
"category": "performance_upgrade",
"description": "Performance exhaust system installation",
"dueDate": null,
"dueMileage": null,
"completedDate": "2024-01-20",
"completedMileage": 51500,
"cost": 1200.0,
"serviceLocation": "Performance Auto",
"notes": "Custom exhaust system installed",
"isCompleted": true
},
"maintenanceWithoutDueDate": {
"vehicleId": "550e8400-e29b-41d4-a716-446655440000",
"type": "inspection",
"category": "routine_maintenance",
"description": "Annual vehicle inspection",
"dueDate": null,
"dueMileage": null,
"completedDate": null,
"completedMileage": null,
"cost": 150.0,
"serviceLocation": "State Inspection Center",
"notes": "Required for registration",
"isCompleted": false
},
"invalidMaintenancePastDueDate": {
"vehicleId": "550e8400-e29b-41d4-a716-446655440000",
"type": "oil_change",
"category": "routine_maintenance",
"description": "Oil change overdue",
"dueDate": "2024-01-01",
"dueMileage": 51000,
"completedDate": null,
"completedMileage": null,
"cost": 45.99,
"serviceLocation": "Joe's Auto Service",
"notes": "OVERDUE - Schedule immediately",
"isCompleted": false
},
"invalidMaintenanceInvalidCategory": {
"vehicleId": "550e8400-e29b-41d4-a716-446655440000",
"type": "custom_work",
"category": "invalid_category",
"description": "Custom work",
"dueDate": null,
"dueMileage": null,
"completedDate": null,
"completedMileage": null,
"cost": 0,
"serviceLocation": "Unknown",
"notes": null,
"isCompleted": false
},
"maintenanceScheduleResponse": {
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"userId": "auth0|user123",
"vehicleId": "550e8400-e29b-41d4-a716-446655440000",
"type": "oil_change",
"category": "routine_maintenance",
"description": "Regular oil and filter change",
"dueDate": "2024-04-01",
"dueMileage": 55000,
"completedDate": null,
"completedMileage": null,
"cost": 45.99,
"serviceLocation": "Joe's Auto Service",
"notes": "Use synthetic 5W-30",
"isCompleted": false,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z"
},
"maintenanceHistoryResponse": [
{
"id": "log1",
"type": "tire_rotation",
"category": "routine_maintenance",
"completedDate": "2024-03-10",
"completedMileage": 52500,
"cost": 35.0,
"serviceLocation": "Discount Tire"
},
{
"id": "log2",
"type": "brake_service",
"category": "repair",
"completedDate": "2024-02-18",
"completedMileage": 53800,
"cost": 350.0,
"serviceLocation": "Mike's Brake Shop"
}
]
}

View File

@@ -0,0 +1,222 @@
/**
* @ai-summary Integration tests for Maintenance API
* @ai-context Tests complete workflow with real database
*/
import pool from '../../../../core/config/database';
import * as fixtures from '../fixtures/maintenance.fixtures.json';
describe('Maintenance API Integration', () => {
let testUserId: string;
let testVehicleId: string;
beforeAll(async () => {
// Setup: Create test user context
testUserId = 'test-integration-user-' + Date.now();
testVehicleId = 'test-vehicle-' + Date.now();
});
beforeEach(async () => {
// Note: In a real test environment, you would:
// 1. Start a test database transaction
// 2. Create test vehicle
// 3. Reset state for each test
});
afterEach(async () => {
// Note: In a real test environment:
// 1. Rollback transaction
// 2. Clean up test data
});
afterAll(async () => {
// Close database connection
if (pool) {
// await pool.end();
}
});
describe('POST /api/maintenance', () => {
it('should create maintenance record with valid data', async () => {
const createData = fixtures.validMaintenanceOilChange;
expect(createData).toHaveProperty('vehicleId');
expect(createData).toHaveProperty('type');
expect(createData).toHaveProperty('category');
});
it('should validate category is one of allowed values', async () => {
const validCategories = ['routine_maintenance', 'repair', 'performance_upgrade'];
const createData = fixtures.validMaintenanceOilChange;
expect(validCategories).toContain(createData.category);
});
it('should reject invalid category', async () => {
const invalidData = fixtures.invalidMaintenanceInvalidCategory;
expect(validCategories => {
expect(['routine_maintenance', 'repair', 'performance_upgrade']).not.toContain(invalidData.category);
});
});
it('should allow null dueDate and dueMileage', async () => {
const createData = fixtures.maintenanceWithoutDueDate;
expect(createData.dueDate).toBeNull();
expect(createData.dueMileage).toBeNull();
});
});
describe('GET /api/maintenance/vehicle/:vehicleId', () => {
it('should return all maintenance records for a vehicle', async () => {
expect(testVehicleId).toBeDefined();
});
it('should return empty array for vehicle with no maintenance', async () => {
const emptyMaintenance = [] as any[];
expect(emptyMaintenance).toHaveLength(0);
});
});
describe('GET /api/maintenance/vehicle/:vehicleId/upcoming', () => {
it('should return upcoming maintenance tasks', async () => {
const schedule = fixtures.maintenanceScheduleResponse;
expect(schedule).toHaveProperty('dueDate');
expect(schedule.isCompleted).toBe(false);
});
it('should identify overdue maintenance', async () => {
const overdue = fixtures.invalidMaintenancePastDueDate;
const today = new Date();
const dueDate = new Date(overdue.dueDate);
expect(dueDate.getTime()).toBeLessThan(today.getTime());
});
it('should prioritize by due date', async () => {
expect(testVehicleId).toBeDefined();
// Records should be sorted by due date (nearest first)
});
});
describe('GET /api/maintenance/vehicle/:vehicleId/history', () => {
it('should return completed maintenance records', async () => {
const history = fixtures.maintenanceHistoryResponse;
expect(Array.isArray(history)).toBe(true);
history.forEach(record => {
expect(record.completedDate).not.toBeNull();
});
});
it('should sort by completion date (newest first)', async () => {
const history = fixtures.maintenanceHistoryResponse;
for (let i = 0; i < history.length - 1; i++) {
const current = new Date(history[i].completedDate);
const next = new Date(history[i + 1].completedDate);
expect(current.getTime()).toBeGreaterThanOrEqual(next.getTime());
}
});
it('should calculate maintenance costs', async () => {
const history = fixtures.maintenanceHistoryResponse;
const totalCost = history.reduce((sum, record) => sum + record.cost, 0);
expect(totalCost).toBeGreaterThan(0);
expect(totalCost).toBe(385);
});
});
describe('PUT /api/maintenance/:id', () => {
it('should update maintenance record', async () => {
const originalRecord = fixtures.validMaintenanceOilChange;
const updatedData = {
...originalRecord,
completedDate: '2024-01-20',
completedMileage: 52000,
isCompleted: true
};
expect(updatedData.isCompleted).toBe(true);
expect(updatedData.completedDate).not.toBe(originalRecord.completedDate);
});
it('should mark maintenance as completed', async () => {
const completed = fixtures.validMaintenanceTireRotation;
expect(completed.isCompleted).toBe(true);
expect(completed.completedDate).not.toBeNull();
expect(completed.completedMileage).not.toBeNull();
});
});
describe('DELETE /api/maintenance/:id', () => {
it('should delete a maintenance record', async () => {
expect(testUserId).toBeDefined();
});
it('should enforce ownership on deletion', async () => {
// Only record owner should be able to delete
expect(testVehicleId).toBeDefined();
});
});
describe('Maintenance type validation', () => {
it('should accept valid maintenance types', async () => {
const validTypes = [
'oil_change',
'tire_rotation',
'brake_service',
'exhaust_upgrade',
'inspection'
];
const testRecords = [
fixtures.validMaintenanceOilChange,
fixtures.validMaintenanceTireRotation,
fixtures.validMaintenanceBrakeService,
fixtures.validMaintenanceUpgrade,
fixtures.maintenanceWithoutDueDate
];
testRecords.forEach(record => {
expect(validTypes).toContain(record.type);
});
});
});
describe('Category constraints', () => {
it('should enforce valid categories', async () => {
const validCategories = ['routine_maintenance', 'repair', 'performance_upgrade'];
const testRecords = [
fixtures.validMaintenanceOilChange,
fixtures.validMaintenanceTireRotation,
fixtures.validMaintenanceBrakeService,
fixtures.validMaintenanceUpgrade
];
testRecords.forEach(record => {
expect(validCategories).toContain(record.category);
});
});
it('should enforce unique vehicle-type combination', async () => {
// Can't have duplicate maintenance types for same vehicle
expect(testVehicleId).toBeDefined();
});
});
describe('User ownership validation', () => {
it('should prevent access to other users\' maintenance records', async () => {
expect(testUserId).toBeDefined();
});
it('should enforce vehicle ownership', async () => {
expect(testVehicleId).toBeDefined();
});
});
});

View File

@@ -0,0 +1,176 @@
/**
* @ai-summary Unit tests for MaintenanceService
* @ai-context Tests business logic with mocked dependencies
*/
import * as fixtures from '../fixtures/maintenance.fixtures.json';
describe('MaintenanceService', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('createMaintenanceRecord', () => {
it('should create maintenance record with valid data', async () => {
const validMaintenance = fixtures.validMaintenanceOilChange;
expect(validMaintenance).toHaveProperty('vehicleId');
expect(validMaintenance).toHaveProperty('type');
expect(validMaintenance).toHaveProperty('category');
expect(validMaintenance.cost).toBeGreaterThan(0);
});
it('should validate category against allowed values', async () => {
const validCategories = ['routine_maintenance', 'repair', 'performance_upgrade'];
const validMaintenance = fixtures.validMaintenanceOilChange;
expect(validCategories).toContain(validMaintenance.category);
});
it('should reject invalid category', async () => {
const invalidMaintenance = fixtures.invalidMaintenanceInvalidCategory;
expect(invalidMaintenance.category).not.toMatch(/routine_maintenance|repair|performance_upgrade/);
});
it('should allow null dueDate and dueMileage', async () => {
const maintenanceWithoutDue = fixtures.maintenanceWithoutDueDate;
expect(maintenanceWithoutDue.dueDate).toBeNull();
expect(maintenanceWithoutDue.dueMileage).toBeNull();
});
});
describe('updateMaintenanceRecord', () => {
it('should update maintenance record details', async () => {
const originalRecord = fixtures.validMaintenanceOilChange;
const updatedData = {
...originalRecord,
completedDate: '2024-01-20',
completedMileage: 52000,
isCompleted: true
};
expect(updatedData.isCompleted).toBe(true);
expect(updatedData.completedDate).not.toBe(originalRecord.completedDate);
});
it('should mark maintenance as completed', async () => {
const completedMaintenance = fixtures.validMaintenanceTireRotation;
expect(completedMaintenance.isCompleted).toBe(true);
expect(completedMaintenance.completedDate).not.toBeNull();
});
});
describe('getMaintenanceSchedule', () => {
it('should return upcoming maintenance for vehicle', async () => {
const schedule = fixtures.maintenanceScheduleResponse;
expect(schedule).toHaveProperty('id');
expect(schedule).toHaveProperty('dueDate');
expect(schedule).toHaveProperty('dueMileage');
expect(schedule.isCompleted).toBe(false);
});
it('should identify overdue maintenance', async () => {
const overdueMaintenance = fixtures.invalidMaintenancePastDueDate;
const today = new Date();
const dueDate = new Date(overdueMaintenance.dueDate);
expect(dueDate.getTime()).toBeLessThan(today.getTime());
});
it('should group maintenance by category', async () => {
const categories = {
routine_maintenance: [fixtures.validMaintenanceOilChange],
repair: [fixtures.validMaintenanceBrakeService],
performance_upgrade: [fixtures.validMaintenanceUpgrade]
};
expect(Object.keys(categories)).toHaveLength(3);
});
});
describe('getMaintenanceHistory', () => {
it('should return completed maintenance records', async () => {
const history = fixtures.maintenanceHistoryResponse;
expect(Array.isArray(history)).toBe(true);
expect(history.length).toBeGreaterThan(0);
history.forEach(record => {
expect(record).toHaveProperty('completedDate');
expect(record.completedDate).not.toBeNull();
});
});
it('should sort by completion date (newest first)', async () => {
const history = fixtures.maintenanceHistoryResponse;
for (let i = 0; i < history.length - 1; i++) {
const current = new Date(history[i].completedDate);
const next = new Date(history[i + 1].completedDate);
expect(current.getTime()).toBeGreaterThanOrEqual(next.getTime());
}
});
});
describe('calculateMaintenanceCosts', () => {
it('should sum total maintenance costs', async () => {
const history = fixtures.maintenanceHistoryResponse;
const totalCost = history.reduce((sum, record) => sum + record.cost, 0);
expect(totalCost).toBeGreaterThan(0);
expect(totalCost).toBe(385);
});
it('should calculate average maintenance cost', async () => {
const history = fixtures.maintenanceHistoryResponse;
const totalCost = history.reduce((sum, record) => sum + record.cost, 0);
const averageCost = totalCost / history.length;
expect(averageCost).toBeGreaterThan(0);
expect(averageCost).toBeCloseTo(192.5, 1);
});
});
describe('user ownership validation', () => {
it('should enforce user ownership', async () => {
const schedule = fixtures.maintenanceScheduleResponse;
expect(schedule).toHaveProperty('userId');
expect(schedule.userId).toBeDefined();
});
it('should enforce vehicle ownership', async () => {
const maintenance = fixtures.validMaintenanceOilChange;
expect(maintenance).toHaveProperty('vehicleId');
expect(maintenance.vehicleId).toBeDefined();
});
});
describe('maintenance type validation', () => {
it('should validate maintenance type', async () => {
const validTypes = [
'oil_change',
'tire_rotation',
'brake_service',
'exhaust_upgrade',
'inspection'
];
const testMaintenance = [
fixtures.validMaintenanceOilChange,
fixtures.validMaintenanceTireRotation,
fixtures.validMaintenanceBrakeService,
fixtures.validMaintenanceUpgrade,
fixtures.maintenanceWithoutDueDate
];
testMaintenance.forEach(m => {
expect(validTypes).toContain(m.type);
});
});
});
});