save results to redis

This commit is contained in:
sbriat 2023-09-28 11:03:56 +02:00
parent 5c802df529
commit 09efe313ba
25 changed files with 950 additions and 176 deletions

View File

@ -15,14 +15,14 @@ MESSAGE_BROKER_EXCHANGE=mobicoop
REDIS_HOST=v3-redis REDIS_HOST=v3-redis
REDIS_PASSWORD=redis REDIS_PASSWORD=redis
REDIS_PORT=6379 REDIS_PORT=6379
REDIS_MATCHING_KEY=MATCHER:MATCHING
REDIS_MATCHING_TTL=900
# CACHE # CACHE
CACHE_TTL=5000 CACHE_TTL=5000
# DEFAULT CONFIGURATION # DEFAULT CONFIGURATION
# default identifier used for match requests
DEFAULT_UUID=00000000-0000-0000-0000-000000000000
# algorithm type # algorithm type
ALGORITHM=PASSENGER_ORIENTED ALGORITHM=PASSENGER_ORIENTED
# max distance in metres between driver # max distance in metres between driver

8
package-lock.json generated
View File

@ -13,7 +13,7 @@
"@grpc/proto-loader": "^0.7.6", "@grpc/proto-loader": "^0.7.6",
"@liaoliaots/nestjs-redis": "^9.0.5", "@liaoliaots/nestjs-redis": "^9.0.5",
"@mobicoop/configuration-module": "^1.2.0", "@mobicoop/configuration-module": "^1.2.0",
"@mobicoop/ddd-library": "^1.3.0", "@mobicoop/ddd-library": "^1.5.0",
"@mobicoop/health-module": "^2.0.0", "@mobicoop/health-module": "^2.0.0",
"@mobicoop/message-broker-module": "^1.2.0", "@mobicoop/message-broker-module": "^1.2.0",
"@nestjs/axios": "^2.0.0", "@nestjs/axios": "^2.0.0",
@ -1505,9 +1505,9 @@
} }
}, },
"node_modules/@mobicoop/ddd-library": { "node_modules/@mobicoop/ddd-library": {
"version": "1.3.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/@mobicoop/ddd-library/-/ddd-library-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@mobicoop/ddd-library/-/ddd-library-1.5.0.tgz",
"integrity": "sha512-WQTOIzGvsoh3o43Kukb9NIbJw18lsfSqu3k3cMZxc2mmgaYD7MtS4Yif/+KayQ6Ea4Ve3Hc6BVDls2X6svsoOg==", "integrity": "sha512-CX/V2+vSXrGtKobsyBfVpMW323ZT8tHrgUl1qrvU1XjRKNShvwsKyC7739x7CNgkJ9sr3XV+75JrOXEnqU83zw==",
"dependencies": { "dependencies": {
"@nestjs/event-emitter": "^1.4.2", "@nestjs/event-emitter": "^1.4.2",
"@nestjs/microservices": "^9.4.0", "@nestjs/microservices": "^9.4.0",

View File

@ -34,7 +34,7 @@
"@grpc/proto-loader": "^0.7.6", "@grpc/proto-loader": "^0.7.6",
"@liaoliaots/nestjs-redis": "^9.0.5", "@liaoliaots/nestjs-redis": "^9.0.5",
"@mobicoop/configuration-module": "^1.2.0", "@mobicoop/configuration-module": "^1.2.0",
"@mobicoop/ddd-library": "^1.3.0", "@mobicoop/ddd-library": "^1.5.0",
"@mobicoop/health-module": "^2.0.0", "@mobicoop/health-module": "^2.0.0",
"@mobicoop/message-broker-module": "^1.2.0", "@mobicoop/message-broker-module": "^1.2.0",
"@nestjs/axios": "^2.0.0", "@nestjs/axios": "^2.0.0",

View File

@ -1,4 +1,5 @@
export const AD_REPOSITORY = Symbol('AD_REPOSITORY'); export const AD_REPOSITORY = Symbol('AD_REPOSITORY');
export const MATCHING_REPOSITORY = Symbol('MATCHING_REPOSITORY');
export const AD_DIRECTION_ENCODER = Symbol('AD_DIRECTION_ENCODER'); export const AD_DIRECTION_ENCODER = Symbol('AD_DIRECTION_ENCODER');
export const AD_MESSAGE_PUBLISHER = Symbol('AD_MESSAGE_PUBLISHER'); export const AD_MESSAGE_PUBLISHER = Symbol('AD_MESSAGE_PUBLISHER');
export const AD_GET_BASIC_ROUTE_CONTROLLER = Symbol( export const AD_GET_BASIC_ROUTE_CONTROLLER = Symbol(

View File

@ -12,6 +12,7 @@ import {
INPUT_DATETIME_TRANSFORMER, INPUT_DATETIME_TRANSFORMER,
AD_GET_DETAILED_ROUTE_CONTROLLER, AD_GET_DETAILED_ROUTE_CONTROLLER,
OUTPUT_DATETIME_TRANSFORMER, OUTPUT_DATETIME_TRANSFORMER,
MATCHING_REPOSITORY,
} from './ad.di-tokens'; } from './ad.di-tokens';
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module'; import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
import { AdRepository } from './infrastructure/ad.repository'; import { AdRepository } from './infrastructure/ad.repository';
@ -32,6 +33,8 @@ import { InputDateTimeTransformer } from './infrastructure/input-datetime-transf
import { GetDetailedRouteController } from '@modules/geography/interface/controllers/get-detailed-route.controller'; import { GetDetailedRouteController } from '@modules/geography/interface/controllers/get-detailed-route.controller';
import { MatchMapper } from './match.mapper'; import { MatchMapper } from './match.mapper';
import { OutputDateTimeTransformer } from './infrastructure/output-datetime-transformer'; import { OutputDateTimeTransformer } from './infrastructure/output-datetime-transformer';
import { MatchingRepository } from './infrastructure/matching.repository';
import { MatchingMapper } from './matching.mapper';
const grpcControllers = [MatchGrpcController]; const grpcControllers = [MatchGrpcController];
@ -41,13 +44,17 @@ const commandHandlers: Provider[] = [CreateAdService];
const queryHandlers: Provider[] = [MatchQueryHandler]; const queryHandlers: Provider[] = [MatchQueryHandler];
const mappers: Provider[] = [AdMapper, MatchMapper]; const mappers: Provider[] = [AdMapper, MatchMapper, MatchingMapper];
const repositories: Provider[] = [ const repositories: Provider[] = [
{ {
provide: AD_REPOSITORY, provide: AD_REPOSITORY,
useClass: AdRepository, useClass: AdRepository,
}, },
{
provide: MATCHING_REPOSITORY,
useClass: MatchingRepository,
},
]; ];
const messagePublishers: Provider[] = [ const messagePublishers: Provider[] = [

View File

@ -0,0 +1,6 @@
import { MatchingEntity } from '../../domain/matching.entity';
export type MatchingRepositoryPort = {
get(id: string): Promise<MatchingEntity>;
save(matching: MatchingEntity): Promise<void>;
};

View File

@ -1,5 +1,5 @@
import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
import { MatchQuery } from './match.query'; import { MatchQuery, ScheduleItem } from './match.query';
import { Algorithm } from './algorithm.abstract'; import { Algorithm } from './algorithm.abstract';
import { PassengerOrientedAlgorithm } from './passenger-oriented-algorithm'; import { PassengerOrientedAlgorithm } from './passenger-oriented-algorithm';
import { AlgorithmType } from '../../types/algorithm.types'; import { AlgorithmType } from '../../types/algorithm.types';
@ -8,12 +8,16 @@ import { AdRepositoryPort } from '@modules/ad/core/application/ports/ad.reposito
import { import {
AD_REPOSITORY, AD_REPOSITORY,
INPUT_DATETIME_TRANSFORMER, INPUT_DATETIME_TRANSFORMER,
MATCHING_REPOSITORY,
PARAMS_PROVIDER, PARAMS_PROVIDER,
} from '@modules/ad/ad.di-tokens'; } from '@modules/ad/ad.di-tokens';
import { MatchEntity } from '@modules/ad/core/domain/match.entity'; import { MatchEntity } from '@modules/ad/core/domain/match.entity';
import { DefaultParamsProviderPort } from '../../ports/default-params-provider.port'; import { DefaultParamsProviderPort } from '../../ports/default-params-provider.port';
import { DefaultParams } from '../../ports/default-params.type'; import { DefaultParams } from '../../ports/default-params.type';
import { DateTimeTransformerPort } from '../../ports/datetime-transformer.port'; import { DateTimeTransformerPort } from '../../ports/datetime-transformer.port';
import { Paginator } from '@mobicoop/ddd-library';
import { MatchingEntity } from '@modules/ad/core/domain/matching.entity';
import { MatchingRepositoryPort } from '../../ports/matching.repository.port';
@QueryHandler(MatchQuery) @QueryHandler(MatchQuery)
export class MatchQueryHandler implements IQueryHandler { export class MatchQueryHandler implements IQueryHandler {
@ -22,14 +26,16 @@ export class MatchQueryHandler implements IQueryHandler {
constructor( constructor(
@Inject(PARAMS_PROVIDER) @Inject(PARAMS_PROVIDER)
private readonly defaultParamsProvider: DefaultParamsProviderPort, private readonly defaultParamsProvider: DefaultParamsProviderPort,
@Inject(AD_REPOSITORY) private readonly repository: AdRepositoryPort, @Inject(AD_REPOSITORY) private readonly adRepository: AdRepositoryPort,
@Inject(MATCHING_REPOSITORY)
private readonly matchingRepository: MatchingRepositoryPort,
@Inject(INPUT_DATETIME_TRANSFORMER) @Inject(INPUT_DATETIME_TRANSFORMER)
private readonly datetimeTransformer: DateTimeTransformerPort, private readonly datetimeTransformer: DateTimeTransformerPort,
) { ) {
this._defaultParams = defaultParamsProvider.getParams(); this._defaultParams = defaultParamsProvider.getParams();
} }
execute = async (query: MatchQuery): Promise<MatchEntity[]> => { execute = async (query: MatchQuery): Promise<MatchingResult> => {
query query
.setMissingMarginDurations(this._defaultParams.DEPARTURE_TIME_MARGIN) .setMissingMarginDurations(this._defaultParams.DEPARTURE_TIME_MARGIN)
.setMissingStrict(this._defaultParams.STRICT) .setMissingStrict(this._defaultParams.STRICT)
@ -60,8 +66,74 @@ export class MatchQueryHandler implements IQueryHandler {
switch (query.algorithmType) { switch (query.algorithmType) {
case AlgorithmType.PASSENGER_ORIENTED: case AlgorithmType.PASSENGER_ORIENTED:
default: default:
algorithm = new PassengerOrientedAlgorithm(query, this.repository); algorithm = new PassengerOrientedAlgorithm(query, this.adRepository);
} }
return algorithm.match();
const matches: MatchEntity[] = await algorithm.match();
const perPage: number = query.perPage as number;
const page: number = Paginator.pageNumber(
matches.length,
perPage,
query.page as number,
);
// create Matching Entity for persistence
const matchingEntity: MatchingEntity = MatchingEntity.create({
matches: matches.map((matchEntity: MatchEntity) => ({
adId: matchEntity.getProps().adId,
role: matchEntity.getProps().role,
frequency: matchEntity.getProps().frequency,
distance: matchEntity.getProps().distance,
duration: matchEntity.getProps().duration,
initialDistance: matchEntity.getProps().initialDistance,
initialDuration: matchEntity.getProps().initialDuration,
distanceDetour: matchEntity.getProps().distanceDetour,
durationDetour: matchEntity.getProps().durationDetour,
distanceDetourPercentage:
matchEntity.getProps().distanceDetourPercentage,
durationDetourPercentage:
matchEntity.getProps().durationDetourPercentage,
journeys: matchEntity.getProps().journeys,
})),
query: {
driver: query.driver as boolean,
passenger: query.passenger as boolean,
frequency: query.frequency,
fromDate: query.fromDate,
toDate: query.toDate,
schedule: query.schedule.map((scheduleItem: ScheduleItem) => ({
day: scheduleItem.day as number,
time: scheduleItem.time,
margin: scheduleItem.margin as number,
})),
seatsProposed: query.seatsProposed as number,
seatsRequested: query.seatsRequested as number,
strict: query.strict as boolean,
waypoints: query.waypoints,
algorithmType: query.algorithmType as AlgorithmType,
remoteness: query.remoteness as number,
useProportion: query.useProportion as boolean,
proportion: query.proportion as number,
useAzimuth: query.useAzimuth as boolean,
azimuthMargin: query.azimuthMargin as number,
maxDetourDistanceRatio: query.maxDetourDistanceRatio as number,
maxDetourDurationRatio: query.maxDetourDurationRatio as number,
},
});
await this.matchingRepository.save(matchingEntity);
return {
id: matchingEntity.id,
matches: Paginator.pageItems(matches, page, perPage),
total: matches.length,
page,
perPage,
};
}; };
} }
export type MatchingResult = {
id: string;
matches: MatchEntity[];
total: number;
page: number;
perPage: number;
};

View File

@ -0,0 +1,19 @@
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
import { v4 } from 'uuid';
import { CreateMatchingProps, MatchingProps } from './matching.types';
export class MatchingEntity extends AggregateRoot<MatchingProps> {
protected readonly _id: AggregateID;
static create = (create: CreateMatchingProps): MatchingEntity => {
const id = v4();
const props: MatchingProps = {
...create,
};
return new MatchingEntity({ id, props });
};
validate(): void {
// entity business rules validation to protect it's invariant before saving entity to a database
}
}

View File

@ -0,0 +1,11 @@
import { ExceptionBase } from '@mobicoop/ddd-library';
export class MatchingNotFoundException extends ExceptionBase {
static readonly message = 'Matching error';
public readonly code = 'MATCHER.MATCHING_NOT_FOUND';
constructor(cause?: Error, metadata?: unknown) {
super(MatchingNotFoundException.message, cause, metadata);
}
}

View File

@ -0,0 +1,14 @@
import { MatchProps } from './match.types';
import { MatchQueryProps } from './value-objects/match-query.value-object';
// All properties that a Matching has
export interface MatchingProps {
query: MatchQueryProps; // the query that induced the matches
matches: MatchProps[];
}
// Properties that are needed for a Matching creation
export interface CreateMatchingProps {
query: MatchQueryProps;
matches: MatchProps[];
}

View File

@ -0,0 +1,109 @@
import { ValueObject } from '@mobicoop/ddd-library';
import { Frequency } from '../ad.types';
import { ScheduleItemProps } from './schedule-item.value-object';
import { PointProps } from './point.value-object';
/** Note:
* Value Objects with multiple properties can contain
* other Value Objects inside if needed.
* */
export interface MatchQueryProps {
driver: boolean;
passenger: boolean;
frequency: Frequency;
fromDate: string;
toDate: string;
schedule: ScheduleItemProps[];
seatsProposed: number;
seatsRequested: number;
strict: boolean;
waypoints: PointProps[];
algorithmType: string;
remoteness: number;
useProportion: boolean;
proportion: number;
useAzimuth: boolean;
azimuthMargin: number;
maxDetourDistanceRatio: number;
maxDetourDurationRatio: number;
}
export class MatchQuery extends ValueObject<MatchQueryProps> {
get driver(): boolean {
return this.props.driver;
}
get passenger(): boolean {
return this.props.passenger;
}
get frequency(): Frequency {
return this.props.frequency;
}
get fromDate(): string {
return this.props.fromDate;
}
get toDate(): string {
return this.props.toDate;
}
get schedule(): ScheduleItemProps[] {
return this.props.schedule;
}
get seatsProposed(): number {
return this.props.seatsProposed;
}
get seatsRequested(): number {
return this.props.seatsRequested;
}
get strict(): boolean {
return this.props.strict;
}
get waypoints(): PointProps[] {
return this.props.waypoints;
}
get algorithmType(): string {
return this.props.algorithmType;
}
get remoteness(): number {
return this.props.remoteness;
}
get useProportion(): boolean {
return this.props.useProportion;
}
get proportion(): number {
return this.props.proportion;
}
get useAzimuth(): boolean {
return this.props.useAzimuth;
}
get azimuthMargin(): number {
return this.props.azimuthMargin;
}
get maxDetourDistanceRatio(): number {
return this.props.maxDetourDistanceRatio;
}
get maxDetourDurationRatio(): number {
return this.props.maxDetourDurationRatio;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
protected validate(props: MatchQueryProps): void {
return;
}
}

View File

@ -0,0 +1,46 @@
import { InjectRedis } from '@liaoliaots/nestjs-redis';
import { MatchingRepositoryPort } from '../core/application/ports/matching.repository.port';
import { MatchingEntity } from '../core/domain/matching.entity';
import { Redis } from 'ioredis';
import { MatchingMapper } from '../matching.mapper';
import { ConfigService } from '@nestjs/config';
import { MatchingNotFoundException } from '../core/domain/matching.errors';
const REDIS_MATCHING_TTL = 900;
const REDIS_MATCHING_KEY = 'MATCHER:MATCHING';
export class MatchingRepository implements MatchingRepositoryPort {
private _redisKey: string;
private _redisTtl: number;
constructor(
@InjectRedis() private readonly redis: Redis,
private readonly configService: ConfigService,
private readonly mapper: MatchingMapper,
) {
this._redisKey =
this.configService.get('REDIS_MATCHING_KEY') !== undefined
? (this.configService.get('REDIS_MATCHING_KEY') as string)
: REDIS_MATCHING_KEY;
this._redisTtl =
this.configService.get('REDIS_MATCHING_TTL') !== undefined
? (this.configService.get('REDIS_MATCHING_TTL') as number)
: REDIS_MATCHING_TTL;
}
get = async (matchingId: string): Promise<MatchingEntity> => {
const matching: string | null = await this.redis.get(
`${this._redisKey}:${matchingId}`,
);
if (matching) return this.mapper.toDomain(matching);
throw new MatchingNotFoundException(new Error('Matching not found'));
};
save = async (matching: MatchingEntity): Promise<void> => {
await this.redis.set(
`${this._redisKey}:${matching.id}`,
this.mapper.toPersistence(matching),
'EX',
this._redisTtl,
);
};
}

View File

@ -33,7 +33,7 @@ export class TimeConverter implements TimeConverterPort {
date: string, date: string,
time: string, time: string,
timezone: string, timezone: string,
dst?: boolean, dst = false,
): string => ): string =>
new DateTime(`${date}T${time}`, TimeZone.zone('UTC')) new DateTime(`${date}T${time}`, TimeZone.zone('UTC'))
.convert(TimeZone.zone(timezone, dst)) .convert(TimeZone.zone(timezone, dst))

View File

@ -0,0 +1,7 @@
import { PaginatedResponseDto } from '@mobicoop/ddd-library';
export abstract class IdPaginatedResponseDto<
T,
> extends PaginatedResponseDto<T> {
readonly id: string;
}

View File

@ -1,6 +0,0 @@
import { PaginatedResponseDto } from '@mobicoop/ddd-library';
import { MatchResponseDto } from './match.response.dto';
export class MatchPaginatedResponseDto extends PaginatedResponseDto<MatchResponseDto> {
readonly data: readonly MatchResponseDto[];
}

View File

@ -0,0 +1,11 @@
import { MatchResponseDto } from './match.response.dto';
import { IdPaginatedResponseDto } from './id-paginated.reponse.dto';
export class MatchingPaginatedResponseDto extends IdPaginatedResponseDto<MatchResponseDto> {
readonly id: string;
readonly data: readonly MatchResponseDto[];
constructor(props: IdPaginatedResponseDto<MatchResponseDto>) {
super(props);
this.id = props.id;
}
}

View File

@ -2,7 +2,7 @@ import { Controller, Inject, UsePipes } from '@nestjs/common';
import { GrpcMethod, RpcException } from '@nestjs/microservices'; 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 { RpcExceptionCode } from '@mobicoop/ddd-library';
import { MatchPaginatedResponseDto } from '../dtos/match.paginated.response.dto'; import { MatchingPaginatedResponseDto } from '../dtos/matching.paginated.response.dto';
import { QueryBus } from '@nestjs/cqrs'; import { QueryBus } from '@nestjs/cqrs';
import { MatchRequestDto } from './dtos/match.request.dto'; import { MatchRequestDto } from './dtos/match.request.dto';
import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query'; import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query';
@ -10,6 +10,7 @@ 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 { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port'; import { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port';
import { MatchMapper } from '@modules/ad/match.mapper'; import { MatchMapper } from '@modules/ad/match.mapper';
import { MatchingResult } from '@modules/ad/core/application/queries/match/match.query-handler';
@UsePipes( @UsePipes(
new RpcValidationPipe({ new RpcValidationPipe({
@ -27,18 +28,19 @@ export class MatchGrpcController {
) {} ) {}
@GrpcMethod('MatcherService', 'Match') @GrpcMethod('MatcherService', 'Match')
async match(data: MatchRequestDto): Promise<MatchPaginatedResponseDto> { async match(data: MatchRequestDto): Promise<MatchingPaginatedResponseDto> {
try { try {
const matches: MatchEntity[] = await this.queryBus.execute( const matchingResult: MatchingResult = await this.queryBus.execute(
new MatchQuery(data, this.routeProvider), new MatchQuery(data, this.routeProvider),
); );
return new MatchPaginatedResponseDto({ return new MatchingPaginatedResponseDto({
data: matches.map((match: MatchEntity) => id: matchingResult.id,
data: matchingResult.matches.map((match: MatchEntity) =>
this.matchMapper.toResponse(match), this.matchMapper.toResponse(match),
), ),
page: 1, page: matchingResult.page,
perPage: 5, perPage: matchingResult.perPage,
total: matches.length, total: matchingResult.total,
}); });
} catch (e) { } catch (e) {
throw new RpcException({ throw new RpcException({

View File

@ -24,6 +24,8 @@ message MatchRequest {
float maxDetourDistanceRatio = 15; float maxDetourDistanceRatio = 15;
float maxDetourDurationRatio = 16; float maxDetourDurationRatio = 16;
int32 identifier = 22; int32 identifier = 22;
optional int32 page = 23;
optional int32 perPage = 24;
} }
message ScheduleItem { message ScheduleItem {
@ -90,6 +92,9 @@ message Actor {
} }
message Matches { message Matches {
repeated Match data = 1; string id = 1;
int32 total = 2; repeated Match data = 2;
int32 total = 3;
int32 page = 4;
int32 perPage = 5;
} }

View File

@ -0,0 +1,13 @@
import { Injectable } from '@nestjs/common';
import { Mapper } from '@mobicoop/ddd-library';
import { MatchingEntity } from './core/domain/matching.entity';
@Injectable()
export class MatchingMapper
implements Mapper<MatchingEntity, string, string, undefined>
{
toPersistence = (entity: MatchingEntity): string => JSON.stringify(entity);
toDomain = (record: string): MatchingEntity =>
new MatchingEntity(JSON.parse(record));
}

View File

@ -0,0 +1,61 @@
import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types';
import { Frequency } from '@modules/ad/core/domain/ad.types';
import { MatchQuery } from '@modules/ad/core/domain/value-objects/match-query.value-object';
describe('Match Query value object', () => {
it('should create a match query value object', () => {
const matchQueryVO = new MatchQuery({
driver: false,
passenger: true,
frequency: Frequency.PUNCTUAL,
fromDate: '2023-09-01',
toDate: '2023-09-01',
schedule: [
{
day: 5,
time: '07:10',
margin: 900,
},
],
seatsProposed: 3,
seatsRequested: 1,
strict: false,
waypoints: [
{
lat: 48.689445,
lon: 6.17651,
},
{
lat: 48.21548,
lon: 5.65874,
},
],
algorithmType: AlgorithmType.PASSENGER_ORIENTED,
remoteness: 15000,
useProportion: true,
proportion: 0.3,
useAzimuth: true,
azimuthMargin: 10,
maxDetourDistanceRatio: 0.3,
maxDetourDurationRatio: 0.3,
});
expect(matchQueryVO.driver).toBe(false);
expect(matchQueryVO.passenger).toBe(true);
expect(matchQueryVO.frequency).toBe(Frequency.PUNCTUAL);
expect(matchQueryVO.fromDate).toBe('2023-09-01');
expect(matchQueryVO.toDate).toBe('2023-09-01');
expect(matchQueryVO.schedule.length).toBe(1);
expect(matchQueryVO.seatsProposed).toBe(3);
expect(matchQueryVO.seatsRequested).toBe(1);
expect(matchQueryVO.strict).toBe(false);
expect(matchQueryVO.waypoints.length).toBe(2);
expect(matchQueryVO.algorithmType).toBe(AlgorithmType.PASSENGER_ORIENTED);
expect(matchQueryVO.remoteness).toBe(15000);
expect(matchQueryVO.useProportion).toBe(true);
expect(matchQueryVO.proportion).toBe(0.3);
expect(matchQueryVO.useAzimuth).toBe(true);
expect(matchQueryVO.azimuthMargin).toBe(10);
expect(matchQueryVO.maxDetourDistanceRatio).toBe(0.3);
expect(matchQueryVO.maxDetourDurationRatio).toBe(0.3);
});
});

View File

@ -1,17 +1,22 @@
import { import {
AD_REPOSITORY, AD_REPOSITORY,
INPUT_DATETIME_TRANSFORMER, INPUT_DATETIME_TRANSFORMER,
MATCHING_REPOSITORY,
PARAMS_PROVIDER, PARAMS_PROVIDER,
} from '@modules/ad/ad.di-tokens'; } from '@modules/ad/ad.di-tokens';
import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.port'; import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.port';
import { DefaultParamsProviderPort } from '@modules/ad/core/application/ports/default-params-provider.port'; import { DefaultParamsProviderPort } from '@modules/ad/core/application/ports/default-params-provider.port';
import { MatchingRepositoryPort } from '@modules/ad/core/application/ports/matching.repository.port';
import { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port'; import { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port';
import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query'; import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query';
import { MatchQueryHandler } from '@modules/ad/core/application/queries/match/match.query-handler'; import {
MatchQueryHandler,
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';
import { Waypoint } from '@modules/ad/core/application/types/waypoint.type'; import { Waypoint } from '@modules/ad/core/application/types/waypoint.type';
import { Frequency, Role } from '@modules/ad/core/domain/ad.types'; import { Frequency, Role } from '@modules/ad/core/domain/ad.types';
import { MatchEntity } from '@modules/ad/core/domain/match.entity'; import { MatchingEntity } from '@modules/ad/core/domain/matching.entity';
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
const originWaypoint: Waypoint = { const originWaypoint: Waypoint = {
@ -51,9 +56,30 @@ const mockAdRepository = {
], ],
})), })),
}, },
{
id: '4431adea-2e10-4032-a743-01d537058914',
getProps: jest.fn().mockImplementation(() => ({
role: Role.DRIVER,
waypoints: [
{
lat: 48.698754,
lon: 6.159874,
},
{
lat: 48.969874,
lon: 2.449875,
},
],
})),
},
]), ]),
}; };
const mockMatchingRepository: MatchingRepositoryPort = {
get: jest.fn(),
save: jest.fn(),
};
const mockDefaultParamsProvider: DefaultParamsProviderPort = { const mockDefaultParamsProvider: DefaultParamsProviderPort = {
getParams: () => { getParams: () => {
return { return {
@ -107,6 +133,10 @@ describe('Match Query Handler', () => {
provide: AD_REPOSITORY, provide: AD_REPOSITORY,
useValue: mockAdRepository, useValue: mockAdRepository,
}, },
{
provide: MATCHING_REPOSITORY,
useValue: mockMatchingRepository,
},
{ {
provide: PARAMS_PROVIDER, provide: PARAMS_PROVIDER,
useValue: mockDefaultParamsProvider, useValue: mockDefaultParamsProvider,
@ -125,7 +155,8 @@ describe('Match Query Handler', () => {
expect(matchQueryHandler).toBeDefined(); expect(matchQueryHandler).toBeDefined();
}); });
it('should return a Match entity', async () => { it('should return a Matching', async () => {
jest.spyOn(MatchingEntity, 'create');
const matchQuery = new MatchQuery( const matchQuery = new MatchQuery(
{ {
algorithmType: AlgorithmType.PASSENGER_ORIENTED, algorithmType: AlgorithmType.PASSENGER_ORIENTED,
@ -146,7 +177,10 @@ describe('Match Query Handler', () => {
}, },
mockRouteProvider, mockRouteProvider,
); );
const matches: MatchEntity[] = await matchQueryHandler.execute(matchQuery); const matching: MatchingResult = await matchQueryHandler.execute(
expect(matches.length).toBeGreaterThanOrEqual(0); matchQuery,
);
expect(matching.id).toHaveLength(36);
expect(MatchingEntity.create).toHaveBeenCalledTimes(1);
}); });
}); });

File diff suppressed because one or more lines are too long

View File

@ -52,7 +52,7 @@ describe('Time Converter', () => {
}); });
describe('localStringDateTimeToUtcDate', () => { describe('localStringDateTimeToUtcDate', () => {
it('should convert a summer paris date and time to a utc date', () => { it('should convert a summer paris date and time to a utc date with dst', () => {
const timeConverter: TimeConverter = new TimeConverter(); const timeConverter: TimeConverter = new TimeConverter();
const parisDate = '2023-06-22'; const parisDate = '2023-06-22';
const parisTime = '12:00'; const parisTime = '12:00';
@ -64,7 +64,7 @@ describe('Time Converter', () => {
); );
expect(utcDate.toISOString()).toBe('2023-06-22T10:00:00.000Z'); expect(utcDate.toISOString()).toBe('2023-06-22T10:00:00.000Z');
}); });
it('should convert a winter paris date and time to a utc date', () => { it('should convert a winter paris date and time to a utc date with dst', () => {
const timeConverter: TimeConverter = new TimeConverter(); const timeConverter: TimeConverter = new TimeConverter();
const parisDate = '2023-02-02'; const parisDate = '2023-02-02';
const parisTime = '12:00'; const parisTime = '12:00';
@ -72,6 +72,7 @@ describe('Time Converter', () => {
parisDate, parisDate,
parisTime, parisTime,
'Europe/Paris', 'Europe/Paris',
true,
); );
expect(utcDate.toISOString()).toBe('2023-02-02T11:00:00.000Z'); expect(utcDate.toISOString()).toBe('2023-02-02T11:00:00.000Z');
}); });
@ -83,7 +84,6 @@ describe('Time Converter', () => {
parisDate, parisDate,
parisTime, parisTime,
'Europe/Paris', 'Europe/Paris',
false,
); );
expect(utcDate.toISOString()).toBe('2023-06-22T11:00:00.000Z'); expect(utcDate.toISOString()).toBe('2023-06-22T11:00:00.000Z');
}); });
@ -148,6 +148,30 @@ describe('Time Converter', () => {
}); });
describe('utcStringDateTimeToLocalIsoString', () => { describe('utcStringDateTimeToLocalIsoString', () => {
it('should convert a utc string date and time to a summer paris date isostring with dst', () => {
const timeConverter: TimeConverter = new TimeConverter();
const utcDate = '2023-06-22';
const utcTime = '10:00';
const localIsoString = timeConverter.utcStringDateTimeToLocalIsoString(
utcDate,
utcTime,
'Europe/Paris',
true,
);
expect(localIsoString).toBe('2023-06-22T12:00:00.000+02:00');
});
it('should convert a utc string date and time to a winter paris date isostring with dst', () => {
const timeConverter: TimeConverter = new TimeConverter();
const utcDate = '2023-02-02';
const utcTime = '10:00';
const localIsoString = timeConverter.utcStringDateTimeToLocalIsoString(
utcDate,
utcTime,
'Europe/Paris',
true,
);
expect(localIsoString).toBe('2023-02-02T11:00:00.000+01:00');
});
it('should convert a utc string date and time to a summer paris date isostring', () => { it('should convert a utc string date and time to a summer paris date isostring', () => {
const timeConverter: TimeConverter = new TimeConverter(); const timeConverter: TimeConverter = new TimeConverter();
const utcDate = '2023-06-22'; const utcDate = '2023-06-22';
@ -157,29 +181,6 @@ describe('Time Converter', () => {
utcTime, utcTime,
'Europe/Paris', 'Europe/Paris',
); );
expect(localIsoString).toBe('2023-06-22T12:00:00.000+02:00');
});
it('should convert a utc string date and time to a winter paris date isostring', () => {
const timeConverter: TimeConverter = new TimeConverter();
const utcDate = '2023-02-02';
const utcTime = '10:00';
const localIsoString = timeConverter.utcStringDateTimeToLocalIsoString(
utcDate,
utcTime,
'Europe/Paris',
);
expect(localIsoString).toBe('2023-02-02T11:00:00.000+01:00');
});
it('should convert a utc string date and time to a summer paris date isostring without dst', () => {
const timeConverter: TimeConverter = new TimeConverter();
const utcDate = '2023-06-22';
const utcTime = '10:00';
const localIsoString = timeConverter.utcStringDateTimeToLocalIsoString(
utcDate,
utcTime,
'Europe/Paris',
false,
);
expect(localIsoString).toBe('2023-06-22T11:00:00.000+01:00'); expect(localIsoString).toBe('2023-06-22T11:00:00.000+01:00');
}); });
it('should convert a utc date to a tonga date isostring', () => { it('should convert a utc date to a tonga date isostring', () => {

View File

@ -1,12 +1,14 @@
import { RpcExceptionCode } from '@mobicoop/ddd-library'; 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 { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port'; import { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port';
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';
import { Frequency, Role } from '@modules/ad/core/domain/ad.types'; import { Frequency, Role } from '@modules/ad/core/domain/ad.types';
import { Target } from '@modules/ad/core/domain/candidate.types'; import { Target } from '@modules/ad/core/domain/candidate.types';
import { MatchEntity } from '@modules/ad/core/domain/match.entity'; import { MatchEntity } from '@modules/ad/core/domain/match.entity';
import { ActorTime } from '@modules/ad/core/domain/value-objects/actor-time.value-object'; import { ActorTime } from '@modules/ad/core/domain/value-objects/actor-time.value-object';
import { JourneyItem } from '@modules/ad/core/domain/value-objects/journey-item.value-object'; import { JourneyItem } from '@modules/ad/core/domain/value-objects/journey-item.value-object';
import { MatchingPaginatedResponseDto } from '@modules/ad/interface/dtos/matching.paginated.response.dto';
import { MatchRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/match.request.dto'; import { MatchRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/match.request.dto';
import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto'; import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto';
import { MatchGrpcController } from '@modules/ad/interface/grpc-controllers/match.grpc-controller'; import { MatchGrpcController } from '@modules/ad/interface/grpc-controllers/match.grpc-controller';
@ -55,117 +57,126 @@ const recurrentMatchRequestDto: MatchRequestDto = {
const mockQueryBus = { const mockQueryBus = {
execute: jest execute: jest
.fn() .fn()
.mockImplementationOnce(() => [ .mockImplementationOnce(
MatchEntity.create({ () =>
adId: '53a0bf71-4132-4f7b-a4cc-88c796b6bdf1', <MatchingResult>{
role: Role.DRIVER, id: '43c83ae2-f4b0-4ac6-b8bf-8071801924d4',
frequency: Frequency.RECURRENT, page: 1,
distance: 356041, perPage: 10,
duration: 12647, matches: [
initialDistance: 349251, MatchEntity.create({
initialDuration: 12103, adId: '53a0bf71-4132-4f7b-a4cc-88c796b6bdf1',
journeys: [ role: Role.DRIVER,
{ frequency: Frequency.RECURRENT,
firstDate: new Date('2023-09-01'), distance: 356041,
lastDate: new Date('2024-08-30'), duration: 12647,
journeyItems: [ initialDistance: 349251,
new JourneyItem({ initialDuration: 12103,
lat: 48.689445, journeys: [
lon: 6.17651, {
duration: 0, firstDate: new Date('2023-09-01'),
distance: 0, lastDate: new Date('2024-08-30'),
actorTimes: [ journeyItems: [
new ActorTime({ new JourneyItem({
role: Role.DRIVER, lat: 48.689445,
target: Target.START, lon: 6.17651,
firstDatetime: new Date('2023-09-01 07:00'), duration: 0,
firstMinDatetime: new Date('2023-09-01 06:45'), distance: 0,
firstMaxDatetime: new Date('2023-09-01 07:15'), actorTimes: [
lastDatetime: new Date('2024-08-30 07:00'), new ActorTime({
lastMinDatetime: new Date('2024-08-30 06:45'), role: Role.DRIVER,
lastMaxDatetime: new Date('2024-08-30 07:15'), target: Target.START,
}), firstDatetime: new Date('2023-09-01 07:00'),
], firstMinDatetime: new Date('2023-09-01 06:45'),
}), firstMaxDatetime: new Date('2023-09-01 07:15'),
new JourneyItem({ lastDatetime: new Date('2024-08-30 07:00'),
lat: 48.369445, lastMinDatetime: new Date('2024-08-30 06:45'),
lon: 6.67487, lastMaxDatetime: new Date('2024-08-30 07:15'),
duration: 2100, }),
distance: 56878, ],
actorTimes: [ }),
new ActorTime({ new JourneyItem({
role: Role.DRIVER, lat: 48.369445,
target: Target.NEUTRAL, lon: 6.67487,
firstDatetime: new Date('2023-09-01 07:35'), duration: 2100,
firstMinDatetime: new Date('2023-09-01 07:20'), distance: 56878,
firstMaxDatetime: new Date('2023-09-01 07:50'), actorTimes: [
lastDatetime: new Date('2024-08-30 07:35'), new ActorTime({
lastMinDatetime: new Date('2024-08-30 07:20'), role: Role.DRIVER,
lastMaxDatetime: new Date('2024-08-30 07:50'), target: Target.NEUTRAL,
}), firstDatetime: new Date('2023-09-01 07:35'),
new ActorTime({ firstMinDatetime: new Date('2023-09-01 07:20'),
role: Role.PASSENGER, firstMaxDatetime: new Date('2023-09-01 07:50'),
target: Target.START, lastDatetime: new Date('2024-08-30 07:35'),
firstDatetime: new Date('2023-09-01 07:32'), lastMinDatetime: new Date('2024-08-30 07:20'),
firstMinDatetime: new Date('2023-09-01 07:17'), lastMaxDatetime: new Date('2024-08-30 07:50'),
firstMaxDatetime: new Date('2023-09-01 07:47'), }),
lastDatetime: new Date('2024-08-30 07:32'), new ActorTime({
lastMinDatetime: new Date('2024-08-30 07:17'), role: Role.PASSENGER,
lastMaxDatetime: new Date('2024-08-30 07:47'), target: Target.START,
}), firstDatetime: new Date('2023-09-01 07:32'),
], firstMinDatetime: new Date('2023-09-01 07:17'),
}), firstMaxDatetime: new Date('2023-09-01 07:47'),
new JourneyItem({ lastDatetime: new Date('2024-08-30 07:32'),
lat: 47.98487, lastMinDatetime: new Date('2024-08-30 07:17'),
lon: 6.9427, lastMaxDatetime: new Date('2024-08-30 07:47'),
duration: 3840, }),
distance: 76491, ],
actorTimes: [ }),
new ActorTime({ new JourneyItem({
role: Role.DRIVER, lat: 47.98487,
target: Target.NEUTRAL, lon: 6.9427,
firstDatetime: new Date('2023-09-01 08:04'), duration: 3840,
firstMinDatetime: new Date('2023-09-01 07:51'), distance: 76491,
firstMaxDatetime: new Date('2023-09-01 08:19'), actorTimes: [
lastDatetime: new Date('2024-08-30 08:04'), new ActorTime({
lastMinDatetime: new Date('2024-08-30 07:51'), role: Role.DRIVER,
lastMaxDatetime: new Date('2024-08-30 08:19'), target: Target.NEUTRAL,
}), firstDatetime: new Date('2023-09-01 08:04'),
new ActorTime({ firstMinDatetime: new Date('2023-09-01 07:51'),
role: Role.PASSENGER, firstMaxDatetime: new Date('2023-09-01 08:19'),
target: Target.FINISH, lastDatetime: new Date('2024-08-30 08:04'),
firstDatetime: new Date('2023-09-01 08:01'), lastMinDatetime: new Date('2024-08-30 07:51'),
firstMinDatetime: new Date('2023-09-01 07:46'), lastMaxDatetime: new Date('2024-08-30 08:19'),
firstMaxDatetime: new Date('2023-09-01 08:16'), }),
lastDatetime: new Date('2024-08-30 08:01'), new ActorTime({
lastMinDatetime: new Date('2024-08-30 07:46'), role: Role.PASSENGER,
lastMaxDatetime: new Date('2024-08-30 08:16'), target: Target.FINISH,
}), firstDatetime: new Date('2023-09-01 08:01'),
], firstMinDatetime: new Date('2023-09-01 07:46'),
}), firstMaxDatetime: new Date('2023-09-01 08:16'),
new JourneyItem({ lastDatetime: new Date('2024-08-30 08:01'),
lat: 47.365987, lastMinDatetime: new Date('2024-08-30 07:46'),
lon: 7.02154, lastMaxDatetime: new Date('2024-08-30 08:16'),
duration: 4980, }),
distance: 96475, ],
actorTimes: [ }),
new ActorTime({ new JourneyItem({
role: Role.DRIVER, lat: 47.365987,
target: Target.FINISH, lon: 7.02154,
firstDatetime: new Date('2023-09-01 08:23'), duration: 4980,
firstMinDatetime: new Date('2023-09-01 08:08'), distance: 96475,
firstMaxDatetime: new Date('2023-09-01 08:38'), actorTimes: [
lastDatetime: new Date('2024-08-30 08:23'), new ActorTime({
lastMinDatetime: new Date('2024-08-30 08:08'), role: Role.DRIVER,
lastMaxDatetime: new Date('2024-08-30 08:38'), target: Target.FINISH,
}), firstDatetime: new Date('2023-09-01 08:23'),
], firstMinDatetime: new Date('2023-09-01 08:08'),
}), firstMaxDatetime: new Date('2023-09-01 08:38'),
], lastDatetime: new Date('2024-08-30 08:23'),
}, lastMinDatetime: new Date('2024-08-30 08:08'),
], lastMaxDatetime: new Date('2024-08-30 08:38'),
}), }),
]) ],
}),
],
},
],
}),
],
total: 1,
},
)
.mockImplementationOnce(() => { .mockImplementationOnce(() => {
throw new Error(); throw new Error();
}), }),
@ -319,12 +330,16 @@ describe('Match Grpc Controller', () => {
expect(matchGrpcController).toBeDefined(); expect(matchGrpcController).toBeDefined();
}); });
it('should return matches', async () => { it('should return a matching', async () => {
jest.spyOn(mockQueryBus, 'execute'); jest.spyOn(mockQueryBus, 'execute');
const matchPaginatedResponseDto = await matchGrpcController.match( const matchingPaginatedResponseDto: MatchingPaginatedResponseDto =
recurrentMatchRequestDto, await matchGrpcController.match(recurrentMatchRequestDto);
expect(matchingPaginatedResponseDto.id).toBe(
'43c83ae2-f4b0-4ac6-b8bf-8071801924d4',
); );
expect(matchPaginatedResponseDto.data).toHaveLength(1); expect(matchingPaginatedResponseDto.data).toHaveLength(1);
expect(matchingPaginatedResponseDto.page).toBe(1);
expect(matchingPaginatedResponseDto.perPage).toBe(10);
expect(mockQueryBus.execute).toHaveBeenCalledTimes(1); expect(mockQueryBus.execute).toHaveBeenCalledTimes(1);
}); });

View File

@ -0,0 +1,118 @@
import { Frequency, Role } from '@modules/ad/core/domain/ad.types';
import { Target } from '@modules/ad/core/domain/candidate.types';
import { MatchingEntity } from '@modules/ad/core/domain/matching.entity';
import { MatchingMapper } from '@modules/ad/matching.mapper';
import { Test } from '@nestjs/testing';
describe('Matching Mapper', () => {
let matchingMapper: MatchingMapper;
beforeAll(async () => {
const module = await Test.createTestingModule({
providers: [MatchingMapper],
}).compile();
matchingMapper = module.get<MatchingMapper>(MatchingMapper);
});
it('should be defined', () => {
expect(matchingMapper).toBeDefined();
});
it('should map domain entity to persistence', async () => {
const matchingEntity: MatchingEntity = new MatchingEntity({
id: '644a7cb3-6436-4db5-850d-b4c7421d4b97',
createdAt: new Date('2023-08-20T09:48:00Z'),
updatedAt: new Date('2023-08-20T09:48:00Z'),
props: {
matches: [
{
adId: 'dd937edf-1264-4868-b073-d1952abe30b1',
role: Role.DRIVER,
frequency: Frequency.PUNCTUAL,
distance: 356041,
duration: 12647,
initialDistance: 348745,
initialDuration: 12105,
distanceDetour: 7296,
durationDetour: 542,
distanceDetourPercentage: 4.1,
durationDetourPercentage: 3.8,
journeys: [
{
firstDate: new Date('2023-09-01'),
lastDate: new Date('2023-09-01'),
journeyItems: [
{
lon: 6.35484,
lat: 48.26587,
duration: 0,
distance: 0,
actorTimes: [
{
role: Role.DRIVER,
target: Target.START,
firstDatetime: new Date('2023-09-01T07:00:00Z'),
firstMinDatetime: new Date('2023-09-01T06:45:00Z'),
firstMaxDatetime: new Date('2023-09-01T07:15:00Z'),
lastDatetime: new Date('2023-09-01T07:00:00Z'),
lastMinDatetime: new Date('2023-09-01T06:45:00Z'),
lastMaxDatetime: new Date('2023-09-01T07:15:00Z'),
},
],
},
],
},
],
// ...
},
],
query: {
driver: false,
passenger: true,
frequency: Frequency.PUNCTUAL,
fromDate: '2023-09-01',
toDate: '2023-09-01',
schedule: [
{
day: 5,
time: '06:40',
margin: 900,
},
],
seatsProposed: 3,
seatsRequested: 1,
strict: true,
waypoints: [
{
lon: 6.389745,
lat: 48.32644,
},
{
lon: 6.984567,
lat: 48.021548,
},
],
algorithmType: 'PASSENGER_ORIENTED',
remoteness: 15000,
useProportion: true,
proportion: 0.3,
useAzimuth: true,
azimuthMargin: 10,
maxDetourDistanceRatio: 0.3,
maxDetourDurationRatio: 0.3,
},
},
});
const mapped: string = matchingMapper.toPersistence(matchingEntity);
expect(mapped).toBe(
'{"_id":"644a7cb3-6436-4db5-850d-b4c7421d4b97","_createdAt":"2023-08-20T09:48:00.000Z","_updatedAt":"2023-08-20T09:48:00.000Z","props":{"matches":[{"adId":"dd937edf-1264-4868-b073-d1952abe30b1","role":"DRIVER","frequency":"PUNCTUAL","distance":356041,"duration":12647,"initialDistance":348745,"initialDuration":12105,"distanceDetour":7296,"durationDetour":542,"distanceDetourPercentage":4.1,"durationDetourPercentage":3.8,"journeys":[{"firstDate":"2023-09-01T00:00:00.000Z","lastDate":"2023-09-01T00:00:00.000Z","journeyItems":[{"lon":6.35484,"lat":48.26587,"duration":0,"distance":0,"actorTimes":[{"role":"DRIVER","target":"START","firstDatetime":"2023-09-01T07:00:00.000Z","firstMinDatetime":"2023-09-01T06:45:00.000Z","firstMaxDatetime":"2023-09-01T07:15:00.000Z","lastDatetime":"2023-09-01T07:00:00.000Z","lastMinDatetime":"2023-09-01T06:45:00.000Z","lastMaxDatetime":"2023-09-01T07:15:00.000Z"}]}]}]}],"query":{"driver":false,"passenger":true,"frequency":"PUNCTUAL","fromDate":"2023-09-01","toDate":"2023-09-01","schedule":[{"day":5,"time":"06:40","margin":900}],"seatsProposed":3,"seatsRequested":1,"strict":true,"waypoints":[{"lon":6.389745,"lat":48.32644},{"lon":6.984567,"lat":48.021548}],"algorithmType":"PASSENGER_ORIENTED","remoteness":15000,"useProportion":true,"proportion":0.3,"useAzimuth":true,"azimuthMargin":10,"maxDetourDistanceRatio":0.3,"maxDetourDurationRatio":0.3}},"_domainEvents":[]}',
);
});
it('should map persisted string to domain entity', async () => {
const matchingEntity: MatchingEntity = matchingMapper.toDomain(
'{"_id":"644a7cb3-6436-4db5-850d-b4c7421d4b97","_createdAt":"2023-08-20T09:48:00.000Z","_updatedAt":"2023-08-20T09:48:00.000Z","props":{"matches":[{"adId":"dd937edf-1264-4868-b073-d1952abe30b1","role":"DRIVER","frequency":"PUNCTUAL","distance":356041,"duration":12647,"initialDistance":348745,"initialDuration":12105,"distanceDetour":7296,"durationDetour":542,"distanceDetourPercentage":4.1,"durationDetourPercentage":3.8,"journeys":[{"firstDate":"2023-09-01T00:00:00.000Z","lastDate":"2023-09-01T00:00:00.000Z","journeyItems":[{"lon":6.35484,"lat":48.26587,"duration":0,"distance":0,"actorTimes":[{"role":"DRIVER","target":"START","firstDatetime":"2023-09-01T07:00:00.000Z","firstMinDatetime":"2023-09-01T06:45:00.000Z","firstMaxDatetime":"2023-09-01T07:15:00.000Z","lastDatetime":"2023-09-01T07:00:00.000Z","lastMinDatetime":"2023-09-01T06:45:00.000Z","lastMaxDatetime":"2023-09-01T07:15:00.000Z"}]}]}]}],"query":{"driver":false,"passenger":true,"frequency":"PUNCTUAL","fromDate":"2023-09-01","toDate":"2023-09-01","schedule":[{"day":5,"time":"06:40","margin":900}],"seatsProposed":3,"seatsRequested":1,"strict":true,"waypoints":[{"lon":6.389745,"lat":48.32644},{"lon":6.984567,"lat":48.021548}],"algorithmType":"PASSENGER_ORIENTED","remoteness":15000,"useProportion":true,"proportion":0.3,"useAzimuth":true,"azimuthMargin":10,"maxDetourDistanceRatio":0.3,"maxDetourDurationRatio":0.3}},"_domainEvents":[]}',
);
expect(matchingEntity.getProps().query.fromDate).toBe('2023-09-01');
});
});