fail faster in path creator

This commit is contained in:
sbriat 2023-09-18 11:14:46 +02:00
parent 40227be69a
commit a277a9547f
8 changed files with 162 additions and 56 deletions

View File

@ -31,6 +31,7 @@ export class CreateAdService implements ICommandHandler {
const roles: Role[] = [];
if (command.driver) roles.push(Role.DRIVER);
if (command.passenger) roles.push(Role.PASSENGER);
const pathCreator: PathCreator = new PathCreator(
roles,
command.waypoints.map(
@ -41,6 +42,7 @@ export class CreateAdService implements ICommandHandler {
}),
),
);
let typedRoutes: TypedRoute[];
try {
typedRoutes = await Promise.all(
@ -60,24 +62,11 @@ export class CreateAdService implements ICommandHandler {
let points: PointValueObject[] | undefined;
let fwdAzimuth: number | undefined;
let backAzimuth: number | undefined;
typedRoutes.forEach((typedRoute: TypedRoute) => {
if (typedRoute.type !== PathType.PASSENGER) {
driverDistance = typedRoute.route.distance;
driverDuration = typedRoute.route.duration;
points = typedRoute.route.points.map(
(point: Point) =>
new PointValueObject({
lon: point.lon,
lat: point.lat,
}),
);
fwdAzimuth = typedRoute.route.fwdAzimuth;
backAzimuth = typedRoute.route.backAzimuth;
}
if (typedRoute.type !== PathType.DRIVER) {
passengerDistance = typedRoute.route.distance;
passengerDuration = typedRoute.route.duration;
if (!points)
try {
typedRoutes.forEach((typedRoute: TypedRoute) => {
if ([PathType.DRIVER, PathType.GENERIC].includes(typedRoute.type)) {
driverDistance = typedRoute.route.distance;
driverDuration = typedRoute.route.duration;
points = typedRoute.route.points.map(
(point: Point) =>
new PointValueObject({
@ -85,41 +74,55 @@ export class CreateAdService implements ICommandHandler {
lat: point.lat,
}),
);
if (!fwdAzimuth) fwdAzimuth = typedRoute.route.fwdAzimuth;
if (!backAzimuth) backAzimuth = typedRoute.route.backAzimuth;
}
});
if (points && fwdAzimuth && backAzimuth) {
const ad = AdEntity.create({
id: command.id,
driver: command.driver,
passenger: command.passenger,
frequency: command.frequency,
fromDate: command.fromDate,
toDate: command.toDate,
schedule: command.schedule,
seatsProposed: command.seatsProposed,
seatsRequested: command.seatsRequested,
strict: command.strict,
waypoints: command.waypoints,
points,
driverDistance,
driverDuration,
passengerDistance,
passengerDuration,
fwdAzimuth,
backAzimuth,
});
try {
await this.repository.insertExtra(ad, 'ad');
return ad.id;
} catch (error: any) {
if (error instanceof ConflictException) {
throw new AdAlreadyExistsException(error);
fwdAzimuth = typedRoute.route.fwdAzimuth;
backAzimuth = typedRoute.route.backAzimuth;
}
throw error;
}
if ([PathType.PASSENGER, PathType.GENERIC].includes(typedRoute.type)) {
passengerDistance = typedRoute.route.distance;
passengerDuration = typedRoute.route.duration;
if (!points)
points = typedRoute.route.points.map(
(point: Point) =>
new PointValueObject({
lon: point.lon,
lat: point.lat,
}),
);
if (!fwdAzimuth) fwdAzimuth = typedRoute.route.fwdAzimuth;
if (!backAzimuth) backAzimuth = typedRoute.route.backAzimuth;
}
});
} catch (error: any) {
throw new Error('Invalid route');
}
const ad = AdEntity.create({
id: command.id,
driver: command.driver,
passenger: command.passenger,
frequency: command.frequency,
fromDate: command.fromDate,
toDate: command.toDate,
schedule: command.schedule,
seatsProposed: command.seatsProposed,
seatsRequested: command.seatsRequested,
strict: command.strict,
waypoints: command.waypoints,
points: points as PointValueObject[],
driverDistance,
driverDuration,
passengerDistance,
passengerDuration,
fwdAzimuth: fwdAzimuth as number,
backAzimuth: backAzimuth as number,
});
try {
await this.repository.insertExtra(ad, 'ad');
return ad.id;
} catch (error: any) {
if (error instanceof ConflictException) {
throw new AdAlreadyExistsException(error);
}
throw error;
}
throw new Error('Route error');
}
}

View File

@ -1,5 +1,6 @@
import { Role } from './ad.types';
import { Target } from './candidate.types';
import { CarpoolPathCreatorException } from './match.errors';
import { Actor } from './value-objects/actor.value-object';
import { Point } from './value-objects/point.value-object';
import { WayStep } from './value-objects/waystep.value-object';
@ -10,7 +11,16 @@ export class CarpoolPathCreator {
constructor(
private readonly driverWaypoints: Point[],
private readonly passengerWaypoints: Point[],
) {}
) {
if (driverWaypoints.length < 2)
throw new CarpoolPathCreatorException(
new Error('At least 2 driver waypoints must be defined'),
);
if (passengerWaypoints.length < 2)
throw new CarpoolPathCreatorException(
new Error('At least 2 passenger waypoints must be defined'),
);
}
/**
* Creates a path (a list of waysteps) between driver waypoints

View File

@ -0,0 +1,21 @@
import { ExceptionBase } from '@mobicoop/ddd-library';
export class PathCreatorException extends ExceptionBase {
static readonly message = 'Path creator error';
public readonly code = 'MATCHER.PATH_CREATOR';
constructor(cause?: Error, metadata?: unknown) {
super(PathCreatorException.message, cause, metadata);
}
}
export class CarpoolPathCreatorException extends ExceptionBase {
static readonly message = 'Carpool path creator error';
public readonly code = 'MATCHER.CARPOOL_PATH_CREATOR';
constructor(cause?: Error, metadata?: unknown) {
super(CarpoolPathCreatorException.message, cause, metadata);
}
}

View File

@ -1,12 +1,22 @@
import { Route } from '@modules/geography/core/domain/route.types';
import { Role } from './ad.types';
import { Point } from './value-objects/point.value-object';
import { PathCreatorException } from './match.errors';
export class PathCreator {
constructor(
private readonly roles: Role[],
private readonly waypoints: Point[],
) {}
) {
if (roles.length == 0)
throw new PathCreatorException(
new Error('At least a role must be defined'),
);
if (waypoints.length < 2)
throw new PathCreatorException(
new Error('At least 2 waypoints must be defined'),
);
}
public getBasePaths = (): Path[] => {
const paths: Path[] = [];
@ -61,6 +71,15 @@ export type TypedRoute = {
route: Route;
};
/**
* PathType id used for route calculation, to reduce the number of routes to compute :
* - a single route for a driver only
* - a single route for a passenger only
* - a single route for a driver and passenger with 2 waypoints given
* - two routes for a driver and passenger with more than 2 waypoints given
* (all the waypoints as driver, only origin and destination as passenger as
* intermediate waypoints doesn't matter in that case)
*/
export enum PathType {
GENERIC = 'generic',
DRIVER = 'driver',

View File

@ -1,4 +1,5 @@
import { CarpoolPathCreator } from '@modules/ad/core/domain/carpool-path-creator.service';
import { CarpoolPathCreatorException } from '@modules/ad/core/domain/match.errors';
import { Point } from '@modules/ad/core/domain/value-objects/point.value-object';
import { WayStep } from '@modules/ad/core/domain/value-objects/waystep.value-object';
@ -99,4 +100,18 @@ describe('Carpool Path Creator Service', () => {
expect(waysteps[4].actors.length).toBe(1);
expect(waysteps[5].actors.length).toBe(1);
});
it('should throw an exception if less than 2 driver waypoints are given', () => {
try {
new CarpoolPathCreator([waypoint1], [waypoint3, waypoint4]);
} catch (e: any) {
expect(e).toBeInstanceOf(CarpoolPathCreatorException);
}
});
it('should throw an exception if less than 2 passenger waypoints are given', () => {
try {
new CarpoolPathCreator([waypoint1, waypoint6], [waypoint3]);
} catch (e: any) {
expect(e).toBeInstanceOf(CarpoolPathCreatorException);
}
});
});

View File

@ -49,6 +49,7 @@ const mockAdRepository = {
insertExtra: jest
.fn()
.mockImplementationOnce(() => ({}))
.mockImplementationOnce(() => ({}))
.mockImplementationOnce(() => {
throw new Error();
})
@ -131,7 +132,7 @@ describe('create-ad.service', () => {
createAdService.execute(createAdCommand),
).rejects.toBeInstanceOf(Error);
});
it('should create a new ad', async () => {
it('should create a new ad as driver and passenger', async () => {
AdEntity.create = jest.fn().mockReturnValue({
id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
});
@ -140,6 +141,16 @@ describe('create-ad.service', () => {
);
expect(result).toBe('047a6ecf-23d4-4d3e-877c-3225d560a8da');
});
it('should create a new ad as passenger', async () => {
AdEntity.create = jest.fn().mockReturnValue({
id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
});
const result: AggregateID = await createAdService.execute({
...createAdCommand,
driver: false,
});
expect(result).toBe('047a6ecf-23d4-4d3e-877c-3225d560a8da');
});
it('should throw an error if something bad happens', async () => {
await expect(
createAdService.execute(createAdCommand),

View File

@ -55,7 +55,16 @@ const mockMatcherRepository: AdRepositoryPort = {
{
id: 'cc260669-1c6d-441f-80a5-19cd59afb777',
getProps: jest.fn().mockImplementation(() => ({
waypoints: [],
waypoints: [
{
lat: 48.6645,
lon: 6.18457,
},
{
lat: 48.7898,
lon: 2.36845,
},
],
})),
},
]),

View File

@ -1,4 +1,5 @@
import { Role } from '@modules/ad/core/domain/ad.types';
import { PathCreatorException } from '@modules/ad/core/domain/match.errors';
import {
Path,
PathCreator,
@ -68,4 +69,21 @@ describe('Path Creator Service', () => {
.waypoints,
).toHaveLength(2);
});
it('should throw an exception if a role is not given', () => {
try {
new PathCreator(
[],
[originWaypoint, intermediateWaypoint, destinationWaypoint],
);
} catch (e: any) {
expect(e).toBeInstanceOf(PathCreatorException);
}
});
it('should throw an exception if less than 2 waypoints are given', () => {
try {
new PathCreator([Role.DRIVER], [originWaypoint]);
} catch (e: any) {
expect(e).toBeInstanceOf(PathCreatorException);
}
});
});