extract path creator
This commit is contained in:
parent
59a2644bb4
commit
2058bfce4c
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Candidate[]>;
|
||||
abstract select(): Promise<CandidateEntity[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -47,5 +48,5 @@ export abstract class Processor {
|
|||
constructor(query: MatchQuery) {
|
||||
this.query = query;
|
||||
}
|
||||
abstract execute(candidates: Candidate[]): Promise<Candidate[]>;
|
||||
abstract execute(candidates: CandidateEntity[]): Promise<CandidateEntity[]>;
|
||||
}
|
||||
|
|
|
@ -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<Candidate[]> =>
|
||||
execute = async (candidates: CandidateEntity[]): Promise<CandidateEntity[]> =>
|
||||
this.complete(candidates);
|
||||
|
||||
abstract complete(candidates: Candidate[]): Promise<Candidate[]>;
|
||||
abstract complete(candidates: CandidateEntity[]): Promise<CandidateEntity[]>;
|
||||
}
|
||||
|
|
|
@ -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<Candidate[]> =>
|
||||
candidates;
|
||||
complete = async (
|
||||
candidates: CandidateEntity[],
|
||||
): Promise<CandidateEntity[]> => candidates;
|
||||
}
|
||||
|
||||
// complete = async (candidates: Candidate[]): Promise<Candidate[]> => {
|
||||
// candidates.forEach( (candidate: Candidate) => {
|
||||
// if (candidate.role == Role.DRIVER) {
|
||||
// candidate.driverWaypoints = th
|
||||
// }
|
||||
|
||||
// return candidates;
|
||||
// }
|
||||
|
|
|
@ -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<Candidate[]> =>
|
||||
execute = async (candidates: CandidateEntity[]): Promise<CandidateEntity[]> =>
|
||||
this.filter(candidates);
|
||||
|
||||
abstract filter(candidates: Candidate[]): Promise<Candidate[]>;
|
||||
abstract filter(candidates: CandidateEntity[]): Promise<CandidateEntity[]>;
|
||||
}
|
||||
|
|
|
@ -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<Candidate[]> => candidates;
|
||||
filter = async (candidates: CandidateEntity[]): Promise<CandidateEntity[]> =>
|
||||
candidates;
|
||||
}
|
||||
|
|
|
@ -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<MatchQuery> => {
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -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<Candidate[]> => {
|
||||
select = async (): Promise<CandidateEntity[]> => {
|
||||
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) =>
|
||||
<Candidate>{
|
||||
ad: {
|
||||
id: adReadModel.uuid,
|
||||
},
|
||||
role: adsRole.role,
|
||||
},
|
||||
adsRole.ads.map((adReadModel: AdReadModel) =>
|
||||
CandidateEntity.create({
|
||||
id: adReadModel.uuid,
|
||||
role: adsRole.role,
|
||||
}),
|
||||
),
|
||||
)
|
||||
.flat();
|
||||
|
|
|
@ -11,7 +11,8 @@ export enum AlgorithmType {
|
|||
export type Candidate = {
|
||||
ad: Ad;
|
||||
role: Role;
|
||||
waypoints: Waypoint[];
|
||||
driverWaypoints: Waypoint[];
|
||||
crewWaypoints: Waypoint[];
|
||||
};
|
||||
|
||||
export type Ad = {
|
||||
|
|
|
@ -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[];
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
|
||||
import { CandidateProps, CreateCandidateProps } from './candidate.types';
|
||||
|
||||
export class CandidateEntity extends AggregateRoot<CandidateProps> {
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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',
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue