From 2058bfce4cdd8749511d27edf753840abcb49175 Mon Sep 17 00:00:00 2001 From: sbriat Date: Mon, 11 Sep 2023 12:34:31 +0200 Subject: [PATCH] extract path creator --- .../commands/create-ad/create-ad.service.ts | 103 +++++++++++++----- .../queries/match/algorithm.abstract.ts | 13 ++- .../match/completer/completer.abstract.ts | 6 +- .../passenger-oriented-waypoints.completer.ts | 19 +++- .../queries/match/filter/filter.abstract.ts | 6 +- .../filter/passenger-oriented-geo.filter.ts | 5 +- .../application/queries/match/match.query.ts | 67 ++---------- .../selector/passenger-oriented.selector.ts | 17 ++- .../core/application/types/algorithm.types.ts | 3 +- .../application/types/carpool-route.type.ts | 14 --- .../ad/core/domain/candidate.entity.ts | 15 +++ src/modules/ad/core/domain/candidate.types.ts | 18 +++ .../ad/core/domain/patch-creator.service.ts | 77 +++++++++++++ .../tests/unit/core/create-ad.service.spec.ts | 71 +++++++----- .../passenger-oriented-geo-filter.spec.ts | 50 ++------- .../core/passenger-oriented-selector.spec.ts | 9 +- ...enger-oriented-waypoints-completer.spec.ts | 50 ++------- .../unit/core/path-creator.service.spec.ts | 78 +++++++++++++ 18 files changed, 382 insertions(+), 239 deletions(-) delete mode 100644 src/modules/ad/core/application/types/carpool-route.type.ts create mode 100644 src/modules/ad/core/domain/candidate.entity.ts create mode 100644 src/modules/ad/core/domain/candidate.types.ts create mode 100644 src/modules/ad/core/domain/patch-creator.service.ts create mode 100644 src/modules/ad/tests/unit/core/path-creator.service.spec.ts 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 c197801..55c2788 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 @@ -8,7 +8,13 @@ import { AggregateID, ConflictException } from '@mobicoop/ddd-library'; import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors'; import { RouteProviderPort } from '../../ports/route-provider.port'; import { Role } from '@modules/ad/core/domain/ad.types'; -import { Route } from '../../types/carpool-route.type'; +import { + Path, + PathCreator, + PathType, + TypedRoute, +} from '@modules/ad/core/domain/patch-creator.service'; +import { Point } from '../../types/point.type'; @CommandHandler(CreateAdCommand) export class CreateAdService implements ICommandHandler { @@ -23,36 +29,73 @@ export class CreateAdService implements ICommandHandler { const roles: Role[] = []; if (command.driver) roles.push(Role.DRIVER); if (command.passenger) roles.push(Role.PASSENGER); - const route: Route = await this.routeProvider.getBasic(command.waypoints); - 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: route.points, - driverDistance: route.driverDistance, - driverDuration: route.driverDuration, - passengerDistance: route.passengerDistance, - passengerDuration: route.passengerDuration, - fwdAzimuth: route.fwdAzimuth, - backAzimuth: route.backAzimuth, - }); - + const pathCreator: PathCreator = new PathCreator(roles, command.waypoints); + let typedRoutes: TypedRoute[]; try { - await this.repository.insertExtra(ad, 'ad'); - return ad.id; - } catch (error: any) { - if (error instanceof ConflictException) { - throw new AdAlreadyExistsException(error); - } - throw error; + typedRoutes = await Promise.all( + pathCreator.getPaths().map(async (path: Path) => ({ + type: path.type, + route: await this.routeProvider.getBasic(path.waypoints), + })), + ); + } catch (e: any) { + throw new Error('Unable to find a route for given waypoints'); } + + let driverDistance: number | undefined; + let driverDuration: number | undefined; + let passengerDistance: number | undefined; + let passengerDuration: number | undefined; + let points: Point[] | 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; + fwdAzimuth = typedRoute.route.fwdAzimuth; + backAzimuth = typedRoute.route.backAzimuth; + } + if (typedRoute.type !== PathType.DRIVER) { + passengerDistance = typedRoute.route.distance; + passengerDuration = typedRoute.route.duration; + if (!points) points = typedRoute.route.points; + 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); + } + throw error; + } + } + throw new Error('Route error'); } } diff --git a/src/modules/ad/core/application/queries/match/algorithm.abstract.ts b/src/modules/ad/core/application/queries/match/algorithm.abstract.ts index 7fe7d78..93a4cda 100644 --- a/src/modules/ad/core/application/queries/match/algorithm.abstract.ts +++ b/src/modules/ad/core/application/queries/match/algorithm.abstract.ts @@ -1,10 +1,10 @@ +import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity'; import { MatchEntity } from '../../../domain/match.entity'; -import { Candidate } from '../../types/algorithm.types'; import { MatchQuery } from './match.query'; import { AdRepositoryPort } from '@modules/ad/core/application/ports/ad.repository.port'; export abstract class Algorithm { - protected candidates: Candidate[]; + protected candidates: CandidateEntity[]; protected selector: Selector; protected processors: Processor[]; constructor( @@ -20,8 +20,9 @@ export abstract class Algorithm { for (const processor of this.processors) { this.candidates = await processor.execute(this.candidates); } - return this.candidates.map((candidate: Candidate) => - MatchEntity.create({ adId: candidate.ad.id }), + // console.log(this.candidates); + return this.candidates.map((candidate: CandidateEntity) => + MatchEntity.create({ adId: candidate.id }), ); }; } @@ -36,7 +37,7 @@ export abstract class Selector { this.query = query; this.repository = repository; } - abstract select(): Promise; + abstract select(): Promise; } /** @@ -47,5 +48,5 @@ export abstract class Processor { constructor(query: MatchQuery) { this.query = query; } - abstract execute(candidates: Candidate[]): Promise; + abstract execute(candidates: CandidateEntity[]): Promise; } diff --git a/src/modules/ad/core/application/queries/match/completer/completer.abstract.ts b/src/modules/ad/core/application/queries/match/completer/completer.abstract.ts index 883edd0..ee155b3 100644 --- a/src/modules/ad/core/application/queries/match/completer/completer.abstract.ts +++ b/src/modules/ad/core/application/queries/match/completer/completer.abstract.ts @@ -1,9 +1,9 @@ -import { Candidate } from '../../../types/algorithm.types'; +import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity'; import { Processor } from '../algorithm.abstract'; export abstract class Completer extends Processor { - execute = async (candidates: Candidate[]): Promise => + execute = async (candidates: CandidateEntity[]): Promise => this.complete(candidates); - abstract complete(candidates: Candidate[]): Promise; + abstract complete(candidates: CandidateEntity[]): Promise; } 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 9f2f4ee..8a7104b 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,7 +1,20 @@ -import { Candidate } from '../../../types/algorithm.types'; +import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity'; import { Completer } from './completer.abstract'; +/** + * Complete candidates by setting driver and crew waypoints + */ export class PassengerOrientedWaypointsCompleter extends Completer { - complete = async (candidates: Candidate[]): Promise => - candidates; + complete = async ( + candidates: CandidateEntity[], + ): Promise => candidates; } + +// complete = async (candidates: Candidate[]): Promise => { +// candidates.forEach( (candidate: Candidate) => { +// if (candidate.role == Role.DRIVER) { +// candidate.driverWaypoints = th +// } + +// return candidates; +// } diff --git a/src/modules/ad/core/application/queries/match/filter/filter.abstract.ts b/src/modules/ad/core/application/queries/match/filter/filter.abstract.ts index 8262592..f4522b9 100644 --- a/src/modules/ad/core/application/queries/match/filter/filter.abstract.ts +++ b/src/modules/ad/core/application/queries/match/filter/filter.abstract.ts @@ -1,9 +1,9 @@ -import { Candidate } from '../../../types/algorithm.types'; +import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity'; import { Processor } from '../algorithm.abstract'; export abstract class Filter extends Processor { - execute = async (candidates: Candidate[]): Promise => + execute = async (candidates: CandidateEntity[]): Promise => this.filter(candidates); - abstract filter(candidates: Candidate[]): Promise; + abstract filter(candidates: CandidateEntity[]): Promise; } diff --git a/src/modules/ad/core/application/queries/match/filter/passenger-oriented-geo.filter.ts b/src/modules/ad/core/application/queries/match/filter/passenger-oriented-geo.filter.ts index 2d13980..ca6a558 100644 --- a/src/modules/ad/core/application/queries/match/filter/passenger-oriented-geo.filter.ts +++ b/src/modules/ad/core/application/queries/match/filter/passenger-oriented-geo.filter.ts @@ -1,6 +1,7 @@ -import { Candidate } from '../../../types/algorithm.types'; +import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity'; import { Filter } from './filter.abstract'; export class PassengerOrientedGeoFilter extends Filter { - filter = async (candidates: Candidate[]): Promise => candidates; + filter = async (candidates: CandidateEntity[]): Promise => + candidates; } 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 1b0c559..0716e19 100644 --- a/src/modules/ad/core/application/queries/match/match.query.ts +++ b/src/modules/ad/core/application/queries/match/match.query.ts @@ -1,11 +1,17 @@ import { QueryBase } from '@mobicoop/ddd-library'; import { AlgorithmType } from '../../types/algorithm.types'; import { Waypoint } from '../../types/waypoint.type'; -import { Frequency } from '@modules/ad/core/domain/ad.types'; +import { Frequency, Role } from '@modules/ad/core/domain/ad.types'; import { MatchRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/match.request.dto'; import { DateTimeTransformerPort } from '../../ports/datetime-transformer.port'; import { RouteProviderPort } from '../../ports/route-provider.port'; import { Route } from '@modules/geography/core/domain/route.types'; +import { + Path, + PathCreator, + PathType, + TypedRoute, +} from '@modules/ad/core/domain/patch-creator.service'; export class MatchQuery extends QueryBase { driver?: boolean; @@ -168,10 +174,14 @@ export class MatchQuery extends QueryBase { }; setRoutes = async (routeProvider: RouteProviderPort): Promise => { + 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); try { ( await Promise.all( - this._getPaths().map(async (path: Path) => ({ + pathCreator.getPaths().map(async (path: Path) => ({ type: path.type, route: await routeProvider.getBasic(path.waypoints), })), @@ -192,43 +202,6 @@ export class MatchQuery extends QueryBase { } return this; }; - - private _getPaths = (): Path[] => { - const paths: Path[] = []; - if (this.driver && this.passenger) { - if (this.waypoints.length == 2) { - // 2 points => same route for driver and passenger - paths.push(this._createGenericPath(this.waypoints)); - } else { - paths.push( - this._createDriverPath(this.waypoints), - this._createPassengerPath(this.waypoints), - ); - } - } else if (this.driver) { - paths.push(this._createDriverPath(this.waypoints)); - } else if (this.passenger) { - paths.push(this._createPassengerPath(this.waypoints)); - } - return paths; - }; - - private _createGenericPath = (waypoints: Waypoint[]): Path => - this._createPath(waypoints, PathType.GENERIC); - - private _createDriverPath = (waypoints: Waypoint[]): Path => - this._createPath(waypoints, PathType.DRIVER); - - private _createPassengerPath = (waypoints: Waypoint[]): Path => - this._createPath( - [waypoints[0], waypoints[waypoints.length - 1]], - PathType.PASSENGER, - ); - - private _createPath = (waypoints: Waypoint[], type: PathType): Path => ({ - type, - waypoints, - }); } export type ScheduleItem = { @@ -254,19 +227,3 @@ interface DefaultAlgorithmParameters { maxDetourDistanceRatio: number; maxDetourDurationRatio: number; } - -type Path = { - type: PathType; - waypoints: Waypoint[]; -}; - -enum PathType { - GENERIC = 'generic', - DRIVER = 'driver', - PASSENGER = 'passenger', -} - -type TypedRoute = { - type: PathType; - route: Route; -}; diff --git a/src/modules/ad/core/application/queries/match/selector/passenger-oriented.selector.ts b/src/modules/ad/core/application/queries/match/selector/passenger-oriented.selector.ts index 55a394d..05ab801 100644 --- a/src/modules/ad/core/application/queries/match/selector/passenger-oriented.selector.ts +++ b/src/modules/ad/core/application/queries/match/selector/passenger-oriented.selector.ts @@ -1,13 +1,13 @@ import { Frequency, Role } from '@modules/ad/core/domain/ad.types'; -import { Candidate } from '../../../types/algorithm.types'; import { Selector } from '../algorithm.abstract'; import { AdReadModel } from '@modules/ad/infrastructure/ad.repository'; import { ScheduleItem } from '../match.query'; import { Waypoint } from '../../../types/waypoint.type'; import { Point } from '../../../types/point.type'; +import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity'; export class PassengerOrientedSelector extends Selector { - select = async (): Promise => { + select = async (): Promise => { const queryStringRoles: QueryStringRole[] = []; if (this.query.driver) queryStringRoles.push({ @@ -32,14 +32,11 @@ export class PassengerOrientedSelector extends Selector { ) ) .map((adsRole: AdsRole) => - adsRole.ads.map( - (adReadModel: AdReadModel) => - { - ad: { - id: adReadModel.uuid, - }, - role: adsRole.role, - }, + adsRole.ads.map((adReadModel: AdReadModel) => + CandidateEntity.create({ + id: adReadModel.uuid, + role: adsRole.role, + }), ), ) .flat(); diff --git a/src/modules/ad/core/application/types/algorithm.types.ts b/src/modules/ad/core/application/types/algorithm.types.ts index d26b41d..4d1fd81 100644 --- a/src/modules/ad/core/application/types/algorithm.types.ts +++ b/src/modules/ad/core/application/types/algorithm.types.ts @@ -11,7 +11,8 @@ export enum AlgorithmType { export type Candidate = { ad: Ad; role: Role; - waypoints: Waypoint[]; + driverWaypoints: Waypoint[]; + crewWaypoints: Waypoint[]; }; export type Ad = { diff --git a/src/modules/ad/core/application/types/carpool-route.type.ts b/src/modules/ad/core/application/types/carpool-route.type.ts deleted file mode 100644 index 7b23bfb..0000000 --- a/src/modules/ad/core/application/types/carpool-route.type.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Point } from './point.type'; - -/** - * A carpool route is a route with distance and duration as driver and / or passenger - */ -export type Route = { - driverDistance?: number; - driverDuration?: number; - passengerDistance?: number; - passengerDuration?: number; - fwdAzimuth: number; - backAzimuth: number; - points: Point[]; -}; diff --git a/src/modules/ad/core/domain/candidate.entity.ts b/src/modules/ad/core/domain/candidate.entity.ts new file mode 100644 index 0000000..6da779e --- /dev/null +++ b/src/modules/ad/core/domain/candidate.entity.ts @@ -0,0 +1,15 @@ +import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library'; +import { CandidateProps, CreateCandidateProps } from './candidate.types'; + +export class CandidateEntity extends AggregateRoot { + protected readonly _id: AggregateID; + + static create = (create: CreateCandidateProps): CandidateEntity => { + const props: CandidateProps = { ...create }; + return new CandidateEntity({ id: create.id, props }); + }; + + 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 new file mode 100644 index 0000000..0aaf52e --- /dev/null +++ b/src/modules/ad/core/domain/candidate.types.ts @@ -0,0 +1,18 @@ +import { Role } from './ad.types'; + +// All properties that a Candidate has +export interface CandidateProps { + role: Role; +} + +// Properties that are needed for a Candidate creation +export interface CreateCandidateProps { + id: string; + role: Role; +} + +export type Waypoint = { + lon: number; + lat: number; + position: number; +}; diff --git a/src/modules/ad/core/domain/patch-creator.service.ts b/src/modules/ad/core/domain/patch-creator.service.ts new file mode 100644 index 0000000..4ac4668 --- /dev/null +++ b/src/modules/ad/core/domain/patch-creator.service.ts @@ -0,0 +1,77 @@ +import { Route } from '@modules/geography/core/domain/route.types'; +import { Role } from './ad.types'; +import { Waypoint } from './candidate.types'; + +export class PathCreator { + constructor( + private readonly roles: Role[], + private readonly waypoints: Waypoint[], + ) {} + + public getPaths = (): Path[] => { + const paths: Path[] = []; + if ( + this.roles.includes(Role.DRIVER) && + this.roles.includes(Role.PASSENGER) + ) { + if (this.waypoints.length == 2) { + // 2 points => same route for driver and passenger + paths.push(this._createGenericPath()); + } else { + paths.push(this._createDriverPath(), this._createPassengerPath()); + } + } else if (this.roles.includes(Role.DRIVER)) { + paths.push(this._createDriverPath()); + } else if (this.roles.includes(Role.PASSENGER)) { + paths.push(this._createPassengerPath()); + } + return paths; + }; + + private _createGenericPath = (): Path => + this._createPath(this.waypoints, PathType.GENERIC); + + private _createDriverPath = (): Path => + this._createPath(this.waypoints, PathType.DRIVER); + + private _createPassengerPath = (): Path => + this._createPath( + [this._firstWaypoint(), this._lastWaypoint()], + PathType.PASSENGER, + ); + + private _firstWaypoint = (): Waypoint => + this.waypoints.find( + (waypoint: Waypoint) => waypoint.position == 0, + ) as Waypoint; + + private _lastWaypoint = (): Waypoint => + this.waypoints.find( + (waypoint: Waypoint) => + waypoint.position == + Math.max( + ...this.waypoints.map((waypoint: Waypoint) => waypoint.position), + ), + ) as Waypoint; + + private _createPath = (waypoints: Waypoint[], type: PathType): Path => ({ + type, + waypoints, + }); +} + +export type Path = { + type: PathType; + waypoints: Waypoint[]; +}; + +export type TypedRoute = { + type: PathType; + route: Route; +}; + +export enum PathType { + GENERIC = 'generic', + DRIVER = 'driver', + PASSENGER = 'passenger', +} diff --git a/src/modules/ad/tests/unit/core/create-ad.service.spec.ts b/src/modules/ad/tests/unit/core/create-ad.service.spec.ts index d0cfb3e..6db752e 100644 --- a/src/modules/ad/tests/unit/core/create-ad.service.spec.ts +++ b/src/modules/ad/tests/unit/core/create-ad.service.spec.ts @@ -60,27 +60,40 @@ const mockAdRepository = { }; const mockRouteProvider: RouteProviderPort = { - getBasic: jest.fn().mockImplementation(() => ({ - 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, - }, - ], - })), + getBasic: jest + .fn() + .mockImplementationOnce(() => { + throw new Error(); + }) + .mockImplementationOnce(() => ({ + distance: 350101, + duration: 14422, + fwdAzimuth: 273, + backAzimuth: 93, + distanceAzimuth: 336544, + points: undefined, + })) + .mockImplementation(() => ({ + 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, + }, + ], + })), }; describe('create-ad.service', () => { @@ -110,6 +123,16 @@ describe('create-ad.service', () => { describe('execution', () => { const createAdCommand = new CreateAdCommand(createAdProps); + it('should throw an error if route cant be computed', async () => { + await expect( + createAdService.execute(createAdCommand), + ).rejects.toBeInstanceOf(Error); + }); + it('should throw an error if route is corrupted', async () => { + await expect( + createAdService.execute(createAdCommand), + ).rejects.toBeInstanceOf(Error); + }); it('should create a new ad', async () => { AdEntity.create = jest.fn().mockReturnValue({ id: '047a6ecf-23d4-4d3e-877c-3225d560a8da', @@ -120,17 +143,11 @@ describe('create-ad.service', () => { expect(result).toBe('047a6ecf-23d4-4d3e-877c-3225d560a8da'); }); it('should throw an error if something bad happens', async () => { - AdEntity.create = jest.fn().mockReturnValue({ - id: '047a6ecf-23d4-4d3e-877c-3225d560a8da', - }); await expect( createAdService.execute(createAdCommand), ).rejects.toBeInstanceOf(Error); }); it('should throw an exception if Ad already exists', async () => { - AdEntity.create = jest.fn().mockReturnValue({ - id: '047a6ecf-23d4-4d3e-877c-3225d560a8da', - }); await expect( createAdService.execute(createAdCommand), ).rejects.toBeInstanceOf(AdAlreadyExistsException); diff --git a/src/modules/ad/tests/unit/core/passenger-oriented-geo-filter.spec.ts b/src/modules/ad/tests/unit/core/passenger-oriented-geo-filter.spec.ts index 43f9533..f75ef21 100644 --- a/src/modules/ad/tests/unit/core/passenger-oriented-geo-filter.spec.ts +++ b/src/modules/ad/tests/unit/core/passenger-oriented-geo-filter.spec.ts @@ -1,11 +1,9 @@ import { PassengerOrientedGeoFilter } from '@modules/ad/core/application/queries/match/filter/passenger-oriented-geo.filter'; import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query'; -import { - AlgorithmType, - Candidate, -} from '@modules/ad/core/application/types/algorithm.types'; +import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types'; import { Waypoint } from '@modules/ad/core/application/types/waypoint.type'; import { Frequency, Role } from '@modules/ad/core/domain/ad.types'; +import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity'; const originWaypoint: Waypoint = { position: 0, @@ -42,50 +40,22 @@ const matchQuery = new MatchQuery({ waypoints: [originWaypoint, destinationWaypoint], }); -const candidates: Candidate[] = [ - { - ad: { - id: 'cc260669-1c6d-441f-80a5-19cd59afb777', - }, +const candidates: CandidateEntity[] = [ + CandidateEntity.create({ + id: 'cc260669-1c6d-441f-80a5-19cd59afb777', role: Role.DRIVER, - waypoints: [ - { - position: 0, - lat: 48.68874, - lon: 6.18546, - }, - { - position: 1, - lat: 48.87845, - lon: 2.36547, - }, - ], - }, - { - ad: { - id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0', - }, + }), + CandidateEntity.create({ + id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0', role: Role.PASSENGER, - waypoints: [ - { - position: 0, - lat: 48.69844, - lon: 6.168484, - }, - { - position: 1, - lat: 48.855648, - lon: 2.34645, - }, - ], - }, + }), ]; describe('Passenger oriented geo filter', () => { it('should filter candidates', async () => { const passengerOrientedGeoFilter: PassengerOrientedGeoFilter = new PassengerOrientedGeoFilter(matchQuery); - const filteredCandidates: Candidate[] = + const filteredCandidates: CandidateEntity[] = await passengerOrientedGeoFilter.filter(candidates); expect(filteredCandidates.length).toBe(2); }); diff --git a/src/modules/ad/tests/unit/core/passenger-oriented-selector.spec.ts b/src/modules/ad/tests/unit/core/passenger-oriented-selector.spec.ts index 85911f1..954a021 100644 --- a/src/modules/ad/tests/unit/core/passenger-oriented-selector.spec.ts +++ b/src/modules/ad/tests/unit/core/passenger-oriented-selector.spec.ts @@ -1,12 +1,10 @@ import { AdRepositoryPort } from '@modules/ad/core/application/ports/ad.repository.port'; import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query'; import { PassengerOrientedSelector } from '@modules/ad/core/application/queries/match/selector/passenger-oriented.selector'; -import { - AlgorithmType, - Candidate, -} from '@modules/ad/core/application/types/algorithm.types'; +import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types'; import { Waypoint } from '@modules/ad/core/application/types/waypoint.type'; import { Frequency } from '@modules/ad/core/domain/ad.types'; +import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity'; const originWaypoint: Waypoint = { position: 0, @@ -136,7 +134,8 @@ describe('Passenger oriented selector', () => { it('should select candidates', async () => { const passengerOrientedSelector: PassengerOrientedSelector = new PassengerOrientedSelector(matchQuery, mockMatcherRepository); - const candidates: Candidate[] = await passengerOrientedSelector.select(); + const candidates: CandidateEntity[] = + await passengerOrientedSelector.select(); expect(candidates.length).toBe(2); }); }); 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 43d898b..e06e82b 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 @@ -1,11 +1,9 @@ import { PassengerOrientedWaypointsCompleter } from '@modules/ad/core/application/queries/match/completer/passenger-oriented-waypoints.completer'; import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query'; -import { - AlgorithmType, - Candidate, -} from '@modules/ad/core/application/types/algorithm.types'; +import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types'; import { Waypoint } from '@modules/ad/core/application/types/waypoint.type'; import { Frequency, Role } from '@modules/ad/core/domain/ad.types'; +import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity'; const originWaypoint: Waypoint = { position: 0, @@ -42,50 +40,22 @@ const matchQuery = new MatchQuery({ waypoints: [originWaypoint, destinationWaypoint], }); -const candidates: Candidate[] = [ - { - ad: { - id: 'cc260669-1c6d-441f-80a5-19cd59afb777', - }, +const candidates: CandidateEntity[] = [ + CandidateEntity.create({ + id: 'cc260669-1c6d-441f-80a5-19cd59afb777', role: Role.DRIVER, - waypoints: [ - { - position: 0, - lat: 48.69, - lon: 6.18, - }, - { - position: 1, - lat: 48.87, - lon: 2.37, - }, - ], - }, - { - ad: { - id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0', - }, + }), + CandidateEntity.create({ + id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0', role: Role.PASSENGER, - waypoints: [ - { - position: 0, - lat: 48.63584, - lon: 6.148754, - }, - { - position: 1, - lat: 48.89874, - lon: 2.368745, - }, - ], - }, + }), ]; describe('Passenger oriented waypoints completer', () => { it('should complete candidates', async () => { const passengerOrientedWaypointsCompleter: PassengerOrientedWaypointsCompleter = new PassengerOrientedWaypointsCompleter(matchQuery); - const completedCandidates: Candidate[] = + const completedCandidates: CandidateEntity[] = await passengerOrientedWaypointsCompleter.complete(candidates); expect(completedCandidates.length).toBe(2); }); 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 new file mode 100644 index 0000000..6a9c693 --- /dev/null +++ b/src/modules/ad/tests/unit/core/path-creator.service.spec.ts @@ -0,0 +1,78 @@ +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'; + +const originWaypoint: Waypoint = { + position: 0, + lat: 48.689445, + lon: 6.17651, +}; +const destinationWaypoint: Waypoint = { + position: 1, + lat: 48.8566, + lon: 2.3522, +}; +const intermediateWaypoint: Waypoint = { + position: 1, + lat: 48.74488, + lon: 4.8972, +}; + +describe('Path Creator Service', () => { + it('should create a path for a driver only', () => { + const pathCreator: PathCreator = new PathCreator( + [Role.DRIVER], + [originWaypoint, destinationWaypoint], + ); + const paths: Path[] = pathCreator.getPaths(); + expect(paths).toHaveLength(1); + expect(paths[0].type).toBe(PathType.DRIVER); + }); + it('should create a path for a passenger only', () => { + const pathCreator: PathCreator = new PathCreator( + [Role.PASSENGER], + [originWaypoint, destinationWaypoint], + ); + const paths: Path[] = pathCreator.getPaths(); + expect(paths).toHaveLength(1); + expect(paths[0].type).toBe(PathType.PASSENGER); + }); + it('should create a single path for a driver and passenger', () => { + const pathCreator: PathCreator = new PathCreator( + [Role.DRIVER, Role.PASSENGER], + [originWaypoint, destinationWaypoint], + ); + const paths: Path[] = pathCreator.getPaths(); + expect(paths).toHaveLength(1); + expect(paths[0].type).toBe(PathType.GENERIC); + }); + it('should create two different paths for a driver and passenger with intermediate waypoint', () => { + const pathCreator: PathCreator = new PathCreator( + [Role.DRIVER, Role.PASSENGER], + [ + originWaypoint, + intermediateWaypoint, + { ...destinationWaypoint, position: 2 }, + ], + ); + const paths: Path[] = pathCreator.getPaths(); + expect(paths).toHaveLength(2); + expect( + paths.filter((path: Path) => path.type == PathType.DRIVER), + ).toHaveLength(1); + expect( + paths.filter((path: Path) => path.type == PathType.DRIVER)[0].waypoints, + ).toHaveLength(3); + expect( + paths.filter((path: Path) => path.type == PathType.PASSENGER), + ).toHaveLength(1); + expect( + paths.filter((path: Path) => path.type == PathType.PASSENGER)[0] + .waypoints, + ).toHaveLength(2); + }); +});