From 74fb2c120e538fbfeeb39dd245183f16dcfd593d Mon Sep 17 00:00:00 2001 From: sbriat Date: Wed, 13 Sep 2023 15:28:07 +0200 Subject: [PATCH] bette use of value objects --- src/modules/ad/ad.mapper.ts | 14 ++--- .../commands/create-ad/create-ad.service.ts | 18 +++++- .../application/ports/route-provider.port.ts | 2 +- .../passenger-oriented-waypoints.completer.ts | 55 +++++++++++++++- .../application/queries/match/match.query.ts | 37 +++++++---- .../ad/core/domain/candidate.entity.ts | 5 ++ src/modules/ad/core/domain/candidate.types.ts | 21 +++++-- ...tor.service.ts => path-creator.service.ts} | 4 +- .../value-objects/actor.value-object.ts | 27 ++++++++ .../value-objects/waypoint.value-object.ts | 15 +---- .../value-objects/waystep.value-object.ts | 46 ++++++++++++++ .../core/domain/waysteps-creator.service.ts | 62 +++++++++++++++++++ .../ad/infrastructure/route-provider.ts | 3 +- .../unit/core/actor.value-object.spec.ts | 14 +++++ .../unit/core/match.query-handler.spec.ts | 10 +++ ...enger-oriented-waypoints-completer.spec.ts | 8 +-- .../unit/core/path-creator.service.spec.ts | 31 ++++++---- .../unit/core/waystep.value-object.spec.ts | 62 +++++++++++++++++++ .../queries/get-route/get-route.query.ts | 2 +- .../geography/core/domain/route.types.ts | 1 + .../domain/value-objects/step.value-object.ts | 31 ++++------ .../value-objects/waypoint.value-object.ts | 15 +---- 22 files changed, 387 insertions(+), 96 deletions(-) rename src/modules/ad/core/domain/{patch-creator.service.ts => path-creator.service.ts} (94%) create mode 100644 src/modules/ad/core/domain/value-objects/actor.value-object.ts create mode 100644 src/modules/ad/core/domain/value-objects/waystep.value-object.ts create mode 100644 src/modules/ad/core/domain/waysteps-creator.service.ts create mode 100644 src/modules/ad/tests/unit/core/actor.value-object.spec.ts create mode 100644 src/modules/ad/tests/unit/core/waystep.value-object.spec.ts diff --git a/src/modules/ad/ad.mapper.ts b/src/modules/ad/ad.mapper.ts index 6fbc9b7..f31f7e6 100644 --- a/src/modules/ad/ad.mapper.ts +++ b/src/modules/ad/ad.mapper.ts @@ -14,7 +14,6 @@ import { import { DirectionEncoderPort } from '@modules/geography/core/application/ports/direction-encoder.port'; import { AD_DIRECTION_ENCODER } from './ad.di-tokens'; import { ExtendedMapper } from '@mobicoop/ddd-library'; -import { Waypoint } from './core/domain/value-objects/waypoint.value-object'; /** * Mapper constructs objects that are used in different layers: @@ -112,13 +111,12 @@ export class AdMapper margin: scheduleItem.margin, }), ), - waypoints: this.directionEncoder.decode(record.waypoints).map( - (coordinates, index) => - new Waypoint({ - position: index, - ...coordinates, - }), - ), + waypoints: this.directionEncoder + .decode(record.waypoints) + .map((coordinates, index) => ({ + position: index, + ...coordinates, + })), fwdAzimuth: record.fwdAzimuth, backAzimuth: record.backAzimuth, points: [], diff --git a/src/modules/ad/core/application/commands/create-ad/create-ad.service.ts b/src/modules/ad/core/application/commands/create-ad/create-ad.service.ts index 55c2788..380de24 100644 --- a/src/modules/ad/core/application/commands/create-ad/create-ad.service.ts +++ b/src/modules/ad/core/application/commands/create-ad/create-ad.service.ts @@ -13,8 +13,10 @@ import { PathCreator, PathType, TypedRoute, -} from '@modules/ad/core/domain/patch-creator.service'; +} from '@modules/ad/core/domain/path-creator.service'; import { Point } from '../../types/point.type'; +import { Waypoint } from '../../types/waypoint.type'; +import { Waypoint as WaypointValueObject } from '@modules/ad/core/domain/value-objects/waypoint.value-object'; @CommandHandler(CreateAdCommand) export class CreateAdService implements ICommandHandler { @@ -29,11 +31,21 @@ 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); + const pathCreator: PathCreator = new PathCreator( + roles, + command.waypoints.map( + (waypoint: Waypoint) => + new WaypointValueObject({ + position: waypoint.position, + lon: waypoint.lon, + lat: waypoint.lat, + }), + ), + ); let typedRoutes: TypedRoute[]; try { typedRoutes = await Promise.all( - pathCreator.getPaths().map(async (path: Path) => ({ + pathCreator.getBasePaths().map(async (path: Path) => ({ type: path.type, route: await this.routeProvider.getBasic(path.waypoints), })), diff --git a/src/modules/ad/core/application/ports/route-provider.port.ts b/src/modules/ad/core/application/ports/route-provider.port.ts index 7974886..7d86496 100644 --- a/src/modules/ad/core/application/ports/route-provider.port.ts +++ b/src/modules/ad/core/application/ports/route-provider.port.ts @@ -1,5 +1,5 @@ import { Route } from '@modules/geography/core/domain/route.types'; -import { Waypoint } from '../types/waypoint.type'; +import { Waypoint } from '../../domain/value-objects/waypoint.value-object'; export interface RouteProviderPort { /** diff --git a/src/modules/ad/core/application/queries/match/completer/passenger-oriented-waypoints.completer.ts b/src/modules/ad/core/application/queries/match/completer/passenger-oriented-waypoints.completer.ts index 8a7104b..c281177 100644 --- a/src/modules/ad/core/application/queries/match/completer/passenger-oriented-waypoints.completer.ts +++ b/src/modules/ad/core/application/queries/match/completer/passenger-oriented-waypoints.completer.ts @@ -1,5 +1,12 @@ import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity'; import { Completer } from './completer.abstract'; +import { Role } from '@modules/ad/core/domain/ad.types'; +import { + Waypoint as WaypointValueObject, + WaypointProps, +} from '@modules/ad/core/domain/value-objects/waypoint.value-object'; +import { Waypoint } from '../../../types/waypoint.type'; +import { WayStepsCreator } from '@modules/ad/core/domain/waysteps-creator.service'; /** * Complete candidates by setting driver and crew waypoints @@ -7,7 +14,53 @@ import { Completer } from './completer.abstract'; export class PassengerOrientedWaypointsCompleter extends Completer { complete = async ( candidates: CandidateEntity[], - ): Promise => candidates; + ): Promise => { + candidates.forEach((candidate: CandidateEntity) => { + const carpoolPathCreator = new WayStepsCreator( + candidate.getProps().role == Role.DRIVER + ? candidate.getProps().waypoints.map( + (waypoint: WaypointProps) => + new WaypointValueObject({ + position: waypoint.position, + lon: waypoint.lon, + lat: waypoint.lat, + }), + ) + : this.query.waypoints.map( + (waypoint: Waypoint) => + new WaypointValueObject({ + position: waypoint.position, + lon: waypoint.lon, + lat: waypoint.lat, + }), + ), + candidate.getProps().role == Role.PASSENGER + ? candidate.getProps().waypoints.map( + (waypoint: WaypointProps) => + new WaypointValueObject({ + position: waypoint.position, + lon: waypoint.lon, + lat: waypoint.lat, + }), + ) + : this.query.waypoints.map( + (waypoint: Waypoint) => + new WaypointValueObject({ + position: waypoint.position, + lon: waypoint.lon, + lat: waypoint.lat, + }), + ), + ); + candidate.setWaySteps(carpoolPathCreator.getCrewCarpoolPath()); + }); + // console.log( + // candidates[0] + // .getProps() + // .waySteps?.map((waystep: WayStep) => waystep.actors), + // ); + return candidates; + }; } // complete = async (candidates: Candidate[]): Promise => { diff --git a/src/modules/ad/core/application/queries/match/match.query.ts b/src/modules/ad/core/application/queries/match/match.query.ts index 0716e19..55a31b6 100644 --- a/src/modules/ad/core/application/queries/match/match.query.ts +++ b/src/modules/ad/core/application/queries/match/match.query.ts @@ -11,7 +11,8 @@ import { PathCreator, PathType, TypedRoute, -} from '@modules/ad/core/domain/patch-creator.service'; +} from '@modules/ad/core/domain/path-creator.service'; +import { Waypoint as WaypointValueObject } from '@modules/ad/core/domain/value-objects/waypoint.value-object'; export class MatchQuery extends QueryBase { driver?: boolean; @@ -37,6 +38,7 @@ export class MatchQuery extends QueryBase { driverRoute?: Route; passengerRoute?: Route; backAzimuth?: number; + private readonly originWaypoint: Waypoint; constructor(props: MatchRequestDto) { super(); @@ -60,6 +62,9 @@ export class MatchQuery extends QueryBase { this.maxDetourDurationRatio = props.maxDetourDurationRatio; this.page = props.page ?? 1; this.perPage = props.perPage ?? 10; + this.originWaypoint = this.waypoints.filter( + (waypoint: Waypoint) => waypoint.position == 0, + )[0]; } setMissingMarginDurations = (defaultMarginDuration: number): MatchQuery => { @@ -126,8 +131,8 @@ export class MatchQuery extends QueryBase { date: initialFromDate, time: this.schedule[0].time, coordinates: { - lon: this.waypoints[0].lon, - lat: this.waypoints[0].lat, + lon: this.originWaypoint.lon, + lat: this.originWaypoint.lat, }, }, this.frequency, @@ -138,8 +143,8 @@ export class MatchQuery extends QueryBase { date: initialFromDate, time: this.schedule[0].time, coordinates: { - lon: this.waypoints[0].lon, - lat: this.waypoints[0].lat, + lon: this.originWaypoint.lon, + lat: this.originWaypoint.lat, }, }, this.frequency, @@ -151,8 +156,8 @@ export class MatchQuery extends QueryBase { date: this.fromDate, time: scheduleItem.time, coordinates: { - lon: this.waypoints[0].lon, - lat: this.waypoints[0].lat, + lon: this.originWaypoint.lon, + lat: this.originWaypoint.lat, }, }, this.frequency, @@ -162,8 +167,8 @@ export class MatchQuery extends QueryBase { date: this.fromDate, time: scheduleItem.time, coordinates: { - lon: this.waypoints[0].lon, - lat: this.waypoints[0].lat, + lon: this.originWaypoint.lon, + lat: this.originWaypoint.lat, }, }, this.frequency, @@ -177,11 +182,21 @@ export class MatchQuery extends QueryBase { const roles: Role[] = []; if (this.driver) roles.push(Role.DRIVER); if (this.passenger) roles.push(Role.PASSENGER); - const pathCreator: PathCreator = new PathCreator(roles, this.waypoints); + const pathCreator: PathCreator = new PathCreator( + roles, + this.waypoints.map( + (waypoint: Waypoint) => + new WaypointValueObject({ + position: waypoint.position, + lon: waypoint.lon, + lat: waypoint.lat, + }), + ), + ); try { ( await Promise.all( - pathCreator.getPaths().map(async (path: Path) => ({ + pathCreator.getBasePaths().map(async (path: Path) => ({ type: path.type, route: await routeProvider.getBasic(path.waypoints), })), diff --git a/src/modules/ad/core/domain/candidate.entity.ts b/src/modules/ad/core/domain/candidate.entity.ts index 6da779e..3ebd818 100644 --- a/src/modules/ad/core/domain/candidate.entity.ts +++ b/src/modules/ad/core/domain/candidate.entity.ts @@ -1,5 +1,6 @@ import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library'; import { CandidateProps, CreateCandidateProps } from './candidate.types'; +import { WayStepProps } from './value-objects/waystep.value-object'; export class CandidateEntity extends AggregateRoot { protected readonly _id: AggregateID; @@ -9,6 +10,10 @@ export class CandidateEntity extends AggregateRoot { return new CandidateEntity({ id: create.id, props }); }; + setWaySteps = (waySteps: WayStepProps[]): void => { + this.props.waySteps = waySteps; + }; + validate(): void { // entity business rules validation to protect it's invariant before saving entity to a database } diff --git a/src/modules/ad/core/domain/candidate.types.ts b/src/modules/ad/core/domain/candidate.types.ts index 65e8cd6..7f48f73 100644 --- a/src/modules/ad/core/domain/candidate.types.ts +++ b/src/modules/ad/core/domain/candidate.types.ts @@ -1,20 +1,29 @@ import { Role } from './ad.types'; +import { WaypointProps } from './value-objects/waypoint.value-object'; +import { WayStepProps } from './value-objects/waystep.value-object'; // All properties that a Candidate has export interface CandidateProps { role: Role; - waypoints: Waypoint[]; + waypoints: WaypointProps[]; // waypoints of the original Ad + waySteps?: WayStepProps[]; // carpool path for the crew (driver + passenger) } // Properties that are needed for a Candidate creation export interface CreateCandidateProps { id: string; role: Role; - waypoints: Waypoint[]; + waypoints: WaypointProps[]; } -export type Waypoint = { - lon: number; - lat: number; - position: number; +export type Spacetime = { + duration: number; + distance?: number; }; + +export enum Target { + START = 'START', + INTERMEDIATE = 'INTERMEDIATE', + FINISH = 'FINISH', + NEUTRAL = 'NEUTRAL', +} diff --git a/src/modules/ad/core/domain/patch-creator.service.ts b/src/modules/ad/core/domain/path-creator.service.ts similarity index 94% rename from src/modules/ad/core/domain/patch-creator.service.ts rename to src/modules/ad/core/domain/path-creator.service.ts index 4ac4668..a682173 100644 --- a/src/modules/ad/core/domain/patch-creator.service.ts +++ b/src/modules/ad/core/domain/path-creator.service.ts @@ -1,6 +1,6 @@ import { Route } from '@modules/geography/core/domain/route.types'; import { Role } from './ad.types'; -import { Waypoint } from './candidate.types'; +import { Waypoint } from './value-objects/waypoint.value-object'; export class PathCreator { constructor( @@ -8,7 +8,7 @@ export class PathCreator { private readonly waypoints: Waypoint[], ) {} - public getPaths = (): Path[] => { + public getBasePaths = (): Path[] => { const paths: Path[] = []; if ( this.roles.includes(Role.DRIVER) && diff --git a/src/modules/ad/core/domain/value-objects/actor.value-object.ts b/src/modules/ad/core/domain/value-objects/actor.value-object.ts new file mode 100644 index 0000000..d73c260 --- /dev/null +++ b/src/modules/ad/core/domain/value-objects/actor.value-object.ts @@ -0,0 +1,27 @@ +import { ValueObject } from '@mobicoop/ddd-library'; +import { Role } from '../ad.types'; +import { Target } from '../candidate.types'; + +/** Note: + * Value Objects with multiple properties can contain + * other Value Objects inside if needed. + * */ + +export interface ActorProps { + role: Role; + target: Target; +} + +export class Actor extends ValueObject { + get role(): Role { + return this.props.role; + } + + get target(): Target { + return this.props.target; + } + + protected validate(): void { + return; + } +} diff --git a/src/modules/ad/core/domain/value-objects/waypoint.value-object.ts b/src/modules/ad/core/domain/value-objects/waypoint.value-object.ts index 353f51d..d18fe04 100644 --- a/src/modules/ad/core/domain/value-objects/waypoint.value-object.ts +++ b/src/modules/ad/core/domain/value-objects/waypoint.value-object.ts @@ -1,18 +1,13 @@ -import { - ArgumentInvalidException, - ArgumentOutOfRangeException, - ValueObject, -} from '@mobicoop/ddd-library'; +import { ArgumentInvalidException, ValueObject } from '@mobicoop/ddd-library'; +import { PointProps } from './point.value-object'; /** Note: * Value Objects with multiple properties can contain * other Value Objects inside if needed. * */ -export interface WaypointProps { +export interface WaypointProps extends PointProps { position: number; - lon: number; - lat: number; } export class Waypoint extends ValueObject { @@ -33,9 +28,5 @@ export class Waypoint extends ValueObject { throw new ArgumentInvalidException( 'position must be greater than or equal to 0', ); - if (props.lon > 180 || props.lon < -180) - throw new ArgumentOutOfRangeException('lon must be between -180 and 180'); - if (props.lat > 90 || props.lat < -90) - throw new ArgumentOutOfRangeException('lat must be between -90 and 90'); } } diff --git a/src/modules/ad/core/domain/value-objects/waystep.value-object.ts b/src/modules/ad/core/domain/value-objects/waystep.value-object.ts new file mode 100644 index 0000000..306f3f9 --- /dev/null +++ b/src/modules/ad/core/domain/value-objects/waystep.value-object.ts @@ -0,0 +1,46 @@ +import { + ArgumentOutOfRangeException, + ValueObject, +} from '@mobicoop/ddd-library'; +import { WaypointProps } from './waypoint.value-object'; +import { Actor } from './actor.value-object'; +import { Role } from '../ad.types'; + +/** Note: + * Value Objects with multiple properties can contain + * other Value Objects inside if needed. + * */ + +export interface WayStepProps extends WaypointProps { + actors: Actor[]; +} + +export class WayStep extends ValueObject { + get position(): number { + return this.props.position; + } + + get lon(): number { + return this.props.lon; + } + + get lat(): number { + return this.props.lat; + } + + get actors(): Actor[] { + return this.props.actors; + } + + protected validate(props: WayStepProps): void { + if (props.actors.length <= 0) + throw new ArgumentOutOfRangeException('at least one actor is required'); + if ( + props.actors.filter((actor: Actor) => actor.role == Role.DRIVER).length > + 1 + ) + throw new ArgumentOutOfRangeException( + 'a waystep can contain only one driver', + ); + } +} diff --git a/src/modules/ad/core/domain/waysteps-creator.service.ts b/src/modules/ad/core/domain/waysteps-creator.service.ts new file mode 100644 index 0000000..1d9fb05 --- /dev/null +++ b/src/modules/ad/core/domain/waysteps-creator.service.ts @@ -0,0 +1,62 @@ +import { Role } from './ad.types'; +import { Target } from './candidate.types'; +import { Actor } from './value-objects/actor.value-object'; +import { Waypoint } from './value-objects/waypoint.value-object'; +import { WayStep } from './value-objects/waystep.value-object'; + +export class WayStepsCreator { + constructor( + private readonly driverWaypoints: Waypoint[], + private readonly passengerWaypoints: Waypoint[], + ) {} + + public getCrewCarpoolPath = (): WayStep[] => this._createPassengerWaysteps(); + + private _createPassengerWaysteps = (): WayStep[] => { + const waysteps: WayStep[] = []; + this.passengerWaypoints.forEach((passengerWaypoint: Waypoint) => { + const waystep: WayStep = new WayStep({ + lon: passengerWaypoint.lon, + lat: passengerWaypoint.lat, + position: passengerWaypoint.position, + actors: [ + new Actor({ + role: Role.PASSENGER, + target: this._getTarget( + passengerWaypoint.position, + this.passengerWaypoints, + ), + }), + ], + }); + if ( + this.driverWaypoints.filter((driverWaypoint: Waypoint) => + this._isSameWaypoint(driverWaypoint, passengerWaypoint), + ).length > 0 + ) { + waystep.actors.push( + new Actor({ + role: Role.DRIVER, + target: Target.NEUTRAL, + }), + ); + } + waysteps.push(waystep); + }); + return waysteps; + }; + + private _isSameWaypoint = ( + waypoint1: Waypoint, + waypoint2: Waypoint, + ): boolean => + waypoint1.lon === waypoint2.lon && waypoint1.lat === waypoint2.lat; + + private _getTarget = (position: number, waypoints: Waypoint[]): Target => + position == 0 + ? Target.START + : position == + Math.max(...waypoints.map((waypoint: Waypoint) => waypoint.position)) + ? Target.FINISH + : Target.INTERMEDIATE; +} diff --git a/src/modules/ad/infrastructure/route-provider.ts b/src/modules/ad/infrastructure/route-provider.ts index 52a0189..5a7b1b0 100644 --- a/src/modules/ad/infrastructure/route-provider.ts +++ b/src/modules/ad/infrastructure/route-provider.ts @@ -1,9 +1,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { RouteProviderPort } from '../core/application/ports/route-provider.port'; -import { Waypoint } from '../core/application/types/waypoint.type'; import { GetBasicRouteControllerPort } from '@modules/geography/core/application/ports/get-basic-route-controller.port'; import { AD_GET_BASIC_ROUTE_CONTROLLER } from '../ad.di-tokens'; -import { Route } from '@modules/geography/core/domain/route.types'; +import { Route, Waypoint } from '@modules/geography/core/domain/route.types'; @Injectable() export class RouteProvider implements RouteProviderPort { diff --git a/src/modules/ad/tests/unit/core/actor.value-object.spec.ts b/src/modules/ad/tests/unit/core/actor.value-object.spec.ts new file mode 100644 index 0000000..9e4c473 --- /dev/null +++ b/src/modules/ad/tests/unit/core/actor.value-object.spec.ts @@ -0,0 +1,14 @@ +import { Role } from '@modules/ad/core/domain/ad.types'; +import { Target } from '@modules/ad/core/domain/candidate.types'; +import { Actor } from '@modules/ad/core/domain/value-objects/actor.value-object'; + +describe('Actor value object', () => { + it('should create an actor value object', () => { + const actorVO = new Actor({ + role: Role.DRIVER, + target: Target.START, + }); + expect(actorVO.role).toBe(Role.DRIVER); + expect(actorVO.target).toBe(Target.START); + }); +}); diff --git a/src/modules/ad/tests/unit/core/match.query-handler.spec.ts b/src/modules/ad/tests/unit/core/match.query-handler.spec.ts index 5f99b43..d02303c 100644 --- a/src/modules/ad/tests/unit/core/match.query-handler.spec.ts +++ b/src/modules/ad/tests/unit/core/match.query-handler.spec.ts @@ -40,6 +40,16 @@ const mockAdRepository = { id: 'cc260669-1c6d-441f-80a5-19cd59afb777', getProps: jest.fn().mockImplementation(() => ({ role: Role.DRIVER, + waypoints: [ + { + lat: 48.68787, + lon: 6.165871, + }, + { + lat: 48.97878, + lon: 2.45787, + }, + ], })), }, ]), diff --git a/src/modules/ad/tests/unit/core/passenger-oriented-waypoints-completer.spec.ts b/src/modules/ad/tests/unit/core/passenger-oriented-waypoints-completer.spec.ts index 5900ae5..9700b77 100644 --- a/src/modules/ad/tests/unit/core/passenger-oriented-waypoints-completer.spec.ts +++ b/src/modules/ad/tests/unit/core/passenger-oriented-waypoints-completer.spec.ts @@ -63,13 +63,13 @@ const candidates: CandidateEntity[] = [ waypoints: [ { position: 0, - lat: 48.668487, - lon: 6.178457, + lat: 48.689445, + lon: 6.17651, }, { position: 1, - lat: 48.897457, - lon: 2.3688487, + lat: 48.8566, + lon: 2.3522, }, ], }), diff --git a/src/modules/ad/tests/unit/core/path-creator.service.spec.ts b/src/modules/ad/tests/unit/core/path-creator.service.spec.ts index 6a9c693..d2121a9 100644 --- a/src/modules/ad/tests/unit/core/path-creator.service.spec.ts +++ b/src/modules/ad/tests/unit/core/path-creator.service.spec.ts @@ -1,26 +1,31 @@ import { Role } from '@modules/ad/core/domain/ad.types'; -import { Waypoint } from '@modules/ad/core/domain/candidate.types'; import { Path, PathCreator, PathType, -} from '@modules/ad/core/domain/patch-creator.service'; +} from '@modules/ad/core/domain/path-creator.service'; +import { Waypoint } from '@modules/ad/core/domain/value-objects/waypoint.value-object'; -const originWaypoint: Waypoint = { +const originWaypoint: Waypoint = new Waypoint({ position: 0, lat: 48.689445, lon: 6.17651, -}; -const destinationWaypoint: Waypoint = { +}); +const destinationWaypoint: Waypoint = new Waypoint({ position: 1, lat: 48.8566, lon: 2.3522, -}; -const intermediateWaypoint: Waypoint = { +}); +const intermediateWaypoint: Waypoint = new Waypoint({ position: 1, lat: 48.74488, lon: 4.8972, -}; +}); +const destinationWaypointWithIntermediateWaypoint: Waypoint = new Waypoint({ + position: 2, + lat: 48.8566, + lon: 2.3522, +}); describe('Path Creator Service', () => { it('should create a path for a driver only', () => { @@ -28,7 +33,7 @@ describe('Path Creator Service', () => { [Role.DRIVER], [originWaypoint, destinationWaypoint], ); - const paths: Path[] = pathCreator.getPaths(); + const paths: Path[] = pathCreator.getBasePaths(); expect(paths).toHaveLength(1); expect(paths[0].type).toBe(PathType.DRIVER); }); @@ -37,7 +42,7 @@ describe('Path Creator Service', () => { [Role.PASSENGER], [originWaypoint, destinationWaypoint], ); - const paths: Path[] = pathCreator.getPaths(); + const paths: Path[] = pathCreator.getBasePaths(); expect(paths).toHaveLength(1); expect(paths[0].type).toBe(PathType.PASSENGER); }); @@ -46,7 +51,7 @@ describe('Path Creator Service', () => { [Role.DRIVER, Role.PASSENGER], [originWaypoint, destinationWaypoint], ); - const paths: Path[] = pathCreator.getPaths(); + const paths: Path[] = pathCreator.getBasePaths(); expect(paths).toHaveLength(1); expect(paths[0].type).toBe(PathType.GENERIC); }); @@ -56,10 +61,10 @@ describe('Path Creator Service', () => { [ originWaypoint, intermediateWaypoint, - { ...destinationWaypoint, position: 2 }, + destinationWaypointWithIntermediateWaypoint, ], ); - const paths: Path[] = pathCreator.getPaths(); + const paths: Path[] = pathCreator.getBasePaths(); expect(paths).toHaveLength(2); expect( paths.filter((path: Path) => path.type == PathType.DRIVER), diff --git a/src/modules/ad/tests/unit/core/waystep.value-object.spec.ts b/src/modules/ad/tests/unit/core/waystep.value-object.spec.ts new file mode 100644 index 0000000..472cdb4 --- /dev/null +++ b/src/modules/ad/tests/unit/core/waystep.value-object.spec.ts @@ -0,0 +1,62 @@ +import { ArgumentOutOfRangeException } from '@mobicoop/ddd-library'; +import { Role } from '@modules/ad/core/domain/ad.types'; +import { Target } from '@modules/ad/core/domain/candidate.types'; +import { Actor } from '@modules/ad/core/domain/value-objects/actor.value-object'; +import { WayStep } from '@modules/ad/core/domain/value-objects/waystep.value-object'; + +describe('WayStep value object', () => { + it('should create a waystep value object', () => { + const wayStepVO = new WayStep({ + lat: 48.689445, + lon: 6.17651, + position: 0, + actors: [ + new Actor({ + role: Role.DRIVER, + target: Target.NEUTRAL, + }), + new Actor({ + role: Role.PASSENGER, + target: Target.START, + }), + ], + }); + expect(wayStepVO.position).toBe(0); + expect(wayStepVO.lon).toBe(6.17651); + expect(wayStepVO.lat).toBe(48.689445); + expect(wayStepVO.actors).toHaveLength(2); + }); + it('should throw an exception if actors is empty', () => { + try { + new WayStep({ + lat: 48.689445, + lon: 6.17651, + position: 0, + actors: [], + }); + } catch (e: any) { + expect(e).toBeInstanceOf(ArgumentOutOfRangeException); + } + }); + it('should throw an exception if actors contains more than one driver', () => { + try { + new WayStep({ + lat: 48.689445, + lon: 6.17651, + position: 0, + actors: [ + new Actor({ + role: Role.DRIVER, + target: Target.NEUTRAL, + }), + new Actor({ + role: Role.DRIVER, + target: Target.START, + }), + ], + }); + } catch (e: any) { + expect(e).toBeInstanceOf(ArgumentOutOfRangeException); + } + }); +}); diff --git a/src/modules/geography/core/application/queries/get-route/get-route.query.ts b/src/modules/geography/core/application/queries/get-route/get-route.query.ts index 0c936d9..6697942 100644 --- a/src/modules/geography/core/application/queries/get-route/get-route.query.ts +++ b/src/modules/geography/core/application/queries/get-route/get-route.query.ts @@ -1,6 +1,6 @@ import { QueryBase } from '@mobicoop/ddd-library'; -import { Waypoint } from '@modules/geography/core/domain/route.types'; import { GeorouterSettings } from '../../types/georouter-settings.type'; +import { Waypoint } from '@modules/geography/core/domain/route.types'; export class GetRouteQuery extends QueryBase { readonly waypoints: Waypoint[]; diff --git a/src/modules/geography/core/domain/route.types.ts b/src/modules/geography/core/domain/route.types.ts index e6df76c..e6ff203 100644 --- a/src/modules/geography/core/domain/route.types.ts +++ b/src/modules/geography/core/domain/route.types.ts @@ -20,6 +20,7 @@ export interface CreateRouteProps { georouterSettings: GeorouterSettings; } +// Types used outside the domain export type Route = { distance: number; duration: number; diff --git a/src/modules/geography/core/domain/value-objects/step.value-object.ts b/src/modules/geography/core/domain/value-objects/step.value-object.ts index a5c180f..5c04757 100644 --- a/src/modules/geography/core/domain/value-objects/step.value-object.ts +++ b/src/modules/geography/core/domain/value-objects/step.value-object.ts @@ -1,30 +1,17 @@ -import { - ArgumentInvalidException, - ArgumentOutOfRangeException, - ValueObject, -} from '@mobicoop/ddd-library'; +import { ArgumentInvalidException, ValueObject } from '@mobicoop/ddd-library'; +import { PointProps } from './point.value-object'; /** Note: * Value Objects with multiple properties can contain * other Value Objects inside if needed. * */ -export interface StepProps { - lon: number; - lat: number; +export interface StepProps extends PointProps { duration: number; distance: number; } export class Step extends ValueObject { - get lon(): number { - return this.props.lon; - } - - get lat(): number { - return this.props.lat; - } - get duration(): number { return this.props.duration; } @@ -33,6 +20,14 @@ export class Step extends ValueObject { return this.props.distance; } + get lon(): number { + return this.props.lon; + } + + get lat(): number { + return this.props.lat; + } + protected validate(props: StepProps): void { if (props.duration < 0) throw new ArgumentInvalidException( @@ -42,9 +37,5 @@ export class Step extends ValueObject { throw new ArgumentInvalidException( 'distance must be greater than or equal to 0', ); - if (props.lon > 180 || props.lon < -180) - throw new ArgumentOutOfRangeException('lon must be between -180 and 180'); - if (props.lat > 90 || props.lat < -90) - throw new ArgumentOutOfRangeException('lat must be between -90 and 90'); } } diff --git a/src/modules/geography/core/domain/value-objects/waypoint.value-object.ts b/src/modules/geography/core/domain/value-objects/waypoint.value-object.ts index 353f51d..d18fe04 100644 --- a/src/modules/geography/core/domain/value-objects/waypoint.value-object.ts +++ b/src/modules/geography/core/domain/value-objects/waypoint.value-object.ts @@ -1,18 +1,13 @@ -import { - ArgumentInvalidException, - ArgumentOutOfRangeException, - ValueObject, -} from '@mobicoop/ddd-library'; +import { ArgumentInvalidException, ValueObject } from '@mobicoop/ddd-library'; +import { PointProps } from './point.value-object'; /** Note: * Value Objects with multiple properties can contain * other Value Objects inside if needed. * */ -export interface WaypointProps { +export interface WaypointProps extends PointProps { position: number; - lon: number; - lat: number; } export class Waypoint extends ValueObject { @@ -33,9 +28,5 @@ export class Waypoint extends ValueObject { throw new ArgumentInvalidException( 'position must be greater than or equal to 0', ); - if (props.lon > 180 || props.lon < -180) - throw new ArgumentOutOfRangeException('lon must be between -180 and 180'); - if (props.lat > 90 || props.lat < -90) - throw new ArgumentOutOfRangeException('lat must be between -90 and 90'); } }