mirror of
https://gitlab.com/mobicoop/v3/service/matcher.git
synced 2026-01-01 04:32:41 +00:00
Compare commits
18 Commits
amqp-geogr
...
1.6_checkc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f759581157 | ||
|
|
08b5af7511 | ||
|
|
4581af5e9f | ||
|
|
7f7a51d19b | ||
|
|
739d05b095 | ||
|
|
212b609e26 | ||
|
|
bd6fc1576b | ||
|
|
53df6183bd | ||
|
|
ffeb009497 | ||
|
|
3786fcc2c2 | ||
|
|
e53c12ba74 | ||
|
|
c5a5e33256 | ||
|
|
924547c316 | ||
|
|
5f8dd8b4a0 | ||
|
|
90ae3cf9cb | ||
|
|
6b9bf53b4a | ||
|
|
4fd2950027 | ||
|
|
2ce2a46c95 |
@@ -7,6 +7,8 @@ stages:
|
|||||||
include:
|
include:
|
||||||
- template: Security/SAST.gitlab-ci.yml
|
- template: Security/SAST.gitlab-ci.yml
|
||||||
- template: Security/Secret-Detection.gitlab-ci.yml
|
- template: Security/Secret-Detection.gitlab-ci.yml
|
||||||
|
- project: mobicoop/v3/gitlab-templates
|
||||||
|
file: /ci/release.build-job.yml
|
||||||
|
|
||||||
##############
|
##############
|
||||||
# TEST STAGE #
|
# TEST STAGE #
|
||||||
@@ -28,31 +30,3 @@ test:
|
|||||||
rules:
|
rules:
|
||||||
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH || $CI_COMMIT_MESSAGE =~ /--check/ || $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
|
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH || $CI_COMMIT_MESSAGE =~ /--check/ || $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
|
||||||
when: always
|
when: always
|
||||||
|
|
||||||
###############
|
|
||||||
# BUILD STAGE #
|
|
||||||
###############
|
|
||||||
|
|
||||||
build:
|
|
||||||
stage: build
|
|
||||||
image: docker:20.10.22
|
|
||||||
variables:
|
|
||||||
DOCKER_TLS_CERTDIR: ''
|
|
||||||
services:
|
|
||||||
- docker:dind
|
|
||||||
before_script:
|
|
||||||
- echo -n $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
|
|
||||||
script:
|
|
||||||
- export VERSION=$(docker run --rm -v "$PWD":/usr/src/app:ro -w /usr/src/app node:slim node -p "require('./package.json').version")
|
|
||||||
- docker pull $CI_REGISTRY_IMAGE:latest || true
|
|
||||||
- >
|
|
||||||
docker build
|
|
||||||
--pull
|
|
||||||
--cache-from $CI_REGISTRY_IMAGE:latest
|
|
||||||
--tag $CI_REGISTRY_IMAGE:$VERSION
|
|
||||||
--tag $CI_REGISTRY_IMAGE:latest
|
|
||||||
.
|
|
||||||
- docker push $CI_REGISTRY_IMAGE:$VERSION
|
|
||||||
- docker push $CI_REGISTRY_IMAGE:latest
|
|
||||||
only:
|
|
||||||
- main
|
|
||||||
|
|||||||
@@ -156,10 +156,14 @@ The app exposes the following [gRPC](https://grpc.io/) services :
|
|||||||
- **strict** (boolean, optional): if set to true, allow matching only with similar frequency ads (_default : false_)
|
- **strict** (boolean, optional): if set to true, allow matching only with similar frequency ads (_default : false_)
|
||||||
- **fromDate**: start date for recurrent ad, carpool date for punctual ad
|
- **fromDate**: start date for recurrent ad, carpool date for punctual ad
|
||||||
- **toDate**: end date for recurrent ad, same as fromDate for punctual ad
|
- **toDate**: end date for recurrent ad, same as fromDate for punctual ad
|
||||||
- **schedule**: an array of schedule items, a schedule item containing :
|
- **schedule**: an optional array of schedule items, a schedule item containing :
|
||||||
|
|
||||||
- the week day as a number, from 0 (sunday) to 6 (saturday) if the ad is recurrent (default to fromDate day for punctual search)
|
- the week day as a number, from 0 (sunday) to 6 (saturday) if the ad is recurrent (default to fromDate day for punctual search)
|
||||||
- the departure time (as HH:MM)
|
- the departure time (as HH:MM)
|
||||||
- the margin around the departure time in seconds (optional) (_default : 900_)
|
- the margin around the departure time in seconds (optional) (_default : 900_)
|
||||||
|
|
||||||
|
_If the schedule is not set, the driver departure time is guessed to be the ideal departure time to reach the passenger, and the passenger departure time is guessed to be the ideal pick up time for the driver_
|
||||||
|
|
||||||
- **seatsProposed** (integer, optional): number of seats proposed as driver (_default : 3_)
|
- **seatsProposed** (integer, optional): number of seats proposed as driver (_default : 3_)
|
||||||
- **seatsRequested** (integer, optional): number of seats requested as passenger (_default : 1_)
|
- **seatsRequested** (integer, optional): number of seats requested as passenger (_default : 1_)
|
||||||
- **waypoints**: an array of addresses that represent the waypoints of the journey (only first and last waypoints are used for passenger ads). Note that positions are **required** and **must** be consecutives
|
- **waypoints**: an array of addresses that represent the waypoints of the journey (only first and last waypoints are used for passenger ads). Note that positions are **required** and **must** be consecutives
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ services:
|
|||||||
- .:/usr/src/app
|
- .:/usr/src/app
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
command: npm run start:dev
|
command: npm run start:debug
|
||||||
ports:
|
ports:
|
||||||
- ${SERVICE_PORT:-5005}:${SERVICE_PORT:-5005}
|
- ${SERVICE_PORT:-5005}:${SERVICE_PORT:-5005}
|
||||||
- ${HEALTH_SERVICE_PORT:-6005}:${HEALTH_SERVICE_PORT:-6005}
|
- ${HEALTH_SERVICE_PORT:-6005}:${HEALTH_SERVICE_PORT:-6005}
|
||||||
|
- 9225:9229
|
||||||
networks:
|
networks:
|
||||||
v3-network:
|
v3-network:
|
||||||
aliases:
|
aliases:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mobicoop/matcher",
|
"name": "@mobicoop/matcher",
|
||||||
"version": "1.5.5",
|
"version": "1.6.0",
|
||||||
"description": "Mobicoop V3 Matcher",
|
"description": "Mobicoop V3 Matcher",
|
||||||
"author": "sbriat",
|
"author": "sbriat",
|
||||||
"private": true,
|
"private": true,
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||||
"start": "nest start",
|
"start": "nest start",
|
||||||
"start:dev": "nest start --watch",
|
"start:dev": "nest start --watch",
|
||||||
"start:debug": "nest start --debug --watch",
|
"start:debug": "nest start --debug 0.0.0.0:9229 --watch",
|
||||||
"start:prod": "node dist/main",
|
"start:prod": "node dist/main",
|
||||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||||
"lint:check": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix-dry-run --ignore-path .gitignore",
|
"lint:check": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix-dry-run --ignore-path .gitignore",
|
||||||
|
|||||||
24
src/log-cause.exception-filter.ts
Normal file
24
src/log-cause.exception-filter.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { ArgumentsHost, Catch, Logger } from '@nestjs/common';
|
||||||
|
import { BaseRpcExceptionFilter } from '@nestjs/microservices';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
@Catch()
|
||||||
|
export class LogCauseExceptionFilter extends BaseRpcExceptionFilter {
|
||||||
|
private static readonly causeLogger = new Logger('RpcExceptionsHandler');
|
||||||
|
|
||||||
|
catch(exception: any, host: ArgumentsHost): Observable<any> {
|
||||||
|
const response = super.catch(exception, host);
|
||||||
|
const cause = exception.cause;
|
||||||
|
if (cause) {
|
||||||
|
if (this.isError(cause)) {
|
||||||
|
LogCauseExceptionFilter.causeLogger.error(
|
||||||
|
'Caused by: ' + cause.message,
|
||||||
|
cause.stack,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
LogCauseExceptionFilter.causeLogger.error('Caused by: ' + cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -165,7 +165,7 @@ export class MatchQueryHandler implements IQueryHandler {
|
|||||||
frequency: query.frequency,
|
frequency: query.frequency,
|
||||||
fromDate: query.fromDate,
|
fromDate: query.fromDate,
|
||||||
toDate: query.toDate,
|
toDate: query.toDate,
|
||||||
schedule: query.schedule.map((scheduleItem: ScheduleItem) => ({
|
schedule: query.schedule?.map((scheduleItem: ScheduleItem) => ({
|
||||||
day: scheduleItem.day as number,
|
day: scheduleItem.day as number,
|
||||||
time: scheduleItem.time,
|
time: scheduleItem.time,
|
||||||
margin: scheduleItem.margin as number,
|
margin: scheduleItem.margin as number,
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
import { QueryBase } from '@mobicoop/ddd-library';
|
import { QueryBase } from '@mobicoop/ddd-library';
|
||||||
import { AlgorithmType } from '../../types/algorithm.types';
|
|
||||||
import { Waypoint } from '../../types/waypoint.type';
|
|
||||||
import { Frequency, Role } 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 {
|
import {
|
||||||
Path,
|
Path,
|
||||||
PathCreator,
|
PathCreator,
|
||||||
@@ -11,8 +7,12 @@ import {
|
|||||||
TypedRoute,
|
TypedRoute,
|
||||||
} from '@modules/ad/core/domain/path-creator.service';
|
} from '@modules/ad/core/domain/path-creator.service';
|
||||||
import { Point } from '@modules/ad/core/domain/value-objects/point.value-object';
|
import { Point } from '@modules/ad/core/domain/value-objects/point.value-object';
|
||||||
import { Route } from '../../types/route.type';
|
import { MatchRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/match.request.dto';
|
||||||
|
import { DateTimeTransformerPort } from '../../ports/datetime-transformer.port';
|
||||||
import { GeorouterPort } from '../../ports/georouter.port';
|
import { GeorouterPort } from '../../ports/georouter.port';
|
||||||
|
import { AlgorithmType } from '../../types/algorithm.types';
|
||||||
|
import { Route } from '../../types/route.type';
|
||||||
|
import { Waypoint } from '../../types/waypoint.type';
|
||||||
|
|
||||||
export class MatchQuery extends QueryBase {
|
export class MatchQuery extends QueryBase {
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -21,7 +21,7 @@ export class MatchQuery extends QueryBase {
|
|||||||
readonly frequency: Frequency;
|
readonly frequency: Frequency;
|
||||||
fromDate: string;
|
fromDate: string;
|
||||||
toDate: string;
|
toDate: string;
|
||||||
schedule: ScheduleItem[];
|
schedule?: ScheduleItem[];
|
||||||
seatsProposed?: number;
|
seatsProposed?: number;
|
||||||
seatsRequested?: number;
|
seatsRequested?: number;
|
||||||
strict?: boolean;
|
strict?: boolean;
|
||||||
@@ -73,7 +73,7 @@ export class MatchQuery extends QueryBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setMissingMarginDurations = (defaultMarginDuration: number): MatchQuery => {
|
setMissingMarginDurations = (defaultMarginDuration: number): MatchQuery => {
|
||||||
this.schedule.forEach((day: ScheduleItem) => {
|
this.schedule?.forEach((day: ScheduleItem) => {
|
||||||
if (day.margin === undefined) day.margin = defaultMarginDuration;
|
if (day.margin === undefined) day.margin = defaultMarginDuration;
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
@@ -136,6 +136,8 @@ export class MatchQuery extends QueryBase {
|
|||||||
setDatesAndSchedule = (
|
setDatesAndSchedule = (
|
||||||
datetimeTransformer: DateTimeTransformerPort,
|
datetimeTransformer: DateTimeTransformerPort,
|
||||||
): MatchQuery => {
|
): MatchQuery => {
|
||||||
|
// no transformation if schedule is not set
|
||||||
|
if (this.schedule === undefined) return this;
|
||||||
const initialFromDate: string = this.fromDate;
|
const initialFromDate: string = this.fromDate;
|
||||||
this.fromDate = datetimeTransformer.fromDate(
|
this.fromDate = datetimeTransformer.fromDate(
|
||||||
{
|
{
|
||||||
@@ -225,7 +227,9 @@ export class MatchQuery extends QueryBase {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
throw new Error('Unable to find a route for given waypoints');
|
throw new Error('Unable to find a route for given waypoints', {
|
||||||
|
cause: e,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ export class PassengerOrientedSelector extends Selector {
|
|||||||
query: this._createQueryString(Role.PASSENGER),
|
query: this._createQueryString(Role.PASSENGER),
|
||||||
role: Role.PASSENGER,
|
role: Role.PASSENGER,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
queryStringRoles.map<Promise<AdsRole>>(
|
queryStringRoles.map<Promise<AdsRole>>(
|
||||||
@@ -74,7 +73,7 @@ export class PassengerOrientedSelector extends Selector {
|
|||||||
driverSchedule:
|
driverSchedule:
|
||||||
adsRole.role == Role.PASSENGER
|
adsRole.role == Role.PASSENGER
|
||||||
? adEntity.getProps().schedule
|
? adEntity.getProps().schedule
|
||||||
: this.query.schedule.map((scheduleItem: ScheduleItem) => ({
|
: this.query.schedule?.map((scheduleItem: ScheduleItem) => ({
|
||||||
day: scheduleItem.day as number,
|
day: scheduleItem.day as number,
|
||||||
time: scheduleItem.time,
|
time: scheduleItem.time,
|
||||||
margin: scheduleItem.margin as number,
|
margin: scheduleItem.margin as number,
|
||||||
@@ -82,7 +81,7 @@ export class PassengerOrientedSelector extends Selector {
|
|||||||
passengerSchedule:
|
passengerSchedule:
|
||||||
adsRole.role == Role.DRIVER
|
adsRole.role == Role.DRIVER
|
||||||
? adEntity.getProps().schedule
|
? adEntity.getProps().schedule
|
||||||
: this.query.schedule.map((scheduleItem: ScheduleItem) => ({
|
: this.query.schedule?.map((scheduleItem: ScheduleItem) => ({
|
||||||
day: scheduleItem.day as number,
|
day: scheduleItem.day as number,
|
||||||
time: scheduleItem.time,
|
time: scheduleItem.time,
|
||||||
margin: scheduleItem.margin as number,
|
margin: scheduleItem.margin as number,
|
||||||
@@ -155,7 +154,9 @@ export class PassengerOrientedSelector extends Selector {
|
|||||||
: '';
|
: '';
|
||||||
|
|
||||||
private _whereDate = (): string =>
|
private _whereDate = (): string =>
|
||||||
`(\
|
this.query.frequency == Frequency.PUNCTUAL
|
||||||
|
? `("fromDate" <= '${this.query.fromDate}' AND "toDate" >= '${this.query.fromDate}')`
|
||||||
|
: `(\
|
||||||
(\
|
(\
|
||||||
"fromDate" <= '${this.query.fromDate}' AND "fromDate" <= '${this.query.toDate}' AND\
|
"fromDate" <= '${this.query.fromDate}' AND "fromDate" <= '${this.query.toDate}' AND\
|
||||||
"toDate" >= '${this.query.toDate}' AND "toDate" >= '${this.query.fromDate}'\
|
"toDate" >= '${this.query.toDate}' AND "toDate" >= '${this.query.fromDate}'\
|
||||||
@@ -172,6 +173,8 @@ export class PassengerOrientedSelector extends Selector {
|
|||||||
)`;
|
)`;
|
||||||
|
|
||||||
private _whereSchedule = (role: Role): string => {
|
private _whereSchedule = (role: Role): string => {
|
||||||
|
// no schedule filtering if schedule is not set
|
||||||
|
if (this.query.schedule === undefined) return '';
|
||||||
const schedule: string[] = [];
|
const schedule: string[] = [];
|
||||||
// we need full dates to compare times, because margins can lead to compare on previous or next day
|
// we need full dates to compare times, because margins can lead to compare on previous or next day
|
||||||
// - first we establish a base calendar (up to a week)
|
// - first we establish a base calendar (up to a week)
|
||||||
@@ -182,7 +185,7 @@ export class PassengerOrientedSelector extends Selector {
|
|||||||
// - then we compare each resulting day of the schedule with each day of calendar,
|
// - then we compare each resulting day of the schedule with each day of calendar,
|
||||||
// adding / removing margin depending on the role
|
// adding / removing margin depending on the role
|
||||||
scheduleDates.map((date: Date) => {
|
scheduleDates.map((date: Date) => {
|
||||||
this.query.schedule
|
(this.query.schedule as ScheduleItem[])
|
||||||
.filter(
|
.filter(
|
||||||
(scheduleItem: ScheduleItem) => date.getUTCDay() == scheduleItem.day,
|
(scheduleItem: ScheduleItem) => date.getUTCDay() == scheduleItem.day,
|
||||||
)
|
)
|
||||||
@@ -310,6 +313,11 @@ export class PassengerOrientedSelector extends Selector {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of dates containing all the dates (limited to 7 by default) between 2 boundary dates.
|
||||||
|
*
|
||||||
|
* The array length can be limited to a _max_ number of dates (default: 7)
|
||||||
|
*/
|
||||||
private _datesBetweenBoundaries = (
|
private _datesBetweenBoundaries = (
|
||||||
firstDate: string,
|
firstDate: string,
|
||||||
lastDate: string,
|
lastDate: string,
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
|
import {
|
||||||
|
AggregateRoot,
|
||||||
|
AggregateID,
|
||||||
|
ArgumentInvalidException,
|
||||||
|
} from '@mobicoop/ddd-library';
|
||||||
import {
|
import {
|
||||||
CandidateProps,
|
CandidateProps,
|
||||||
CreateCandidateProps,
|
CreateCandidateProps,
|
||||||
@@ -9,7 +13,10 @@ import {
|
|||||||
CarpoolPathItemProps,
|
CarpoolPathItemProps,
|
||||||
} from './value-objects/carpool-path-item.value-object';
|
} from './value-objects/carpool-path-item.value-object';
|
||||||
import { Step, StepProps } from './value-objects/step.value-object';
|
import { Step, StepProps } from './value-objects/step.value-object';
|
||||||
import { ScheduleItem } from './value-objects/schedule-item.value-object';
|
import {
|
||||||
|
ScheduleItem,
|
||||||
|
ScheduleItemProps,
|
||||||
|
} from './value-objects/schedule-item.value-object';
|
||||||
import { Journey } from './value-objects/journey.value-object';
|
import { Journey } from './value-objects/journey.value-object';
|
||||||
import { CalendarTools } from './calendar-tools.service';
|
import { CalendarTools } from './calendar-tools.service';
|
||||||
import { JourneyItem } from './value-objects/journey-item.value-object';
|
import { JourneyItem } from './value-objects/journey-item.value-object';
|
||||||
@@ -53,8 +60,11 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
|
|||||||
* This is a tedious process : additional information can be found in deeper methods !
|
* This is a tedious process : additional information can be found in deeper methods !
|
||||||
*/
|
*/
|
||||||
createJourneys = (): CandidateEntity => {
|
createJourneys = (): CandidateEntity => {
|
||||||
|
// driver and passenger schedules are eventually mandatory
|
||||||
|
if (!this.props.driverSchedule) this._createDriverSchedule();
|
||||||
|
if (!this.props.passengerSchedule) this._createPassengerSchedule();
|
||||||
try {
|
try {
|
||||||
this.props.journeys = this.props.driverSchedule
|
this.props.journeys = (this.props.driverSchedule as ScheduleItemProps[])
|
||||||
// first we create the journeys
|
// first we create the journeys
|
||||||
.map((driverScheduleItem: ScheduleItem) =>
|
.map((driverScheduleItem: ScheduleItem) =>
|
||||||
this._createJourney(driverScheduleItem),
|
this._createJourney(driverScheduleItem),
|
||||||
@@ -82,6 +92,116 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
|
|||||||
(1 + this.props.spacetimeDetourRatio.maxDistanceDetourRatio)
|
(1 + this.props.spacetimeDetourRatio.maxDistanceDetourRatio)
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the driver schedule based on the passenger schedule
|
||||||
|
*/
|
||||||
|
private _createDriverSchedule = (): void => {
|
||||||
|
let driverSchedule: ScheduleItemProps[] = this.props.passengerSchedule!.map(
|
||||||
|
(scheduleItemProps: ScheduleItemProps) => ({
|
||||||
|
day: scheduleItemProps.day,
|
||||||
|
time: scheduleItemProps.time,
|
||||||
|
margin: scheduleItemProps.margin,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// adjust the driver theoretical schedule :
|
||||||
|
// we guess the ideal driver departure time based on the duration to
|
||||||
|
// reach the passenger starting point from the driver starting point
|
||||||
|
driverSchedule = driverSchedule.map(
|
||||||
|
(scheduleItemProps: ScheduleItemProps) => {
|
||||||
|
const driverDate: Date = CalendarTools.firstDate(
|
||||||
|
scheduleItemProps.day,
|
||||||
|
this.props.dateInterval,
|
||||||
|
);
|
||||||
|
const driverStartDatetime: Date = CalendarTools.datetimeWithSeconds(
|
||||||
|
driverDate,
|
||||||
|
scheduleItemProps.time,
|
||||||
|
-this._passengerStartDuration(),
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
day: driverDate.getUTCDay(),
|
||||||
|
margin: scheduleItemProps.margin,
|
||||||
|
time: `${driverStartDatetime
|
||||||
|
.getUTCHours()
|
||||||
|
.toString()
|
||||||
|
.padStart(2, '0')}:${driverStartDatetime
|
||||||
|
.getUTCMinutes()
|
||||||
|
.toString()
|
||||||
|
.padStart(2, '0')}`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
this.props.driverSchedule = driverSchedule.map(
|
||||||
|
(scheduleItemProps: ScheduleItemProps) => ({
|
||||||
|
day: scheduleItemProps.day,
|
||||||
|
time: scheduleItemProps.time,
|
||||||
|
margin: scheduleItemProps.margin,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the duration to reach the passenger starting point from the driver starting point
|
||||||
|
*/
|
||||||
|
private _passengerStartDuration = (): number => {
|
||||||
|
let passengerStartStepIndex = 0;
|
||||||
|
this.props.carpoolPath?.forEach(
|
||||||
|
(carpoolPathItem: CarpoolPathItem, index: number) => {
|
||||||
|
carpoolPathItem.actors.forEach((actor: Actor) => {
|
||||||
|
if (actor.role == Role.PASSENGER && actor.target == Target.START)
|
||||||
|
passengerStartStepIndex = index;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return this.props.steps![passengerStartStepIndex].duration;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the passenger schedule based on the driver schedule
|
||||||
|
*/
|
||||||
|
private _createPassengerSchedule = (): void => {
|
||||||
|
let passengerSchedule: ScheduleItemProps[] = this.props.driverSchedule!.map(
|
||||||
|
(scheduleItemProps: ScheduleItemProps) => ({
|
||||||
|
day: scheduleItemProps.day,
|
||||||
|
time: scheduleItemProps.time,
|
||||||
|
margin: scheduleItemProps.margin,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
// adjust the passenger theoretical schedule :
|
||||||
|
// we guess the ideal passenger departure time based on the duration to
|
||||||
|
// reach the passenger starting point from the driver starting point
|
||||||
|
passengerSchedule = passengerSchedule.map(
|
||||||
|
(scheduleItemProps: ScheduleItemProps) => {
|
||||||
|
const passengerDate: Date = CalendarTools.firstDate(
|
||||||
|
scheduleItemProps.day,
|
||||||
|
this.props.dateInterval,
|
||||||
|
);
|
||||||
|
const passengeStartDatetime: Date = CalendarTools.datetimeWithSeconds(
|
||||||
|
passengerDate,
|
||||||
|
scheduleItemProps.time,
|
||||||
|
this._passengerStartDuration(),
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
day: passengerDate.getUTCDay(),
|
||||||
|
margin: scheduleItemProps.margin,
|
||||||
|
time: `${passengeStartDatetime
|
||||||
|
.getUTCHours()
|
||||||
|
.toString()
|
||||||
|
.padStart(2, '0')}:${passengeStartDatetime
|
||||||
|
.getUTCMinutes()
|
||||||
|
.toString()
|
||||||
|
.padStart(2, '0')}`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
this.props.passengerSchedule = passengerSchedule.map(
|
||||||
|
(scheduleItemProps: ScheduleItemProps) => ({
|
||||||
|
day: scheduleItemProps.day,
|
||||||
|
time: scheduleItemProps.time,
|
||||||
|
margin: scheduleItemProps.margin,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
private _createJourney = (driverScheduleItem: ScheduleItem): Journey =>
|
private _createJourney = (driverScheduleItem: ScheduleItem): Journey =>
|
||||||
new Journey({
|
new Journey({
|
||||||
firstDate: CalendarTools.firstDate(
|
firstDate: CalendarTools.firstDate(
|
||||||
@@ -216,7 +336,7 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
|
|||||||
* Find the passenger schedule item with the minimum duration between a given date and the dates of the passenger schedule
|
* Find the passenger schedule item with the minimum duration between a given date and the dates of the passenger schedule
|
||||||
*/
|
*/
|
||||||
private _minPassengerScheduleItemGapForDate = (date: Date): ScheduleItemGap =>
|
private _minPassengerScheduleItemGapForDate = (date: Date): ScheduleItemGap =>
|
||||||
this.props.passengerSchedule
|
(this.props.passengerSchedule as ScheduleItemProps[])
|
||||||
// first map the passenger schedule to "real" dates (we use unix epoch date as base)
|
// first map the passenger schedule to "real" dates (we use unix epoch date as base)
|
||||||
.map(
|
.map(
|
||||||
(scheduleItem: ScheduleItem) =>
|
(scheduleItem: ScheduleItem) =>
|
||||||
@@ -255,6 +375,10 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
|
|||||||
|
|
||||||
validate(): void {
|
validate(): void {
|
||||||
// entity business rules validation to protect it's invariant before saving entity to a database
|
// entity business rules validation to protect it's invariant before saving entity to a database
|
||||||
|
if (!this.props.driverSchedule && !this.props.passengerSchedule)
|
||||||
|
throw new ArgumentInvalidException(
|
||||||
|
'at least the driver or the passenger schedule is required',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ export interface CandidateProps {
|
|||||||
frequency: Frequency;
|
frequency: Frequency;
|
||||||
driverWaypoints: PointProps[];
|
driverWaypoints: PointProps[];
|
||||||
passengerWaypoints: PointProps[];
|
passengerWaypoints: PointProps[];
|
||||||
driverSchedule: ScheduleItemProps[];
|
driverSchedule?: ScheduleItemProps[];
|
||||||
passengerSchedule: ScheduleItemProps[];
|
passengerSchedule?: ScheduleItemProps[];
|
||||||
driverDistance: number;
|
driverDistance: number;
|
||||||
driverDuration: number;
|
driverDuration: number;
|
||||||
dateInterval: DateInterval;
|
dateInterval: DateInterval;
|
||||||
@@ -33,8 +33,8 @@ export interface CreateCandidateProps {
|
|||||||
driverDuration: number;
|
driverDuration: number;
|
||||||
driverWaypoints: PointProps[];
|
driverWaypoints: PointProps[];
|
||||||
passengerWaypoints: PointProps[];
|
passengerWaypoints: PointProps[];
|
||||||
driverSchedule: ScheduleItemProps[];
|
driverSchedule?: ScheduleItemProps[];
|
||||||
passengerSchedule: ScheduleItemProps[];
|
passengerSchedule?: ScheduleItemProps[];
|
||||||
spacetimeDetourRatio: SpacetimeDetourRatio;
|
spacetimeDetourRatio: SpacetimeDetourRatio;
|
||||||
dateInterval: DateInterval;
|
dateInterval: DateInterval;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ export class JourneyItem extends ValueObject<JourneyItemProps> {
|
|||||||
(actorTime: ActorTime) => actorTime.role == Role.DRIVER,
|
(actorTime: ActorTime) => actorTime.role == Role.DRIVER,
|
||||||
) as ActorTime
|
) as ActorTime
|
||||||
).firstDatetime;
|
).firstDatetime;
|
||||||
return `${driverTime.getHours().toString().padStart(2, '0')}:${driverTime
|
return `${driverTime.getUTCHours().toString().padStart(2, '0')}:${driverTime
|
||||||
.getMinutes()
|
.getUTCMinutes()
|
||||||
.toString()
|
.toString()
|
||||||
.padStart(2, '0')}`;
|
.padStart(2, '0')}`;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,6 +46,29 @@ export class Journey extends ValueObject<JourneyProps> {
|
|||||||
const driverActorTime = passengerDepartureJourneyItem.actorTimes.find(
|
const driverActorTime = passengerDepartureJourneyItem.actorTimes.find(
|
||||||
(actorTime: ActorTime) => actorTime.role == Role.DRIVER,
|
(actorTime: ActorTime) => actorTime.role == Role.DRIVER,
|
||||||
) as ActorTime;
|
) as ActorTime;
|
||||||
|
// TODO : check if the following conditions are even to the ones used in the return
|
||||||
|
// 4 possibilities to be valid :
|
||||||
|
// - 1 : the driver time boundaries are within the passenger time boundaries
|
||||||
|
// - 2 : the max driver time boundary is within the passenger time boundaries
|
||||||
|
// - 3 : the min driver time boundary is within the passenger time boundaries
|
||||||
|
// - 4 : the passenger time boundaries are within the driver time boundaries
|
||||||
|
// return (
|
||||||
|
// // 1
|
||||||
|
// (driverActorTime.firstMinDatetime >=
|
||||||
|
// passengerDepartureActorTime.firstMinDatetime &&
|
||||||
|
// driverActorTime.firstMaxDatetime <=
|
||||||
|
// passengerDepartureActorTime.firstMaxDatetime) ||
|
||||||
|
// // 2 & 4
|
||||||
|
// (driverActorTime.firstMinDatetime <=
|
||||||
|
// passengerDepartureActorTime.firstMinDatetime &&
|
||||||
|
// driverActorTime.firstMaxDatetime >=
|
||||||
|
// passengerDepartureActorTime.firstMinDatetime) ||
|
||||||
|
// // 3
|
||||||
|
// (driverActorTime.firstMinDatetime >=
|
||||||
|
// passengerDepartureActorTime.firstMinDatetime &&
|
||||||
|
// driverActorTime.firstMinDatetime <=
|
||||||
|
// passengerDepartureActorTime.firstMaxDatetime)
|
||||||
|
// );
|
||||||
return (
|
return (
|
||||||
(passengerDepartureActorTime.firstMinDatetime <=
|
(passengerDepartureActorTime.firstMinDatetime <=
|
||||||
driverActorTime.firstMaxDatetime &&
|
driverActorTime.firstMaxDatetime &&
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export interface MatchQueryProps {
|
|||||||
frequency: Frequency;
|
frequency: Frequency;
|
||||||
fromDate: string;
|
fromDate: string;
|
||||||
toDate: string;
|
toDate: string;
|
||||||
schedule: ScheduleItemProps[];
|
schedule?: ScheduleItemProps[];
|
||||||
seatsProposed: number;
|
seatsProposed: number;
|
||||||
seatsRequested: number;
|
seatsRequested: number;
|
||||||
strict: boolean;
|
strict: boolean;
|
||||||
@@ -50,7 +50,7 @@ export class MatchQuery extends ValueObject<MatchQueryProps> {
|
|||||||
return this.props.toDate;
|
return this.props.toDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
get schedule(): ScheduleItemProps[] {
|
get schedule(): ScheduleItemProps[] | undefined {
|
||||||
return this.props.schedule;
|
return this.props.schedule;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,8 +58,9 @@ export class MatchRequestDto {
|
|||||||
@Type(() => ScheduleItemDto)
|
@Type(() => ScheduleItemDto)
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@ArrayMinSize(1)
|
@ArrayMinSize(1)
|
||||||
|
@IsOptional()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
schedule: ScheduleItemDto[];
|
schedule?: ScheduleItemDto[];
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsInt()
|
@IsInt()
|
||||||
|
|||||||
@@ -1,18 +1,25 @@
|
|||||||
import { Controller, Inject, UseInterceptors, UsePipes } from '@nestjs/common';
|
|
||||||
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
|
||||||
import { RpcValidationPipe } from '@mobicoop/ddd-library';
|
import { RpcValidationPipe } from '@mobicoop/ddd-library';
|
||||||
import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
|
||||||
import { MatchingPaginatedResponseDto } from '../dtos/matching.paginated.response.dto';
|
|
||||||
import { QueryBus } from '@nestjs/cqrs';
|
|
||||||
import { MatchRequestDto } from './dtos/match.request.dto';
|
|
||||||
import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query';
|
|
||||||
import { MatchEntity } from '@modules/ad/core/domain/match.entity';
|
|
||||||
import { AD_ROUTE_PROVIDER } from '@modules/ad/ad.di-tokens';
|
import { AD_ROUTE_PROVIDER } from '@modules/ad/ad.di-tokens';
|
||||||
import { MatchMapper } from '@modules/ad/match.mapper';
|
|
||||||
import { MatchingResult } from '@modules/ad/core/application/queries/match/match.query-handler';
|
|
||||||
import { CacheInterceptor, CacheKey } from '@nestjs/cache-manager';
|
|
||||||
import { GeorouterPort } from '@modules/ad/core/application/ports/georouter.port';
|
import { GeorouterPort } from '@modules/ad/core/application/ports/georouter.port';
|
||||||
|
import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query';
|
||||||
|
import { MatchingResult } from '@modules/ad/core/application/queries/match/match.query-handler';
|
||||||
|
import { MatchEntity } from '@modules/ad/core/domain/match.entity';
|
||||||
|
import { MatchMapper } from '@modules/ad/match.mapper';
|
||||||
|
import { CacheInterceptor, CacheKey } from '@nestjs/cache-manager';
|
||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Inject,
|
||||||
|
UseFilters,
|
||||||
|
UseInterceptors,
|
||||||
|
UsePipes,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { QueryBus } from '@nestjs/cqrs';
|
||||||
|
import { GrpcMethod } from '@nestjs/microservices';
|
||||||
|
import { LogCauseExceptionFilter } from '@src/log-cause.exception-filter';
|
||||||
|
import { MatchingPaginatedResponseDto } from '../dtos/matching.paginated.response.dto';
|
||||||
|
import { MatchRequestDto } from './dtos/match.request.dto';
|
||||||
|
|
||||||
|
@UseFilters(LogCauseExceptionFilter)
|
||||||
@UsePipes(
|
@UsePipes(
|
||||||
new RpcValidationPipe({
|
new RpcValidationPipe({
|
||||||
whitelist: false,
|
whitelist: false,
|
||||||
@@ -32,7 +39,6 @@ export class MatchGrpcController {
|
|||||||
@UseInterceptors(CacheInterceptor)
|
@UseInterceptors(CacheInterceptor)
|
||||||
@GrpcMethod('MatcherService', 'Match')
|
@GrpcMethod('MatcherService', 'Match')
|
||||||
async match(data: MatchRequestDto): Promise<MatchingPaginatedResponseDto> {
|
async match(data: MatchRequestDto): Promise<MatchingPaginatedResponseDto> {
|
||||||
try {
|
|
||||||
const matchingResult: MatchingResult = await this.queryBus.execute(
|
const matchingResult: MatchingResult = await this.queryBus.execute(
|
||||||
new MatchQuery(data, this.routeProvider),
|
new MatchQuery(data, this.routeProvider),
|
||||||
);
|
);
|
||||||
@@ -45,11 +51,5 @@ export class MatchGrpcController {
|
|||||||
perPage: matchingResult.perPage,
|
perPage: matchingResult.perPage,
|
||||||
total: matchingResult.total,
|
total: matchingResult.total,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
|
||||||
throw new RpcException({
|
|
||||||
code: RpcExceptionCode.UNKNOWN,
|
|
||||||
message: e.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export class MatchingMapper
|
|||||||
toDate: entity.getProps().query.toDate,
|
toDate: entity.getProps().query.toDate,
|
||||||
schedule: entity
|
schedule: entity
|
||||||
.getProps()
|
.getProps()
|
||||||
.query.schedule.map((scheduleItem: ScheduleItem) => ({
|
.query.schedule?.map((scheduleItem: ScheduleItem) => ({
|
||||||
day: scheduleItem.day,
|
day: scheduleItem.day,
|
||||||
time: scheduleItem.time,
|
time: scheduleItem.time,
|
||||||
margin: scheduleItem.margin,
|
margin: scheduleItem.margin,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { ArgumentInvalidException } from '@mobicoop/ddd-library';
|
||||||
import { Frequency, Role } from '@modules/ad/core/domain/ad.types';
|
import { Frequency, Role } from '@modules/ad/core/domain/ad.types';
|
||||||
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
|
import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity';
|
||||||
import {
|
import {
|
||||||
@@ -6,7 +7,10 @@ import {
|
|||||||
} from '@modules/ad/core/domain/candidate.types';
|
} from '@modules/ad/core/domain/candidate.types';
|
||||||
import { Actor } from '@modules/ad/core/domain/value-objects/actor.value-object';
|
import { Actor } from '@modules/ad/core/domain/value-objects/actor.value-object';
|
||||||
import { CarpoolPathItemProps } from '@modules/ad/core/domain/value-objects/carpool-path-item.value-object';
|
import { CarpoolPathItemProps } from '@modules/ad/core/domain/value-objects/carpool-path-item.value-object';
|
||||||
import { Journey } from '@modules/ad/core/domain/value-objects/journey.value-object';
|
import {
|
||||||
|
Journey,
|
||||||
|
JourneyProps,
|
||||||
|
} from '@modules/ad/core/domain/value-objects/journey.value-object';
|
||||||
import { PointProps } from '@modules/ad/core/domain/value-objects/point.value-object';
|
import { PointProps } from '@modules/ad/core/domain/value-objects/point.value-object';
|
||||||
import { ScheduleItemProps } from '@modules/ad/core/domain/value-objects/schedule-item.value-object';
|
import { ScheduleItemProps } from '@modules/ad/core/domain/value-objects/schedule-item.value-object';
|
||||||
import { StepProps } from '@modules/ad/core/domain/value-objects/step.value-object';
|
import { StepProps } from '@modules/ad/core/domain/value-objects/step.value-object';
|
||||||
@@ -374,6 +378,95 @@ describe('Candidate entity', () => {
|
|||||||
.createJourneys();
|
.createJourneys();
|
||||||
expect(candidateEntity.getProps().journeys).toHaveLength(1);
|
expect(candidateEntity.getProps().journeys).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
it('should create journeys for a single date without driver schedule', () => {
|
||||||
|
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||||
|
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
||||||
|
role: Role.PASSENGER,
|
||||||
|
frequency: Frequency.PUNCTUAL,
|
||||||
|
dateInterval: {
|
||||||
|
lowerDate: '2023-08-28',
|
||||||
|
higherDate: '2023-08-28',
|
||||||
|
},
|
||||||
|
driverWaypoints: waypointsSet1,
|
||||||
|
passengerWaypoints: waypointsSet2,
|
||||||
|
driverDistance: 350145,
|
||||||
|
driverDuration: 13548,
|
||||||
|
driverSchedule: undefined,
|
||||||
|
passengerSchedule: schedule2,
|
||||||
|
spacetimeDetourRatio,
|
||||||
|
})
|
||||||
|
.setCarpoolPath(carpoolPath2)
|
||||||
|
.setSteps(steps)
|
||||||
|
.createJourneys();
|
||||||
|
expect(candidateEntity.getProps().journeys).toHaveLength(1);
|
||||||
|
// computed driver start time should be 06:49
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
candidateEntity.getProps().journeys as JourneyProps[]
|
||||||
|
)[0].journeyItems[0].actorTimes[0].firstDatetime.getUTCMinutes(),
|
||||||
|
).toBe(49);
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
candidateEntity.getProps().journeys as JourneyProps[]
|
||||||
|
)[0].journeyItems[0].actorTimes[0].firstDatetime.getUTCHours(),
|
||||||
|
).toBe(6);
|
||||||
|
});
|
||||||
|
it('should create journeys for a single date without passenger schedule', () => {
|
||||||
|
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||||
|
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
||||||
|
role: Role.PASSENGER,
|
||||||
|
frequency: Frequency.PUNCTUAL,
|
||||||
|
dateInterval: {
|
||||||
|
lowerDate: '2023-08-28',
|
||||||
|
higherDate: '2023-08-28',
|
||||||
|
},
|
||||||
|
driverWaypoints: waypointsSet1,
|
||||||
|
passengerWaypoints: waypointsSet2,
|
||||||
|
driverDistance: 350145,
|
||||||
|
driverDuration: 13548,
|
||||||
|
driverSchedule: schedule1,
|
||||||
|
passengerSchedule: undefined,
|
||||||
|
spacetimeDetourRatio,
|
||||||
|
})
|
||||||
|
.setCarpoolPath(carpoolPath2)
|
||||||
|
.setSteps(steps)
|
||||||
|
.createJourneys();
|
||||||
|
expect(candidateEntity.getProps().journeys).toHaveLength(1);
|
||||||
|
// computed passenger start time should be 07:20
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
candidateEntity.getProps().journeys as JourneyProps[]
|
||||||
|
)[0].journeyItems[1].actorTimes[0].firstDatetime.getUTCMinutes(),
|
||||||
|
).toBe(20);
|
||||||
|
expect(
|
||||||
|
(
|
||||||
|
candidateEntity.getProps().journeys as JourneyProps[]
|
||||||
|
)[0].journeyItems[1].actorTimes[0].firstDatetime.getUTCHours(),
|
||||||
|
).toBe(7);
|
||||||
|
});
|
||||||
|
it('should throw without driver and passenger schedule', () => {
|
||||||
|
expect(() =>
|
||||||
|
CandidateEntity.create({
|
||||||
|
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
||||||
|
role: Role.PASSENGER,
|
||||||
|
frequency: Frequency.PUNCTUAL,
|
||||||
|
dateInterval: {
|
||||||
|
lowerDate: '2023-08-28',
|
||||||
|
higherDate: '2023-08-28',
|
||||||
|
},
|
||||||
|
driverWaypoints: waypointsSet1,
|
||||||
|
passengerWaypoints: waypointsSet2,
|
||||||
|
driverDistance: 350145,
|
||||||
|
driverDuration: 13548,
|
||||||
|
driverSchedule: undefined,
|
||||||
|
passengerSchedule: undefined,
|
||||||
|
spacetimeDetourRatio,
|
||||||
|
})
|
||||||
|
.setCarpoolPath(carpoolPath2)
|
||||||
|
.setSteps(steps)
|
||||||
|
.createJourneys(),
|
||||||
|
).toThrow(ArgumentInvalidException);
|
||||||
|
});
|
||||||
it('should create journeys for multiple dates', () => {
|
it('should create journeys for multiple dates', () => {
|
||||||
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
const candidateEntity: CandidateEntity = CandidateEntity.create({
|
||||||
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
id: '5600ccfb-ab69-4d03-aa30-0fbe84fcedc0',
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ describe('Match Query value object', () => {
|
|||||||
expect(matchQueryVO.frequency).toBe(Frequency.PUNCTUAL);
|
expect(matchQueryVO.frequency).toBe(Frequency.PUNCTUAL);
|
||||||
expect(matchQueryVO.fromDate).toBe('2023-09-01');
|
expect(matchQueryVO.fromDate).toBe('2023-09-01');
|
||||||
expect(matchQueryVO.toDate).toBe('2023-09-01');
|
expect(matchQueryVO.toDate).toBe('2023-09-01');
|
||||||
expect(matchQueryVO.schedule.length).toBe(1);
|
expect(matchQueryVO.schedule?.length).toBe(1);
|
||||||
expect(matchQueryVO.seatsProposed).toBe(3);
|
expect(matchQueryVO.seatsProposed).toBe(3);
|
||||||
expect(matchQueryVO.seatsRequested).toBe(1);
|
expect(matchQueryVO.seatsRequested).toBe(1);
|
||||||
expect(matchQueryVO.strict).toBe(false);
|
expect(matchQueryVO.strict).toBe(false);
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.port';
|
import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.port';
|
||||||
import { GeorouterPort } from '@modules/ad/core/application/ports/georouter.port';
|
import { GeorouterPort } from '@modules/ad/core/application/ports/georouter.port';
|
||||||
import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query';
|
import {
|
||||||
|
MatchQuery,
|
||||||
|
ScheduleItem,
|
||||||
|
} from '@modules/ad/core/application/queries/match/match.query';
|
||||||
import { AlgorithmType } 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 { Waypoint } from '@modules/ad/core/application/types/waypoint.type';
|
||||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||||
@@ -135,9 +138,9 @@ describe('Match Query', () => {
|
|||||||
expect(matchQuery.maxDetourDurationRatio).toBe(0.3);
|
expect(matchQuery.maxDetourDurationRatio).toBe(0.3);
|
||||||
expect(matchQuery.fromDate).toBe('2023-08-27');
|
expect(matchQuery.fromDate).toBe('2023-08-27');
|
||||||
expect(matchQuery.toDate).toBe('2023-08-27');
|
expect(matchQuery.toDate).toBe('2023-08-27');
|
||||||
expect(matchQuery.schedule[0].day).toBe(0);
|
expect((matchQuery.schedule as ScheduleItem[])[0].day).toBe(0);
|
||||||
expect(matchQuery.schedule[0].time).toBe('23:05');
|
expect((matchQuery.schedule as ScheduleItem[])[0].time).toBe('23:05');
|
||||||
expect(matchQuery.schedule[0].margin).toBe(900);
|
expect((matchQuery.schedule as ScheduleItem[])[0].margin).toBe(900);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set good values for seats', async () => {
|
it('should set good values for seats', async () => {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
|
||||||
import { AD_ROUTE_PROVIDER } from '@modules/ad/ad.di-tokens';
|
import { AD_ROUTE_PROVIDER } from '@modules/ad/ad.di-tokens';
|
||||||
import { MatchingResult } from '@modules/ad/core/application/queries/match/match.query-handler';
|
import { MatchingResult } from '@modules/ad/core/application/queries/match/match.query-handler';
|
||||||
import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types';
|
import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types';
|
||||||
@@ -14,7 +13,6 @@ import { MatchGrpcController } from '@modules/ad/interface/grpc-controllers/matc
|
|||||||
import { MatchMapper } from '@modules/ad/match.mapper';
|
import { MatchMapper } from '@modules/ad/match.mapper';
|
||||||
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
||||||
import { QueryBus } from '@nestjs/cqrs';
|
import { QueryBus } from '@nestjs/cqrs';
|
||||||
import { RpcException } from '@nestjs/microservices';
|
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { bareMockGeorouter } from '../georouter.mock';
|
import { bareMockGeorouter } from '../georouter.mock';
|
||||||
|
|
||||||
@@ -56,9 +54,7 @@ const recurrentMatchRequestDto: MatchRequestDto = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const mockQueryBus = {
|
const mockQueryBus = {
|
||||||
execute: jest
|
execute: jest.fn().mockImplementationOnce(
|
||||||
.fn()
|
|
||||||
.mockImplementationOnce(
|
|
||||||
() =>
|
() =>
|
||||||
<MatchingResult>{
|
<MatchingResult>{
|
||||||
id: '43c83ae2-f4b0-4ac6-b8bf-8071801924d4',
|
id: '43c83ae2-f4b0-4ac6-b8bf-8071801924d4',
|
||||||
@@ -177,10 +173,7 @@ const mockQueryBus = {
|
|||||||
],
|
],
|
||||||
total: 1,
|
total: 1,
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
throw new Error();
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockMatchMapper = {
|
const mockMatchMapper = {
|
||||||
@@ -317,16 +310,4 @@ describe('Match Grpc Controller', () => {
|
|||||||
expect(matchingPaginatedResponseDto.perPage).toBe(10);
|
expect(matchingPaginatedResponseDto.perPage).toBe(10);
|
||||||
expect(mockQueryBus.execute).toHaveBeenCalledTimes(1);
|
expect(mockQueryBus.execute).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw a generic RpcException', async () => {
|
|
||||||
jest.spyOn(mockQueryBus, 'execute');
|
|
||||||
expect.assertions(3);
|
|
||||||
try {
|
|
||||||
await matchGrpcController.match(recurrentMatchRequestDto);
|
|
||||||
} catch (e: any) {
|
|
||||||
expect(e).toBeInstanceOf(RpcException);
|
|
||||||
expect(e.error.code).toBe(RpcExceptionCode.UNKNOWN);
|
|
||||||
}
|
|
||||||
expect(mockQueryBus.execute).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"target": "es2017",
|
"target": "es2017",
|
||||||
|
"lib": ["es2022"],
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
|
|||||||
Reference in New Issue
Block a user