Homepage Redesign
This commit is contained in:
@@ -5,17 +5,19 @@
|
||||
|
||||
import { VehiclesService } from '../../domain/vehicles.service';
|
||||
import { VehiclesRepository } from '../../data/vehicles.repository';
|
||||
import { vpicClient } from '../../external/vpic/vpic.client';
|
||||
import { cacheService } from '../../../../core/config/redis';
|
||||
import * as platformModule from '../../../platform';
|
||||
|
||||
// Mock dependencies
|
||||
jest.mock('../../data/vehicles.repository');
|
||||
jest.mock('../../external/vpic/vpic.client');
|
||||
jest.mock('../../../../core/config/redis');
|
||||
jest.mock('../../../platform', () => ({
|
||||
getVINDecodeService: jest.fn()
|
||||
}));
|
||||
|
||||
const mockRepository = jest.mocked(VehiclesRepository);
|
||||
const mockVpicClient = jest.mocked(vpicClient);
|
||||
const mockCacheService = jest.mocked(cacheService);
|
||||
const mockGetVINDecodeService = jest.mocked(platformModule.getVINDecodeService);
|
||||
|
||||
describe('VehiclesService', () => {
|
||||
let service: VehiclesService;
|
||||
@@ -23,7 +25,7 @@ describe('VehiclesService', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
|
||||
repositoryInstance = {
|
||||
create: jest.fn(),
|
||||
findByUserId: jest.fn(),
|
||||
@@ -31,8 +33,6 @@ describe('VehiclesService', () => {
|
||||
findByUserAndVIN: jest.fn(),
|
||||
update: jest.fn(),
|
||||
softDelete: jest.fn(),
|
||||
cacheVINDecode: jest.fn(),
|
||||
getVINFromCache: jest.fn(),
|
||||
} as any;
|
||||
|
||||
mockRepository.mockImplementation(() => repositoryInstance);
|
||||
@@ -74,16 +74,27 @@ describe('VehiclesService', () => {
|
||||
};
|
||||
|
||||
it('should create a vehicle with VIN decoding', async () => {
|
||||
const mockVinDecodeService = {
|
||||
decodeVIN: jest.fn().mockResolvedValue({
|
||||
success: true,
|
||||
data: {
|
||||
vin: '1HGBH41JXMN109186',
|
||||
make: 'Honda',
|
||||
model: 'Civic',
|
||||
year: 2021
|
||||
}
|
||||
})
|
||||
};
|
||||
mockGetVINDecodeService.mockReturnValue(mockVinDecodeService as any);
|
||||
|
||||
repositoryInstance.findByUserAndVIN.mockResolvedValue(null);
|
||||
mockVpicClient.decodeVIN.mockResolvedValue(mockVinDecodeResult);
|
||||
repositoryInstance.create.mockResolvedValue(mockCreatedVehicle);
|
||||
repositoryInstance.cacheVINDecode.mockResolvedValue(undefined);
|
||||
mockCacheService.del.mockResolvedValue(undefined);
|
||||
|
||||
const result = await service.createVehicle(mockVehicleData, 'user-123');
|
||||
|
||||
expect(repositoryInstance.findByUserAndVIN).toHaveBeenCalledWith('user-123', '1HGBH41JXMN109186');
|
||||
expect(mockVpicClient.decodeVIN).toHaveBeenCalledWith('1HGBH41JXMN109186');
|
||||
expect(mockVinDecodeService.decodeVIN).toHaveBeenCalledWith('1HGBH41JXMN109186');
|
||||
expect(repositoryInstance.create).toHaveBeenCalledWith({
|
||||
...mockVehicleData,
|
||||
userId: 'user-123',
|
||||
@@ -91,7 +102,6 @@ describe('VehiclesService', () => {
|
||||
model: 'Civic',
|
||||
year: 2021,
|
||||
});
|
||||
expect(repositoryInstance.cacheVINDecode).toHaveBeenCalledWith('1HGBH41JXMN109186', mockVinDecodeResult);
|
||||
expect(result.id).toBe('vehicle-id-123');
|
||||
expect(result.make).toBe('Honda');
|
||||
});
|
||||
@@ -109,8 +119,15 @@ describe('VehiclesService', () => {
|
||||
});
|
||||
|
||||
it('should handle VIN decode failure gracefully', async () => {
|
||||
const mockVinDecodeService = {
|
||||
decodeVIN: jest.fn().mockResolvedValue({
|
||||
success: false,
|
||||
error: 'VIN decode failed'
|
||||
})
|
||||
};
|
||||
mockGetVINDecodeService.mockReturnValue(mockVinDecodeService as any);
|
||||
|
||||
repositoryInstance.findByUserAndVIN.mockResolvedValue(null);
|
||||
mockVpicClient.decodeVIN.mockResolvedValue(null);
|
||||
repositoryInstance.create.mockResolvedValue({ ...mockCreatedVehicle, make: undefined, model: undefined, year: undefined });
|
||||
mockCacheService.del.mockResolvedValue(undefined);
|
||||
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
/**
|
||||
* @ai-summary Unit tests for VPICClient
|
||||
* @ai-context Tests VIN decoding with mocked HTTP client
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
import { VPICClient } from '../../external/vpic/vpic.client';
|
||||
import { cacheService } from '../../../../core/config/redis';
|
||||
import { VPICResponse } from '../../external/vpic/vpic.types';
|
||||
|
||||
jest.mock('axios');
|
||||
jest.mock('../../../../core/config/redis');
|
||||
|
||||
const mockAxios = jest.mocked(axios);
|
||||
const mockCacheService = jest.mocked(cacheService);
|
||||
|
||||
describe('VPICClient', () => {
|
||||
let client: VPICClient;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
client = new VPICClient();
|
||||
});
|
||||
|
||||
describe('decodeVIN', () => {
|
||||
const mockVin = '1HGBH41JXMN109186';
|
||||
|
||||
const mockVPICResponse: VPICResponse = {
|
||||
Count: 3,
|
||||
Message: 'Success',
|
||||
SearchCriteria: 'VIN: 1HGBH41JXMN109186',
|
||||
Results: [
|
||||
{ Variable: 'Make', Value: 'Honda', ValueId: null, VariableId: 1 },
|
||||
{ Variable: 'Model', Value: 'Civic', ValueId: null, VariableId: 2 },
|
||||
{ Variable: 'Model Year', Value: '2021', ValueId: null, VariableId: 3 },
|
||||
{ Variable: 'Engine Model', Value: '2.0L', ValueId: null, VariableId: 4 },
|
||||
{ Variable: 'Body Class', Value: 'Sedan', ValueId: null, VariableId: 5 },
|
||||
]
|
||||
};
|
||||
|
||||
it('should return cached result if available', async () => {
|
||||
const cachedResult = {
|
||||
make: 'Honda',
|
||||
model: 'Civic',
|
||||
year: 2021,
|
||||
engineType: '2.0L',
|
||||
bodyType: 'Sedan',
|
||||
rawData: mockVPICResponse.Results
|
||||
};
|
||||
|
||||
mockCacheService.get.mockResolvedValue(cachedResult);
|
||||
|
||||
const result = await client.decodeVIN(mockVin);
|
||||
|
||||
expect(mockCacheService.get).toHaveBeenCalledWith(`vpic:vin:${mockVin}`);
|
||||
expect(result).toEqual(cachedResult);
|
||||
expect(mockAxios.get).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should fetch and cache VIN data when not cached', async () => {
|
||||
mockCacheService.get.mockResolvedValue(null);
|
||||
mockAxios.get.mockResolvedValue({ data: mockVPICResponse });
|
||||
mockCacheService.set.mockResolvedValue(undefined);
|
||||
|
||||
const result = await client.decodeVIN(mockVin);
|
||||
|
||||
expect(mockCacheService.get).toHaveBeenCalledWith(`vpic:vin:${mockVin}`);
|
||||
expect(mockAxios.get).toHaveBeenCalledWith(
|
||||
expect.stringContaining(`/DecodeVin/${mockVin}?format=json`)
|
||||
);
|
||||
expect(mockCacheService.set).toHaveBeenCalledWith(
|
||||
`vpic:vin:${mockVin}`,
|
||||
expect.objectContaining({
|
||||
make: 'Honda',
|
||||
model: 'Civic',
|
||||
year: 2021,
|
||||
engineType: '2.0L',
|
||||
bodyType: 'Sedan'
|
||||
}),
|
||||
30 * 24 * 60 * 60 // 30 days
|
||||
);
|
||||
expect(result?.make).toBe('Honda');
|
||||
expect(result?.model).toBe('Civic');
|
||||
expect(result?.year).toBe(2021);
|
||||
});
|
||||
|
||||
it('should return null when API returns no results', async () => {
|
||||
const emptyResponse: VPICResponse = {
|
||||
Count: 0,
|
||||
Message: 'No data found',
|
||||
SearchCriteria: 'VIN: INVALID',
|
||||
Results: []
|
||||
};
|
||||
|
||||
mockCacheService.get.mockResolvedValue(null);
|
||||
mockAxios.get.mockResolvedValue({ data: emptyResponse });
|
||||
|
||||
const result = await client.decodeVIN('INVALID');
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(mockCacheService.set).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return null when required fields are missing', async () => {
|
||||
const incompleteResponse: VPICResponse = {
|
||||
Count: 1,
|
||||
Message: 'Success',
|
||||
SearchCriteria: 'VIN: 1HGBH41JXMN109186',
|
||||
Results: [
|
||||
{ Variable: 'Make', Value: 'Honda', ValueId: null, VariableId: 1 },
|
||||
// Missing Model and Year
|
||||
]
|
||||
};
|
||||
|
||||
mockCacheService.get.mockResolvedValue(null);
|
||||
mockAxios.get.mockResolvedValue({ data: incompleteResponse });
|
||||
|
||||
const result = await client.decodeVIN(mockVin);
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(mockCacheService.set).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle API errors gracefully', async () => {
|
||||
mockCacheService.get.mockResolvedValue(null);
|
||||
mockAxios.get.mockRejectedValue(new Error('Network error'));
|
||||
|
||||
const result = await client.decodeVIN(mockVin);
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(mockCacheService.set).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle null values in API response', async () => {
|
||||
const responseWithNulls: VPICResponse = {
|
||||
Count: 3,
|
||||
Message: 'Success',
|
||||
SearchCriteria: 'VIN: 1HGBH41JXMN109186',
|
||||
Results: [
|
||||
{ Variable: 'Make', Value: 'Honda', ValueId: null, VariableId: 1 },
|
||||
{ Variable: 'Model', Value: 'Civic', ValueId: null, VariableId: 2 },
|
||||
{ Variable: 'Model Year', Value: '2021', ValueId: null, VariableId: 3 },
|
||||
{ Variable: 'Engine Model', Value: null, ValueId: null, VariableId: 4 },
|
||||
{ Variable: 'Body Class', Value: null, ValueId: null, VariableId: 5 },
|
||||
]
|
||||
};
|
||||
|
||||
mockCacheService.get.mockResolvedValue(null);
|
||||
mockAxios.get.mockResolvedValue({ data: responseWithNulls });
|
||||
mockCacheService.set.mockResolvedValue(undefined);
|
||||
|
||||
const result = await client.decodeVIN(mockVin);
|
||||
|
||||
expect(result?.make).toBe('Honda');
|
||||
expect(result?.model).toBe('Civic');
|
||||
expect(result?.year).toBe(2021);
|
||||
expect(result?.engineType).toBeUndefined();
|
||||
expect(result?.bodyType).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user