diff --git a/src/modules/geography/core/application/types/georouter-settings.type.ts b/src/modules/geography/core/application/types/georouter-settings.type.ts index d8f73ae..ae5b391 100644 --- a/src/modules/geography/core/application/types/georouter-settings.type.ts +++ b/src/modules/geography/core/application/types/georouter-settings.type.ts @@ -1,5 +1,5 @@ export type GeorouterSettings = { - withPoints: boolean; - withTime: boolean; - withDistance: boolean; + points: boolean; + detailedDuration: boolean; + detailedDistance: boolean; }; diff --git a/src/modules/geography/core/application/types/path.type.ts b/src/modules/geography/core/application/types/path.type.ts index 1f4a6eb..2f8e46e 100644 --- a/src/modules/geography/core/application/types/path.type.ts +++ b/src/modules/geography/core/application/types/path.type.ts @@ -1,7 +1,7 @@ import { PathType } from '../../domain/route.types'; -import { Point } from './point.type'; +import { Coordinates } from './coordinates.type'; export type Path = { type: PathType; - points: Point[]; + points: Coordinates[]; }; diff --git a/src/modules/geography/core/application/types/point.type.ts b/src/modules/geography/core/application/types/point.type.ts deleted file mode 100644 index 6f71f0f..0000000 --- a/src/modules/geography/core/application/types/point.type.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { PointContext } from '../../domain/route.types'; -import { Coordinates } from './coordinates.type'; - -export type Point = Coordinates & { - context?: PointContext; -}; diff --git a/src/modules/geography/core/application/types/route.type.ts b/src/modules/geography/core/application/types/route.type.ts index d49c2e4..3913deb 100644 --- a/src/modules/geography/core/application/types/route.type.ts +++ b/src/modules/geography/core/application/types/route.type.ts @@ -1,5 +1,5 @@ import { PathType } from '../../domain/route.types'; -import { Point } from './point.type'; +import { Coordinates } from './coordinates.type'; import { SpacetimePoint } from './spacetime-point.type'; export type Route = { @@ -9,6 +9,6 @@ export type Route = { fwdAzimuth: number; backAzimuth: number; distanceAzimuth: number; - points: Point[]; + points: Coordinates[]; spacetimePoints: SpacetimePoint[]; }; diff --git a/src/modules/geography/core/application/types/spacetime-point.type.ts b/src/modules/geography/core/application/types/spacetime-point.type.ts index 6822b1c..9bed0c5 100644 --- a/src/modules/geography/core/application/types/spacetime-point.type.ts +++ b/src/modules/geography/core/application/types/spacetime-point.type.ts @@ -1,6 +1,6 @@ -import { Point } from './point.type'; +import { Coordinates } from './coordinates.type'; -export type SpacetimePoint = Point & { +export type SpacetimePoint = Coordinates & { duration: number; distance: number; }; diff --git a/src/modules/geography/core/application/types/waypoint.type.ts b/src/modules/geography/core/application/types/waypoint.type.ts index af76297..3c635d5 100644 --- a/src/modules/geography/core/application/types/waypoint.type.ts +++ b/src/modules/geography/core/application/types/waypoint.type.ts @@ -1,5 +1,5 @@ -import { Point } from './point.type'; +import { Coordinates } from './coordinates.type'; -export type Waypoint = Point & { +export type Waypoint = Coordinates & { position: number; }; diff --git a/src/modules/geography/core/domain/route.entity.ts b/src/modules/geography/core/domain/route.entity.ts index a4a5822..705a4c2 100644 --- a/src/modules/geography/core/domain/route.entity.ts +++ b/src/modules/geography/core/domain/route.entity.ts @@ -10,15 +10,23 @@ import { import { WaypointProps } from './value-objects/waypoint.value-object'; import { Route } from '../application/types/route.type'; import { v4 } from 'uuid'; +import { RouteNotFoundException } from './route.errors'; export class RouteEntity extends AggregateRoot { protected readonly _id: AggregateID; static create = async (create: CreateRouteProps): Promise => { - const directions: Direction[] = await create.georouter.routes( - this.getPaths(create.roles, create.waypoints), - create.georouterSettings, - ); + let directions: Direction[]; + try { + directions = await create.georouter.routes( + this.getPaths(create.roles, create.waypoints), + create.georouterSettings, + ); + if (!directions || directions.length == 0) + throw new RouteNotFoundException(); + } catch (e: any) { + throw e; + } let driverRoute: Route; let passengerRoute: Route; if (directions.some((route: Route) => route.type == PathType.GENERIC)) { diff --git a/src/modules/geography/core/domain/route.errors.ts b/src/modules/geography/core/domain/route.errors.ts new file mode 100644 index 0000000..a0f484b --- /dev/null +++ b/src/modules/geography/core/domain/route.errors.ts @@ -0,0 +1,11 @@ +import { ExceptionBase } from '@mobicoop/ddd-library'; + +export class RouteNotFoundException extends ExceptionBase { + static readonly message = 'Route not found'; + + public readonly code = 'ROUTE.NOT_FOUND'; + + constructor(cause?: Error, metadata?: unknown) { + super(RouteNotFoundException.message, cause, metadata); + } +} diff --git a/src/modules/geography/tests/unit/core/route.entity.spec.ts b/src/modules/geography/tests/unit/core/route.entity.spec.ts new file mode 100644 index 0000000..5c0eaf3 --- /dev/null +++ b/src/modules/geography/tests/unit/core/route.entity.spec.ts @@ -0,0 +1,256 @@ +import { GeorouterPort } from '@modules/geography/core/application/ports/georouter.port'; +import { Coordinates } from '@modules/geography/core/application/types/coordinates.type'; +import { RouteEntity } from '@modules/geography/core/domain/route.entity'; +import { RouteNotFoundException } from '@modules/geography/core/domain/route.errors'; +import { + CreateRouteProps, + PathType, + Role, +} from '@modules/geography/core/domain/route.types'; + +const originCoordinates: Coordinates = { + lon: 48.689445, + lat: 6.17651, +}; +const destinationCoordinates: Coordinates = { + lon: 48.8566, + lat: 2.3522, +}; +const additionalCoordinates: Coordinates = { + lon: 48.7566, + lat: 4.4498, +}; + +const mockGeorouter: GeorouterPort = { + routes: jest + .fn() + .mockImplementationOnce(() => [ + { + type: PathType.DRIVER, + distance: 350101, + duration: 14422, + fwdAzimuth: 273, + backAzimuth: 93, + distanceAzimuth: 336544, + points: [ + { + lon: 6.1765102, + lat: 48.689445, + }, + { + lon: 4.984578, + lat: 48.725687, + }, + { + lon: 2.3522, + lat: 48.8566, + }, + ], + spacetimePoints: [], + }, + ]) + .mockImplementationOnce(() => [ + { + type: PathType.PASSENGER, + distance: 350102, + duration: 14423, + fwdAzimuth: 273, + backAzimuth: 93, + distanceAzimuth: 336545, + points: [ + { + lon: 6.1765103, + lat: 48.689446, + }, + { + lon: 4.984579, + lat: 48.725688, + }, + { + lon: 2.3523, + lat: 48.8567, + }, + ], + spacetimePoints: [], + }, + ]) + .mockImplementationOnce(() => [ + { + type: PathType.GENERIC, + distance: 350100, + duration: 14421, + fwdAzimuth: 273, + backAzimuth: 93, + distanceAzimuth: 336543, + points: [ + { + lon: 6.1765101, + lat: 48.689444, + }, + { + lon: 4.984577, + lat: 48.725686, + }, + { + lon: 2.3521, + lat: 48.8565, + }, + ], + spacetimePoints: [], + }, + ]) + .mockImplementationOnce(() => [ + { + type: PathType.GENERIC, + distance: 350108, + duration: 14428, + fwdAzimuth: 273, + backAzimuth: 93, + distanceAzimuth: 336548, + points: [ + { + lon: 6.1765101, + lat: 48.689444, + }, + { + lon: 4.984577, + lat: 48.725686, + }, + { + lon: 2.3521, + lat: 48.8565, + }, + ], + spacetimePoints: [], + }, + ]) + .mockImplementationOnce(() => []), +}; + +const createDriverRouteProps: CreateRouteProps = { + roles: [Role.DRIVER], + waypoints: [ + { + position: 0, + ...originCoordinates, + }, + { + position: 1, + ...destinationCoordinates, + }, + ], + georouter: mockGeorouter, + georouterSettings: { + points: true, + detailedDistance: false, + detailedDuration: false, + }, +}; + +const createPassengerRouteProps: CreateRouteProps = { + roles: [Role.PASSENGER], + waypoints: [ + { + position: 0, + ...originCoordinates, + }, + { + position: 1, + ...destinationCoordinates, + }, + ], + georouter: mockGeorouter, + georouterSettings: { + points: true, + detailedDistance: false, + detailedDuration: false, + }, +}; + +const createSimpleDriverAndPassengerRouteProps: CreateRouteProps = { + roles: [Role.DRIVER, Role.PASSENGER], + waypoints: [ + { + position: 0, + ...originCoordinates, + }, + { + position: 1, + ...destinationCoordinates, + }, + ], + georouter: mockGeorouter, + georouterSettings: { + points: true, + detailedDistance: false, + detailedDuration: false, + }, +}; + +const createComplexDriverAndPassengerRouteProps: CreateRouteProps = { + roles: [Role.DRIVER, Role.PASSENGER], + waypoints: [ + { + position: 0, + ...originCoordinates, + }, + { + position: 1, + ...additionalCoordinates, + }, + { + position: 2, + ...destinationCoordinates, + }, + ], + georouter: mockGeorouter, + georouterSettings: { + points: true, + detailedDistance: false, + detailedDuration: false, + }, +}; + +describe('Route entity create', () => { + it('should create a new entity for a driver only', async () => { + const route: RouteEntity = await RouteEntity.create(createDriverRouteProps); + expect(route.id.length).toBe(36); + expect(route.getProps().driverDuration).toBe(14422); + expect(route.getProps().passengerDistance).toBeUndefined(); + }); + it('should create a new entity for a passenger only', async () => { + const route: RouteEntity = await RouteEntity.create( + createPassengerRouteProps, + ); + expect(route.id.length).toBe(36); + expect(route.getProps().passengerDuration).toBe(14423); + expect(route.getProps().driverDistance).toBeUndefined(); + }); + it('should create a new entity for a simple driver and passenger route', async () => { + const route: RouteEntity = await RouteEntity.create( + createSimpleDriverAndPassengerRouteProps, + ); + expect(route.id.length).toBe(36); + expect(route.getProps().driverDuration).toBe(14421); + expect(route.getProps().driverDistance).toBe(350100); + expect(route.getProps().passengerDuration).toBe(14421); + expect(route.getProps().passengerDistance).toBe(350100); + }); + it('should create a new entity for a complex driver and passenger route', async () => { + const route: RouteEntity = await RouteEntity.create( + createComplexDriverAndPassengerRouteProps, + ); + expect(route.id.length).toBe(36); + expect(route.getProps().driverDuration).toBe(14428); + expect(route.getProps().driverDistance).toBe(350108); + expect(route.getProps().passengerDuration).toBe(14428); + expect(route.getProps().passengerDistance).toBe(350108); + }); + it('should throw an exception if route is not found', async () => { + try { + await RouteEntity.create(createDriverRouteProps); + } catch (e: any) { + expect(e).toBeInstanceOf(RouteNotFoundException); + } + }); +});