781 lines
25 KiB
Markdown
781 lines
25 KiB
Markdown
# Backend Vehicles API Update - Agent 4
|
|
|
|
## Task: Update Vehicles API to use string-based parameters and responses
|
|
|
|
**Status**: Ready for Implementation
|
|
**Dependencies**: Agent 3 (Platform Service) must be complete
|
|
**Estimated Time**: 1-2 hours
|
|
**Assigned To**: Agent 4 (Vehicles API)
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
Update the vehicles controller and service to accept string parameters (make, model, trim) instead of IDs, and return string arrays instead of objects. This is the final backend change before frontend updates.
|
|
|
|
---
|
|
|
|
## Prerequisites
|
|
|
|
### Verify Agent 3 Completed
|
|
```bash
|
|
# Verify service compiles
|
|
cd backend && npm run build
|
|
|
|
# Should see no errors in vehicle-data.service.ts
|
|
```
|
|
|
|
### Files to Modify
|
|
```
|
|
backend/src/features/vehicles/api/vehicles.controller.ts
|
|
backend/src/features/vehicles/domain/vehicles.service.ts
|
|
backend/src/features/vehicles/types/index.ts (query parameter types)
|
|
```
|
|
|
|
---
|
|
|
|
## Current Implementation Analysis
|
|
|
|
### Controller
|
|
|
|
**File**: `backend/src/features/vehicles/api/vehicles.controller.ts`
|
|
|
|
**Current Query Parameters** (ID-based):
|
|
```typescript
|
|
getDropdownModels: { year: number; make_id: number }
|
|
getDropdownTrims: { year: number; make_id: number; model_id: number }
|
|
getDropdownEngines: { year: number; make_id: number; model_id: number; trim_id: number }
|
|
getDropdownTransmissions: { year: number; make_id: number; model_id: number }
|
|
```
|
|
|
|
### Service
|
|
|
|
**File**: `backend/src/features/vehicles/domain/vehicles.service.ts`
|
|
|
|
**Current Methods**:
|
|
```typescript
|
|
getDropdownMakes(year: number): Promise<{ id: number; name: string }[]>
|
|
getDropdownModels(year: number, makeId: number): Promise<{ id: number; name: string }[]>
|
|
getDropdownTrims(year: number, makeId: number, modelId: number): Promise<{ id: number; name: string }[]>
|
|
getDropdownEngines(year: number, makeId: number, modelId: number, trimId: number): Promise<{ id: number; name: string }[]>
|
|
getDropdownTransmissions(_year: number, _makeId: number, _modelId: number): Promise<{ id: number; name: string }[]> // Hardcoded!
|
|
getDropdownYears(): Promise<number[]>
|
|
```
|
|
|
|
---
|
|
|
|
## Part 1: Update Vehicles Service
|
|
|
|
### Step 1: Update getDropdownMakes()
|
|
|
|
**Current** (lines 165-171):
|
|
```typescript
|
|
async getDropdownMakes(year: number): Promise<{ id: number; name: string }[]> {
|
|
const vehicleDataService = getVehicleDataService();
|
|
const pool = getPool();
|
|
|
|
logger.info('Fetching dropdown makes via platform module', { year });
|
|
return vehicleDataService.getMakes(pool, year);
|
|
}
|
|
```
|
|
|
|
**New**:
|
|
```typescript
|
|
async getDropdownMakes(year: number): Promise<string[]> {
|
|
const vehicleDataService = getVehicleDataService();
|
|
const pool = getPool();
|
|
|
|
logger.info('Fetching dropdown makes via platform module', { year });
|
|
return vehicleDataService.getMakes(pool, year);
|
|
}
|
|
```
|
|
|
|
**Changes**:
|
|
- Line 165: Return type `Promise<{ id: number; name: string }[]>` → `Promise<string[]>`
|
|
- Logic unchanged (platform service now returns string[])
|
|
|
|
---
|
|
|
|
### Step 2: Update getDropdownModels()
|
|
|
|
**Current** (lines 173-179):
|
|
```typescript
|
|
async getDropdownModels(year: number, makeId: number): Promise<{ id: number; name: string }[]> {
|
|
const vehicleDataService = getVehicleDataService();
|
|
const pool = getPool();
|
|
|
|
logger.info('Fetching dropdown models via platform module', { year, makeId });
|
|
return vehicleDataService.getModels(pool, year, makeId);
|
|
}
|
|
```
|
|
|
|
**New**:
|
|
```typescript
|
|
async getDropdownModels(year: number, make: string): Promise<string[]> {
|
|
const vehicleDataService = getVehicleDataService();
|
|
const pool = getPool();
|
|
|
|
logger.info('Fetching dropdown models via platform module', { year, make });
|
|
return vehicleDataService.getModels(pool, year, make);
|
|
}
|
|
```
|
|
|
|
**Changes**:
|
|
- Line 173: Parameter `makeId: number` → `make: string`
|
|
- Line 173: Return type changed to `Promise<string[]>`
|
|
- Line 177: Logger uses `make` instead of `makeId`
|
|
- Line 178: Service call uses `make` instead of `makeId`
|
|
|
|
---
|
|
|
|
### Step 3: Update getDropdownTrims()
|
|
|
|
**Current** (lines 197-203):
|
|
```typescript
|
|
async getDropdownTrims(year: number, makeId: number, modelId: number): Promise<{ id: number; name: string }[]> {
|
|
const vehicleDataService = getVehicleDataService();
|
|
const pool = getPool();
|
|
|
|
logger.info('Fetching dropdown trims via platform module', { year, makeId, modelId });
|
|
return vehicleDataService.getTrims(pool, year, modelId);
|
|
}
|
|
```
|
|
|
|
**New**:
|
|
```typescript
|
|
async getDropdownTrims(year: number, make: string, model: string): Promise<string[]> {
|
|
const vehicleDataService = getVehicleDataService();
|
|
const pool = getPool();
|
|
|
|
logger.info('Fetching dropdown trims via platform module', { year, make, model });
|
|
return vehicleDataService.getTrims(pool, year, make, model);
|
|
}
|
|
```
|
|
|
|
**Changes**:
|
|
- Line 197: Parameters `makeId: number, modelId: number` → `make: string, model: string`
|
|
- Line 197: Return type changed to `Promise<string[]>`
|
|
- Line 201: Logger uses `make, model` instead of `makeId, modelId`
|
|
- Line 202: Service call uses `make, model` instead of `modelId`
|
|
|
|
---
|
|
|
|
### Step 4: Update getDropdownEngines()
|
|
|
|
**Current** (lines 189-195):
|
|
```typescript
|
|
async getDropdownEngines(year: number, makeId: number, modelId: number, trimId: number): Promise<{ id: number; name: string }[]> {
|
|
const vehicleDataService = getVehicleDataService();
|
|
const pool = getPool();
|
|
|
|
logger.info('Fetching dropdown engines via platform module', { year, makeId, modelId, trimId });
|
|
return vehicleDataService.getEngines(pool, year, modelId, trimId);
|
|
}
|
|
```
|
|
|
|
**New**:
|
|
```typescript
|
|
async getDropdownEngines(year: number, make: string, model: string, trim: string): Promise<string[]> {
|
|
const vehicleDataService = getVehicleDataService();
|
|
const pool = getPool();
|
|
|
|
logger.info('Fetching dropdown engines via platform module', { year, make, model, trim });
|
|
return vehicleDataService.getEngines(pool, year, make, model, trim);
|
|
}
|
|
```
|
|
|
|
**Changes**:
|
|
- Line 189: Parameters changed to strings: `make: string, model: string, trim: string`
|
|
- Line 189: Return type changed to `Promise<string[]>`
|
|
- Line 193: Logger uses string names
|
|
- Line 194: Service call uses all string parameters
|
|
|
|
---
|
|
|
|
### Step 5: Update getDropdownTransmissions() - CRITICAL
|
|
|
|
**Current** (lines 181-187):
|
|
```typescript
|
|
async getDropdownTransmissions(_year: number, _makeId: number, _modelId: number): Promise<{ id: number; name: string }[]> {
|
|
logger.info('Providing dropdown transmissions from static list');
|
|
return [
|
|
{ id: 1, name: 'Automatic' },
|
|
{ id: 2, name: 'Manual' }
|
|
];
|
|
}
|
|
```
|
|
|
|
**New**:
|
|
```typescript
|
|
async getDropdownTransmissions(year: number, make: string, model: string): Promise<string[]> {
|
|
const vehicleDataService = getVehicleDataService();
|
|
const pool = getPool();
|
|
|
|
logger.info('Fetching dropdown transmissions via platform module', { year, make, model });
|
|
return vehicleDataService.getTransmissions(pool, year, make, model);
|
|
}
|
|
```
|
|
|
|
**Changes**:
|
|
- Line 181: Parameters changed from IDs to strings: `make: string, model: string`
|
|
- Line 181: Return type changed to `Promise<string[]>`
|
|
- Line 182-186: **REMOVED** hardcoded static list
|
|
- Now calls platform service for real transmission data
|
|
|
|
---
|
|
|
|
## Part 2: Update Vehicles Controller
|
|
|
|
### Step 6: Update getDropdownMakes()
|
|
|
|
**Current** (lines 153-172):
|
|
```typescript
|
|
async getDropdownMakes(request: FastifyRequest<{ Querystring: { year: number } }>, reply: FastifyReply) {
|
|
try {
|
|
const { year } = request.query;
|
|
if (!year || year < 1980 || year > new Date().getFullYear() + 1) {
|
|
return reply.code(400).send({
|
|
error: 'Bad Request',
|
|
message: 'Valid year parameter is required (1980-' + (new Date().getFullYear() + 1) + ')'
|
|
});
|
|
}
|
|
|
|
const makes = await this.vehiclesService.getDropdownMakes(year);
|
|
return reply.code(200).send(makes);
|
|
} catch (error) {
|
|
logger.error('Error getting dropdown makes', { error, year: request.query?.year });
|
|
return reply.code(500).send({
|
|
error: 'Internal server error',
|
|
message: 'Failed to get makes'
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
**New**: (Unchanged - already returns array, validation unchanged)
|
|
|
|
**Note**: No changes needed - query parameter is already just `year`, and service now returns `string[]` which gets passed through directly.
|
|
|
|
---
|
|
|
|
### Step 7: Update getDropdownModels()
|
|
|
|
**Current** (lines 174-193):
|
|
```typescript
|
|
async getDropdownModels(request: FastifyRequest<{ Querystring: { year: number; make_id: number } }>, reply: FastifyReply) {
|
|
try {
|
|
const { year, make_id } = request.query;
|
|
if (!year || !make_id || year < 1980 || year > new Date().getFullYear() + 1 || make_id < 1) {
|
|
return reply.code(400).send({
|
|
error: 'Bad Request',
|
|
message: 'Valid year and make_id parameters are required'
|
|
});
|
|
}
|
|
|
|
const models = await this.vehiclesService.getDropdownModels(year, make_id);
|
|
return reply.code(200).send(models);
|
|
} catch (error) {
|
|
logger.error('Error getting dropdown models', { error, year: request.query?.year, make_id: request.query?.make_id });
|
|
return reply.code(500).send({
|
|
error: 'Internal server error',
|
|
message: 'Failed to get models'
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
**New**:
|
|
```typescript
|
|
async getDropdownModels(request: FastifyRequest<{ Querystring: { year: number; make: string } }>, reply: FastifyReply) {
|
|
try {
|
|
const { year, make } = request.query;
|
|
if (!year || !make || year < 1980 || year > new Date().getFullYear() + 1 || make.trim().length === 0) {
|
|
return reply.code(400).send({
|
|
error: 'Bad Request',
|
|
message: 'Valid year and make parameters are required'
|
|
});
|
|
}
|
|
|
|
const models = await this.vehiclesService.getDropdownModels(year, make);
|
|
return reply.code(200).send(models);
|
|
} catch (error) {
|
|
logger.error('Error getting dropdown models', { error, year: request.query?.year, make: request.query?.make });
|
|
return reply.code(500).send({
|
|
error: 'Internal server error',
|
|
message: 'Failed to get models'
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
**Changes**:
|
|
- Line 174: Querystring type `make_id: number` → `make: string`
|
|
- Line 176: Destructure `make` instead of `make_id`
|
|
- Line 177: Validation changed from `make_id < 1` to `make.trim().length === 0`
|
|
- Line 180: Error message updated
|
|
- Line 184: Service call uses `make` string
|
|
- Line 187: Logger uses `make`
|
|
|
|
---
|
|
|
|
### Step 8: Update getDropdownTrims()
|
|
|
|
**Current** (lines 237-256):
|
|
```typescript
|
|
async getDropdownTrims(request: FastifyRequest<{ Querystring: { year: number; make_id: number; model_id: number } }>, reply: FastifyReply) {
|
|
try {
|
|
const { year, make_id, model_id } = request.query;
|
|
if (!year || !make_id || !model_id || year < 1980 || year > new Date().getFullYear() + 1 || make_id < 1 || model_id < 1) {
|
|
return reply.code(400).send({
|
|
error: 'Bad Request',
|
|
message: 'Valid year, make_id, and model_id parameters are required'
|
|
});
|
|
}
|
|
|
|
const trims = await this.vehiclesService.getDropdownTrims(year, make_id, model_id);
|
|
return reply.code(200).send(trims);
|
|
} catch (error) {
|
|
logger.error('Error getting dropdown trims', { error, year: request.query?.year, make_id: request.query?.make_id, model_id: request.query?.model_id });
|
|
return reply.code(500).send({
|
|
error: 'Internal server error',
|
|
message: 'Failed to get trims'
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
**New**:
|
|
```typescript
|
|
async getDropdownTrims(request: FastifyRequest<{ Querystring: { year: number; make: string; model: string } }>, reply: FastifyReply) {
|
|
try {
|
|
const { year, make, model } = request.query;
|
|
if (!year || !make || !model || year < 1980 || year > new Date().getFullYear() + 1 || make.trim().length === 0 || model.trim().length === 0) {
|
|
return reply.code(400).send({
|
|
error: 'Bad Request',
|
|
message: 'Valid year, make, and model parameters are required'
|
|
});
|
|
}
|
|
|
|
const trims = await this.vehiclesService.getDropdownTrims(year, make, model);
|
|
return reply.code(200).send(trims);
|
|
} catch (error) {
|
|
logger.error('Error getting dropdown trims', { error, year: request.query?.year, make: request.query?.make, model: request.query?.model });
|
|
return reply.code(500).send({
|
|
error: 'Internal server error',
|
|
message: 'Failed to get trims'
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
**Changes**:
|
|
- Line 237: Querystring types changed to `make: string; model: string`
|
|
- Line 239: Destructure string parameters
|
|
- Line 240: Validation uses `.trim().length === 0` instead of `< 1`
|
|
- Line 243: Error message updated
|
|
- Line 247: Service call uses strings
|
|
- Line 250: Logger uses strings
|
|
|
|
---
|
|
|
|
### Step 9: Update getDropdownEngines()
|
|
|
|
**Current** (lines 216-235):
|
|
```typescript
|
|
async getDropdownEngines(request: FastifyRequest<{ Querystring: { year: number; make_id: number; model_id: number; trim_id: number } }>, reply: FastifyReply) {
|
|
try {
|
|
const { year, make_id, model_id, trim_id } = request.query;
|
|
if (!year || !make_id || !model_id || !trim_id || year < 1980 || year > new Date().getFullYear() + 1 || make_id < 1 || model_id < 1 || trim_id < 1) {
|
|
return reply.code(400).send({
|
|
error: 'Bad Request',
|
|
message: 'Valid year, make_id, model_id, and trim_id parameters are required'
|
|
});
|
|
}
|
|
|
|
const engines = await this.vehiclesService.getDropdownEngines(year, make_id, model_id, trim_id);
|
|
return reply.code(200).send(engines);
|
|
} catch (error) {
|
|
logger.error('Error getting dropdown engines', { error, year: request.query?.year, make_id: request.query?.make_id, model_id: request.query?.model_id, trim_id: request.query?.trim_id });
|
|
return reply.code(500).send({
|
|
error: 'Internal server error',
|
|
message: 'Failed to get engines'
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
**New**:
|
|
```typescript
|
|
async getDropdownEngines(request: FastifyRequest<{ Querystring: { year: number; make: string; model: string; trim: string } }>, reply: FastifyReply) {
|
|
try {
|
|
const { year, make, model, trim } = request.query;
|
|
if (!year || !make || !model || !trim || year < 1980 || year > new Date().getFullYear() + 1 || make.trim().length === 0 || model.trim().length === 0 || trim.trim().length === 0) {
|
|
return reply.code(400).send({
|
|
error: 'Bad Request',
|
|
message: 'Valid year, make, model, and trim parameters are required'
|
|
});
|
|
}
|
|
|
|
const engines = await this.vehiclesService.getDropdownEngines(year, make, model, trim);
|
|
return reply.code(200).send(engines);
|
|
} catch (error) {
|
|
logger.error('Error getting dropdown engines', { error, year: request.query?.year, make: request.query?.make, model: request.query?.model, trim: request.query?.trim });
|
|
return reply.code(500).send({
|
|
error: 'Internal server error',
|
|
message: 'Failed to get engines'
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
**Changes**:
|
|
- Line 216: Querystring types changed to strings
|
|
- Line 218: Destructure string parameters
|
|
- Line 219: Validation uses `.trim().length === 0`
|
|
- Line 222: Error message updated
|
|
- Line 226: Service call uses strings
|
|
- Line 229: Logger uses strings
|
|
|
|
---
|
|
|
|
### Step 10: Update getDropdownTransmissions()
|
|
|
|
**Current** (lines 195-214):
|
|
```typescript
|
|
async getDropdownTransmissions(request: FastifyRequest<{ Querystring: { year: number; make_id: number; model_id: number } }>, reply: FastifyReply) {
|
|
try {
|
|
const { year, make_id, model_id } = request.query;
|
|
if (!year || !make_id || !model_id || year < 1980 || year > new Date().getFullYear() + 1 || make_id < 1 || model_id < 1) {
|
|
return reply.code(400).send({
|
|
error: 'Bad Request',
|
|
message: 'Valid year, make_id, and model_id parameters are required'
|
|
});
|
|
}
|
|
|
|
const transmissions = await this.vehiclesService.getDropdownTransmissions(year, make_id, model_id);
|
|
return reply.code(200).send(transmissions);
|
|
} catch (error) {
|
|
logger.error('Error getting dropdown transmissions', { error, year: request.query?.year, make_id: request.query?.make_id, model_id: request.query?.model_id });
|
|
return reply.code(500).send({
|
|
error: 'Internal server error',
|
|
message: 'Failed to get transmissions'
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
**New**:
|
|
```typescript
|
|
async getDropdownTransmissions(request: FastifyRequest<{ Querystring: { year: number; make: string; model: string } }>, reply: FastifyReply) {
|
|
try {
|
|
const { year, make, model } = request.query;
|
|
if (!year || !make || !model || year < 1980 || year > new Date().getFullYear() + 1 || make.trim().length === 0 || model.trim().length === 0) {
|
|
return reply.code(400).send({
|
|
error: 'Bad Request',
|
|
message: 'Valid year, make, and model parameters are required'
|
|
});
|
|
}
|
|
|
|
const transmissions = await this.vehiclesService.getDropdownTransmissions(year, make, model);
|
|
return reply.code(200).send(transmissions);
|
|
} catch (error) {
|
|
logger.error('Error getting dropdown transmissions', { error, year: request.query?.year, make: request.query?.make, model: request.query?.model });
|
|
return reply.code(500).send({
|
|
error: 'Internal server error',
|
|
message: 'Failed to get transmissions'
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
**Changes**:
|
|
- Line 195: Querystring types changed to strings
|
|
- Line 197: Destructure string parameters
|
|
- Line 198: Validation uses `.trim().length === 0`
|
|
- Line 201: Error message updated
|
|
- Line 205: Service call uses strings (now fetches real data!)
|
|
- Line 208: Logger uses strings
|
|
|
|
---
|
|
|
|
## Testing & Verification
|
|
|
|
### Manual API Testing
|
|
|
|
Test each endpoint with the new string-based parameters:
|
|
|
|
```bash
|
|
# Assuming backend is running on localhost:3000
|
|
|
|
# Test getMakes
|
|
curl -X GET "http://localhost:3000/api/vehicles/dropdown/makes?year=2024" \
|
|
-H "Authorization: Bearer YOUR_TOKEN"
|
|
# Expected: ["Acura", "Audi", "BMW", "Ford", "Honda", ...]
|
|
|
|
# Test getModels (note: make parameter, not make_id)
|
|
curl -X GET "http://localhost:3000/api/vehicles/dropdown/models?year=2024&make=Ford" \
|
|
-H "Authorization: Bearer YOUR_TOKEN"
|
|
# Expected: ["Bronco", "Edge", "Escape", "Explorer", "F-150", "Mustang", ...]
|
|
|
|
# Test getTrims
|
|
curl -X GET "http://localhost:3000/api/vehicles/dropdown/trims?year=2024&make=Ford&model=F-150" \
|
|
-H "Authorization: Bearer YOUR_TOKEN"
|
|
# Expected: ["King Ranch", "Lariat", "Limited", "Platinum", "XL", "XLT", ...]
|
|
|
|
# Test getEngines
|
|
curl -X GET "http://localhost:3000/api/vehicles/dropdown/engines?year=2024&make=Ford&model=F-150&trim=XLT" \
|
|
-H "Authorization: Bearer YOUR_TOKEN"
|
|
# Expected: ["V6 2.7L Turbo", "V6 3.5L Turbo", "V8 5.0L", ...]
|
|
|
|
# Test getTransmissions (should return REAL data now, not hardcoded)
|
|
curl -X GET "http://localhost:3000/api/vehicles/dropdown/transmissions?year=2024&make=Ford&model=F-150" \
|
|
-H "Authorization: Bearer YOUR_TOKEN"
|
|
# Expected: ["10-Speed Automatic", ...] NOT ["Automatic", "Manual"]
|
|
|
|
# Test with Tesla (electric vehicle - should show N/A for engine)
|
|
curl -X GET "http://localhost:3000/api/vehicles/dropdown/engines?year=2024&make=Tesla&model=Model 3&trim=Long Range" \
|
|
-H "Authorization: Bearer YOUR_TOKEN"
|
|
# Expected: ["N/A (Electric)"]
|
|
```
|
|
|
|
### Test Error Handling
|
|
|
|
```bash
|
|
# Test missing make parameter
|
|
curl -X GET "http://localhost:3000/api/vehicles/dropdown/models?year=2024" \
|
|
-H "Authorization: Bearer YOUR_TOKEN"
|
|
# Expected: 400 Bad Request
|
|
|
|
# Test empty make parameter
|
|
curl -X GET "http://localhost:3000/api/vehicles/dropdown/models?year=2024&make=" \
|
|
-H "Authorization: Bearer YOUR_TOKEN"
|
|
# Expected: 400 Bad Request
|
|
|
|
# Test invalid year
|
|
curl -X GET "http://localhost:3000/api/vehicles/dropdown/makes?year=1900" \
|
|
-H "Authorization: Bearer YOUR_TOKEN"
|
|
# Expected: 400 Bad Request
|
|
```
|
|
|
|
### Integration Tests
|
|
|
|
Create automated tests:
|
|
|
|
```typescript
|
|
// backend/src/features/vehicles/tests/integration/dropdown-api.test.ts
|
|
|
|
describe('Dropdown API Endpoints', () => {
|
|
let app: FastifyInstance;
|
|
let authToken: string;
|
|
|
|
beforeAll(async () => {
|
|
app = await createTestApp();
|
|
authToken = await getTestAuthToken();
|
|
});
|
|
|
|
describe('GET /api/vehicles/dropdown/makes', () => {
|
|
it('should return string array of makes', async () => {
|
|
const response = await app.inject({
|
|
method: 'GET',
|
|
url: '/api/vehicles/dropdown/makes?year=2024',
|
|
headers: { Authorization: `Bearer ${authToken}` }
|
|
});
|
|
|
|
expect(response.statusCode).toBe(200);
|
|
const makes = JSON.parse(response.body);
|
|
expect(Array.isArray(makes)).toBe(true);
|
|
expect(typeof makes[0]).toBe('string');
|
|
expect(makes).toContain('Ford');
|
|
});
|
|
});
|
|
|
|
describe('GET /api/vehicles/dropdown/models', () => {
|
|
it('should accept make string parameter', async () => {
|
|
const response = await app.inject({
|
|
method: 'GET',
|
|
url: '/api/vehicles/dropdown/models?year=2024&make=Ford',
|
|
headers: { Authorization: `Bearer ${authToken}` }
|
|
});
|
|
|
|
expect(response.statusCode).toBe(200);
|
|
const models = JSON.parse(response.body);
|
|
expect(Array.isArray(models)).toBe(true);
|
|
expect(typeof models[0]).toBe('string');
|
|
expect(models).toContain('F-150');
|
|
});
|
|
|
|
it('should return 400 for missing make', async () => {
|
|
const response = await app.inject({
|
|
method: 'GET',
|
|
url: '/api/vehicles/dropdown/models?year=2024',
|
|
headers: { Authorization: `Bearer ${authToken}` }
|
|
});
|
|
|
|
expect(response.statusCode).toBe(400);
|
|
});
|
|
});
|
|
|
|
describe('GET /api/vehicles/dropdown/transmissions', () => {
|
|
it('should return real transmission data (not hardcoded)', async () => {
|
|
const response = await app.inject({
|
|
method: 'GET',
|
|
url: '/api/vehicles/dropdown/transmissions?year=2024&make=Ford&model=F-150',
|
|
headers: { Authorization: `Bearer ${authToken}` }
|
|
});
|
|
|
|
expect(response.statusCode).toBe(200);
|
|
const transmissions = JSON.parse(response.body);
|
|
expect(Array.isArray(transmissions)).toBe(true);
|
|
expect(transmissions.length).toBeGreaterThan(0);
|
|
|
|
// Should NOT be the old hardcoded values
|
|
const hasDetailedTransmissions = transmissions.some((t: string) =>
|
|
t.includes('Speed') || t.includes('CVT')
|
|
);
|
|
expect(hasDetailedTransmissions).toBe(true);
|
|
});
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Completion Checklist
|
|
|
|
Before signaling completion:
|
|
|
|
- [ ] Service methods updated (all use string parameters)
|
|
- [ ] Service return types changed to string[]
|
|
- [ ] getDropdownTransmissions() now fetches real data (not hardcoded)
|
|
- [ ] Controller query parameter types updated (make, model, trim not IDs)
|
|
- [ ] Controller validation updated (string length checks)
|
|
- [ ] Controller error messages updated
|
|
- [ ] All manual API tests pass
|
|
- [ ] Integration tests pass
|
|
- [ ] TypeScript compiles with no errors
|
|
- [ ] Backend builds successfully: `npm run build`
|
|
- [ ] No console errors in logs
|
|
|
|
---
|
|
|
|
## Common Issues
|
|
|
|
### Issue: "Cannot read property 'trim' of undefined"
|
|
**Cause**: Query parameters might be undefined if not provided
|
|
|
|
**Solution**:
|
|
```typescript
|
|
// Add extra validation before .trim()
|
|
if (!make || typeof make !== 'string' || make.trim().length === 0) {
|
|
return reply.code(400).send({ ... });
|
|
}
|
|
```
|
|
|
|
### Issue: Transmissions still returning ["Automatic", "Manual"]
|
|
**Cause**: Service method not updated or cache not cleared
|
|
|
|
**Solution**:
|
|
```bash
|
|
# Clear Redis cache
|
|
docker exec mvp-redis redis-cli FLUSHDB
|
|
|
|
# Restart backend
|
|
make rebuild
|
|
```
|
|
|
|
### Issue: Frontend still sending make_id parameter
|
|
**Cause**: Frontend not yet updated (Agent 5 not complete)
|
|
|
|
**Solution**:
|
|
- This is expected - Agent 5 will update frontend API client
|
|
- For now, test with curl using string parameters
|
|
- Document breaking changes for Agent 5
|
|
|
|
---
|
|
|
|
## API Contract for Agent 5
|
|
|
|
Once complete, provide this to Agent 5 (Frontend API Client):
|
|
|
|
### New API Endpoints
|
|
|
|
**All endpoints now use string parameters (not IDs):**
|
|
|
|
```typescript
|
|
// Makes
|
|
GET /api/vehicles/dropdown/makes?year=2024
|
|
Response: string[] // ["Ford", "Honda", ...]
|
|
|
|
// Models
|
|
GET /api/vehicles/dropdown/models?year=2024&make=Ford
|
|
Response: string[] // ["F-150", "Mustang", ...]
|
|
|
|
// Trims
|
|
GET /api/vehicles/dropdown/trims?year=2024&make=Ford&model=F-150
|
|
Response: string[] // ["XLT", "Lariat", ...]
|
|
|
|
// Engines
|
|
GET /api/vehicles/dropdown/engines?year=2024&make=Ford&model=F-150&trim=XLT
|
|
Response: string[] // ["V8 5.0L", ...]
|
|
|
|
// Transmissions
|
|
GET /api/vehicles/dropdown/transmissions?year=2024&make=Ford&model=F-150
|
|
Response: string[] // ["10-Speed Automatic", ...]
|
|
|
|
// Years (unchanged)
|
|
GET /api/vehicles/dropdown/years
|
|
Response: number[] // [2026, 2025, ...]
|
|
```
|
|
|
|
### Breaking Changes
|
|
|
|
**Query Parameters**:
|
|
- `make_id` → `make` (string)
|
|
- `model_id` → `model` (string)
|
|
- `trim_id` → `trim` (string)
|
|
|
|
**Response Format**:
|
|
- Old: `[{id: 1, name: "Ford"}, ...]`
|
|
- New: `["Ford", ...]`
|
|
|
|
---
|
|
|
|
## Completion Message Template
|
|
|
|
```
|
|
Agent 4 (Vehicles API): COMPLETE
|
|
|
|
Files Modified:
|
|
- backend/src/features/vehicles/api/vehicles.controller.ts
|
|
- backend/src/features/vehicles/domain/vehicles.service.ts
|
|
|
|
Changes Made:
|
|
- Updated all controller query parameters to use strings (make, model, trim)
|
|
- Updated all service methods to accept string parameters
|
|
- Changed return types to string[] (removed objects)
|
|
- getDropdownTransmissions() now fetches real data (not hardcoded!)
|
|
- Updated validation to check string lengths
|
|
- Updated error messages to reflect new parameter names
|
|
|
|
Verification:
|
|
✓ TypeScript compiles successfully
|
|
✓ Backend builds successfully
|
|
✓ Manual API tests with curl pass
|
|
✓ Integration tests pass
|
|
✓ Transmissions return real data (verified)
|
|
✓ Electric vehicles show 'N/A (Electric)' for engines
|
|
|
|
Agent 5 (Frontend API Client) can now update frontend to use new API contract.
|
|
|
|
Breaking Changes for Agent 5:
|
|
- Query parameters changed: make_id→make, model_id→model, trim_id→trim
|
|
- Response format changed: {id, name}[] → string[]
|
|
- Cascade queries now use selected string values (not IDs)
|
|
```
|
|
|
|
---
|
|
|
|
**Document Version**: 1.0
|
|
**Last Updated**: 2025-11-10
|
|
**Status**: Ready for Implementation
|