fail faster in path creator
This commit is contained in:
parent
40227be69a
commit
a277a9547f
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
})),
|
||||
},
|
||||
]),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue