Skip to content

Commit b968135

Browse files
fix: check for geocode object before querying error (#215)
1 parent cb4d708 commit b968135

File tree

2 files changed

+314
-2
lines changed

2 files changed

+314
-2
lines changed

application/frontend/src/app/core/services/csv.service.spec.ts

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { CsvService } from './csv.service';
2222
import { parse, unparse } from 'papaparse';
2323
import { EXPERIMENTAL_API_FIELDS_VEHICLES, UnloadingPolicy } from '../models';
2424
import { GeocodingService } from './geocoding.service';
25+
import { of } from 'rxjs';
2526

2627
describe('CsvService', () => {
2728
let service: CsvService;
@@ -157,6 +158,90 @@ describe('CsvService', () => {
157158
expect(result[0].errors.length).toBe(1);
158159
expect(result[1].errors.length).toBe(1);
159160
});
161+
162+
it('should parse valid allowedVehicleIndices', () => {
163+
const shipments = [{ label: 'test shipment', allowedVehicleIndices: '1,2,3,4' }];
164+
const testMapping = { Label: 'label', AllowedVehicleIndices: 'allowedVehicleIndices' };
165+
const result = service.csvToShipments(shipments, testMapping);
166+
expect(result.length).toBe(1);
167+
expect(result[0].errors.length).toBe(0);
168+
expect(result[0].shipment.allowedVehicleIndices).toEqual([1, 2, 3, 4]);
169+
});
170+
171+
it('should parse valid allowedVehicleIndices when spaces are present', () => {
172+
const shipments = [{ label: 'test shipment', allowedVehicleIndices: '1, 2, 3, 4 ' }];
173+
const testMapping = { Label: 'label', AllowedVehicleIndices: 'allowedVehicleIndices' };
174+
const result = service.csvToShipments(shipments, testMapping);
175+
expect(result.length).toBe(1);
176+
expect(result[0].errors.length).toBe(0);
177+
expect(result[0].shipment.allowedVehicleIndices).toEqual([1, 2, 3, 4]);
178+
});
179+
180+
it('should parse shipment pickup time windows', () => {
181+
const shipments = [
182+
{
183+
startTime: '2025-04-01T10:00:00Z',
184+
softStartTime: '2025-04-01T08:00:00Z',
185+
endTime: '2025-04-01T20:00:00Z',
186+
softEndTime: '2025-04-01T19:00:00Z',
187+
},
188+
];
189+
const testShipmentMapping = {
190+
PickupStartTime: 'startTime',
191+
PickupSoftStartTime: 'softStartTime',
192+
PickupEndTime: 'endTime',
193+
PickupSoftEndTime: 'softEndTime',
194+
};
195+
196+
const result = service.csvToShipments(shipments, testShipmentMapping);
197+
expect(result.length).toBe(1);
198+
expect(result[0].errors.length).toBe(0);
199+
expect(result[0].shipment.pickups[0].timeWindows[0].startTime).toEqual({
200+
seconds: '1743501600',
201+
});
202+
expect(result[0].shipment.pickups[0].timeWindows[0].softStartTime).toEqual({
203+
seconds: '1743494400',
204+
});
205+
expect(result[0].shipment.pickups[0].timeWindows[0].endTime).toEqual({
206+
seconds: '1743537600',
207+
});
208+
expect(result[0].shipment.pickups[0].timeWindows[0].softEndTime).toEqual({
209+
seconds: '1743534000',
210+
});
211+
});
212+
213+
it('should parse shipment delivery time windows', () => {
214+
const shipments = [
215+
{
216+
startTime: '2025-04-01T10:00:00Z',
217+
softStartTime: '2025-04-01T08:00:00Z',
218+
endTime: '2025-04-01T20:00:00Z',
219+
softEndTime: '2025-04-01T19:00:00Z',
220+
},
221+
];
222+
const testShipmentMapping = {
223+
DeliveryStartTime: 'startTime',
224+
DeliverySoftStartTime: 'softStartTime',
225+
DeliveryEndTime: 'endTime',
226+
DeliverySoftEndTime: 'softEndTime',
227+
};
228+
229+
const result = service.csvToShipments(shipments, testShipmentMapping);
230+
expect(result.length).toBe(1);
231+
expect(result[0].errors.length).toBe(0);
232+
expect(result[0].shipment.deliveries[0].timeWindows[0].startTime).toEqual({
233+
seconds: '1743501600',
234+
});
235+
expect(result[0].shipment.deliveries[0].timeWindows[0].softStartTime).toEqual({
236+
seconds: '1743494400',
237+
});
238+
expect(result[0].shipment.deliveries[0].timeWindows[0].endTime).toEqual({
239+
seconds: '1743537600',
240+
});
241+
expect(result[0].shipment.deliveries[0].timeWindows[0].softEndTime).toEqual({
242+
seconds: '1743534000',
243+
});
244+
});
160245
});
161246

162247
describe('Validate vehicles', () => {
@@ -331,6 +416,68 @@ describe('CsvService', () => {
331416
expect(result[2].vehicle.unloadingPolicy).toBe(UnloadingPolicy.FIRST_IN_FIRST_OUT);
332417
expect(result[3].vehicle.unloadingPolicy).toBeUndefined();
333418
});
419+
420+
it('should parse usedIfRouteIsEmpty', () => {
421+
const vehicles = [
422+
{
423+
label: 'test vehicle 1',
424+
usedIfRouteIsEmpty: 'true',
425+
},
426+
{
427+
label: 'test vehicle 2',
428+
usedIfRouteIsEmpty: ' TRUE ',
429+
},
430+
{
431+
label: 'test vehicle 3',
432+
usedIfRouteIsEmpty: 'FALSE',
433+
},
434+
{
435+
label: 'test vehicle 4',
436+
usedIfRouteIsEmpty: 'not a bool',
437+
},
438+
];
439+
const testVehicleMapping = {
440+
Label: 'label',
441+
UsedIfRouteIsEmpty: 'usedIfRouteIsEmpty',
442+
};
443+
const result = service.csvToVehicles(vehicles, testVehicleMapping);
444+
expect(result.length).toBe(4);
445+
expect(result[0].errors.length).toBe(0);
446+
expect(result[1].errors.length).toBe(0);
447+
expect(result[2].errors.length).toBe(0);
448+
expect(result[3].errors.length).toBe(0);
449+
expect(result[0].vehicle.usedIfRouteIsEmpty).toBeTrue();
450+
expect(result[1].vehicle.usedIfRouteIsEmpty).toBeTrue();
451+
expect(result[2].vehicle.usedIfRouteIsEmpty).toBeFalse();
452+
expect(result[3].vehicle.usedIfRouteIsEmpty).toBeFalse();
453+
});
454+
455+
it('should parse vehicle time windows', () => {
456+
const vehicles = [
457+
{
458+
startTime: '2025-04-01T10:00:00Z',
459+
softStartTime: '2025-04-01T08:00:00Z',
460+
endTime: '2025-04-01T20:00:00Z',
461+
softEndTime: '2025-04-01T19:00:00Z',
462+
},
463+
];
464+
const testVehicleMapping = {
465+
StartTimeWindowStartTime: 'startTime',
466+
StartTimeWindowSoftStartTime: 'softStartTime',
467+
StartTimeWindowEndTime: 'endTime',
468+
StartTimeWindowSoftEndTime: 'softEndTime',
469+
};
470+
471+
const result = service.csvToVehicles(vehicles, testVehicleMapping);
472+
expect(result.length).toBe(1);
473+
expect(result[0].errors.length).toBe(0);
474+
expect(result[0].vehicle.startTimeWindows[0].startTime).toEqual({ seconds: '1743501600' });
475+
expect(result[0].vehicle.startTimeWindows[0].softStartTime).toEqual({
476+
seconds: '1743494400',
477+
});
478+
expect(result[0].vehicle.startTimeWindows[0].endTime).toEqual({ seconds: '1743537600' });
479+
expect(result[0].vehicle.startTimeWindows[0].softEndTime).toEqual({ seconds: '1743534000' });
480+
});
334481
});
335482

336483
describe('Validate geocoding', () => {
@@ -441,4 +588,165 @@ describe('CsvService', () => {
441588
});
442589
});
443590
});
591+
592+
describe('Geocode vehicles', () => {
593+
it('should return an empty array when no vehicles are provided', (done) => {
594+
service.geocodeVehicles([]).subscribe((res) => {
595+
expect(res).toEqual([]);
596+
done();
597+
});
598+
});
599+
600+
it('should return null values when no waypoints are provided', (done) => {
601+
service.geocodeVehicles([{}]).subscribe((res) => {
602+
expect(res).toEqual([null, null]);
603+
done();
604+
});
605+
});
606+
607+
it('should return null values for missing start waypoints', (done) => {
608+
service.geocodeVehicles([{ endWaypoint: '35.1, 10.53' }]).subscribe((res) => {
609+
expect(res).toEqual([null, { latitude: 35.1, longitude: 10.53 }]);
610+
done();
611+
});
612+
});
613+
614+
it('should return null values for missing end waypoints', (done) => {
615+
service.geocodeVehicles([{ startWaypoint: '35.1, 10.53' }]).subscribe((res) => {
616+
expect(res).toEqual([{ latitude: 35.1, longitude: 10.53 }, null]);
617+
done();
618+
});
619+
});
620+
621+
it('should parse geocode errors', (done) => {
622+
spyOn(service, 'geocodeLocation').and.callFake((loc: string) => {
623+
return of({
624+
error: true,
625+
message: 'Invalid location',
626+
location: loc,
627+
});
628+
});
629+
630+
const vehicle1 = { startWaypoint: '35.1, 10.53' };
631+
632+
service.geocodeVehicles([vehicle1]).subscribe((res) => {
633+
expect(res).toEqual([
634+
{
635+
error: true,
636+
message: 'Invalid location',
637+
field: 'startWaypoint',
638+
location: '35.1, 10.53',
639+
index: 0,
640+
source: vehicle1,
641+
vehicle: vehicle1,
642+
},
643+
{
644+
error: true,
645+
message: 'Invalid location',
646+
field: 'endWaypoint',
647+
location: undefined,
648+
index: 0,
649+
source: vehicle1,
650+
vehicle: vehicle1,
651+
},
652+
]);
653+
done();
654+
});
655+
});
656+
});
657+
658+
describe('Geocode shipments', () => {
659+
it('should return an empty array when no shiments are provided', (done) => {
660+
service.geocodeShipments([]).subscribe((res) => {
661+
expect(res).toEqual([]);
662+
done();
663+
});
664+
});
665+
666+
it('should return null values when no waypoints are provided', (done) => {
667+
service.geocodeShipments([{}]).subscribe((res) => {
668+
expect(res).toEqual([null, null]);
669+
done();
670+
});
671+
});
672+
673+
it('should return null values for missing pickup arrival waypoints', (done) => {
674+
service
675+
.geocodeShipments([{ deliveries: [{ arrivalWaypoint: '35.1, 10.53' }] }])
676+
.subscribe((res) => {
677+
expect(res).toEqual([null, { latitude: 35.1, longitude: 10.53 }]);
678+
done();
679+
});
680+
});
681+
682+
it('should return null values for missing delivery arrival waypoints', (done) => {
683+
service
684+
.geocodeShipments([{ pickups: [{ arrivalWaypoint: '35.1, 10.53' }] }])
685+
.subscribe((res) => {
686+
expect(res).toEqual([{ latitude: 35.1, longitude: 10.53 }, null]);
687+
done();
688+
});
689+
});
690+
691+
it('should only geocode the first pickup', (done) => {
692+
service
693+
.geocodeShipments([
694+
{ pickups: [{ arrivalWaypoint: '35.1, 10.53' }, { arrivalWaypoint: '-5.123, 38.3' }] },
695+
])
696+
.subscribe((res) => {
697+
expect(res).toEqual([{ latitude: 35.1, longitude: 10.53 }, null]);
698+
done();
699+
});
700+
});
701+
702+
it('should only geocode the first delivery', (done) => {
703+
service
704+
.geocodeShipments([
705+
{ deliveries: [{ arrivalWaypoint: '35.1, 10.53' }, { arrivalWaypoint: '-5.123, 38.3' }] },
706+
])
707+
.subscribe((res) => {
708+
expect(res).toEqual([null, { latitude: 35.1, longitude: 10.53 }]);
709+
done();
710+
});
711+
});
712+
713+
it('should parse geocode errors', (done) => {
714+
spyOn(service, 'geocodeLocation').and.callFake((loc: string) => {
715+
return of({
716+
error: true,
717+
message: 'Invalid location',
718+
location: loc,
719+
});
720+
});
721+
722+
const shipment1 = {
723+
pickups: [{ arrivalWaypoint: '35.1, 10.53' }],
724+
deliveries: [{ arrivalWaypoint: '5.13, 3.51' }],
725+
};
726+
727+
service.geocodeShipments([shipment1]).subscribe((res) => {
728+
expect(res).toEqual([
729+
{
730+
error: true,
731+
message: 'Invalid location',
732+
field: 'arrivalWaypoint',
733+
location: '35.1, 10.53',
734+
index: 0,
735+
source: shipment1.pickups[0],
736+
shipment: shipment1,
737+
},
738+
{
739+
error: true,
740+
message: 'Invalid location',
741+
field: 'arrivalWaypoint',
742+
location: '5.13, 3.51',
743+
index: 0,
744+
source: shipment1.deliveries[0],
745+
shipment: shipment1,
746+
},
747+
]);
748+
done();
749+
});
750+
});
751+
});
444752
});

application/frontend/src/app/core/services/csv.service.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export class CsvService {
5757
// start location
5858
this.geocodeLocation(vehicle.startWaypoint).pipe(
5959
map((g) => {
60-
if ((g as GeocodeErrorResponse).error) {
60+
if ((g as GeocodeErrorResponse)?.error) {
6161
g = <GeocodeErrorResponse>{
6262
...g,
6363
source: vehicle,
@@ -648,7 +648,11 @@ export class CsvService {
648648
}
649649

650650
private toBool(value: string): boolean {
651-
return !!JSON.parse(value.toLowerCase());
651+
try {
652+
return !!JSON.parse(value.toLowerCase());
653+
} catch {
654+
return false;
655+
}
652656
}
653657

654658
private toDuration(value: any): any {

0 commit comments

Comments
 (0)