save results to redis
This commit is contained in:
parent
5c802df529
commit
09efe313ba
|
@ -15,14 +15,14 @@ MESSAGE_BROKER_EXCHANGE=mobicoop
|
|||
REDIS_HOST=v3-redis
|
||||
REDIS_PASSWORD=redis
|
||||
REDIS_PORT=6379
|
||||
REDIS_MATCHING_KEY=MATCHER:MATCHING
|
||||
REDIS_MATCHING_TTL=900
|
||||
|
||||
# CACHE
|
||||
CACHE_TTL=5000
|
||||
|
||||
# DEFAULT CONFIGURATION
|
||||
|
||||
# default identifier used for match requests
|
||||
DEFAULT_UUID=00000000-0000-0000-0000-000000000000
|
||||
# algorithm type
|
||||
ALGORITHM=PASSENGER_ORIENTED
|
||||
# max distance in metres between driver
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
"@grpc/proto-loader": "^0.7.6",
|
||||
"@liaoliaots/nestjs-redis": "^9.0.5",
|
||||
"@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/message-broker-module": "^1.2.0",
|
||||
"@nestjs/axios": "^2.0.0",
|
||||
|
@ -1505,9 +1505,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@mobicoop/ddd-library": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@mobicoop/ddd-library/-/ddd-library-1.3.0.tgz",
|
||||
"integrity": "sha512-WQTOIzGvsoh3o43Kukb9NIbJw18lsfSqu3k3cMZxc2mmgaYD7MtS4Yif/+KayQ6Ea4Ve3Hc6BVDls2X6svsoOg==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@mobicoop/ddd-library/-/ddd-library-1.5.0.tgz",
|
||||
"integrity": "sha512-CX/V2+vSXrGtKobsyBfVpMW323ZT8tHrgUl1qrvU1XjRKNShvwsKyC7739x7CNgkJ9sr3XV+75JrOXEnqU83zw==",
|
||||
"dependencies": {
|
||||
"@nestjs/event-emitter": "^1.4.2",
|
||||
"@nestjs/microservices": "^9.4.0",
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
"@grpc/proto-loader": "^0.7.6",
|
||||
"@liaoliaots/nestjs-redis": "^9.0.5",
|
||||
"@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/message-broker-module": "^1.2.0",
|
||||
"@nestjs/axios": "^2.0.0",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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_MESSAGE_PUBLISHER = Symbol('AD_MESSAGE_PUBLISHER');
|
||||
export const AD_GET_BASIC_ROUTE_CONTROLLER = Symbol(
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
INPUT_DATETIME_TRANSFORMER,
|
||||
AD_GET_DETAILED_ROUTE_CONTROLLER,
|
||||
OUTPUT_DATETIME_TRANSFORMER,
|
||||
MATCHING_REPOSITORY,
|
||||
} from './ad.di-tokens';
|
||||
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
|
||||
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 { MatchMapper } from './match.mapper';
|
||||
import { OutputDateTimeTransformer } from './infrastructure/output-datetime-transformer';
|
||||
import { MatchingRepository } from './infrastructure/matching.repository';
|
||||
import { MatchingMapper } from './matching.mapper';
|
||||
|
||||
const grpcControllers = [MatchGrpcController];
|
||||
|
||||
|
@ -41,13 +44,17 @@ const commandHandlers: Provider[] = [CreateAdService];
|
|||
|
||||
const queryHandlers: Provider[] = [MatchQueryHandler];
|
||||
|
||||
const mappers: Provider[] = [AdMapper, MatchMapper];
|
||||
const mappers: Provider[] = [AdMapper, MatchMapper, MatchingMapper];
|
||||
|
||||
const repositories: Provider[] = [
|
||||
{
|
||||
provide: AD_REPOSITORY,
|
||||
useClass: AdRepository,
|
||||
},
|
||||
{
|
||||
provide: MATCHING_REPOSITORY,
|
||||
useClass: MatchingRepository,
|
||||
},
|
||||
];
|
||||
|
||||
const messagePublishers: Provider[] = [
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
import { MatchingEntity } from '../../domain/matching.entity';
|
||||
|
||||
export type MatchingRepositoryPort = {
|
||||
get(id: string): Promise<MatchingEntity>;
|
||||
save(matching: MatchingEntity): Promise<void>;
|
||||
};
|
|
@ -1,5 +1,5 @@
|
|||
import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
||||
import { MatchQuery } from './match.query';
|
||||
import { MatchQuery, ScheduleItem } from './match.query';
|
||||
import { Algorithm } from './algorithm.abstract';
|
||||
import { PassengerOrientedAlgorithm } from './passenger-oriented-algorithm';
|
||||
import { AlgorithmType } from '../../types/algorithm.types';
|
||||
|
@ -8,12 +8,16 @@ import { AdRepositoryPort } from '@modules/ad/core/application/ports/ad.reposito
|
|||
import {
|
||||
AD_REPOSITORY,
|
||||
INPUT_DATETIME_TRANSFORMER,
|
||||
MATCHING_REPOSITORY,
|
||||
PARAMS_PROVIDER,
|
||||
} from '@modules/ad/ad.di-tokens';
|
||||
import { MatchEntity } from '@modules/ad/core/domain/match.entity';
|
||||
import { DefaultParamsProviderPort } from '../../ports/default-params-provider.port';
|
||||
import { DefaultParams } from '../../ports/default-params.type';
|
||||
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)
|
||||
export class MatchQueryHandler implements IQueryHandler {
|
||||
|
@ -22,14 +26,16 @@ export class MatchQueryHandler implements IQueryHandler {
|
|||
constructor(
|
||||
@Inject(PARAMS_PROVIDER)
|
||||
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)
|
||||
private readonly datetimeTransformer: DateTimeTransformerPort,
|
||||
) {
|
||||
this._defaultParams = defaultParamsProvider.getParams();
|
||||
}
|
||||
|
||||
execute = async (query: MatchQuery): Promise<MatchEntity[]> => {
|
||||
execute = async (query: MatchQuery): Promise<MatchingResult> => {
|
||||
query
|
||||
.setMissingMarginDurations(this._defaultParams.DEPARTURE_TIME_MARGIN)
|
||||
.setMissingStrict(this._defaultParams.STRICT)
|
||||
|
@ -60,8 +66,74 @@ export class MatchQueryHandler implements IQueryHandler {
|
|||
switch (query.algorithmType) {
|
||||
case AlgorithmType.PASSENGER_ORIENTED:
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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[];
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
};
|
||||
}
|
|
@ -33,7 +33,7 @@ export class TimeConverter implements TimeConverterPort {
|
|||
date: string,
|
||||
time: string,
|
||||
timezone: string,
|
||||
dst?: boolean,
|
||||
dst = false,
|
||||
): string =>
|
||||
new DateTime(`${date}T${time}`, TimeZone.zone('UTC'))
|
||||
.convert(TimeZone.zone(timezone, dst))
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import { PaginatedResponseDto } from '@mobicoop/ddd-library';
|
||||
|
||||
export abstract class IdPaginatedResponseDto<
|
||||
T,
|
||||
> extends PaginatedResponseDto<T> {
|
||||
readonly id: string;
|
||||
}
|
|
@ -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[];
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ import { Controller, Inject, UsePipes } from '@nestjs/common';
|
|||
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
||||
import { RpcValidationPipe } 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 { MatchRequestDto } from './dtos/match.request.dto';
|
||||
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 { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port';
|
||||
import { MatchMapper } from '@modules/ad/match.mapper';
|
||||
import { MatchingResult } from '@modules/ad/core/application/queries/match/match.query-handler';
|
||||
|
||||
@UsePipes(
|
||||
new RpcValidationPipe({
|
||||
|
@ -27,18 +28,19 @@ export class MatchGrpcController {
|
|||
) {}
|
||||
|
||||
@GrpcMethod('MatcherService', 'Match')
|
||||
async match(data: MatchRequestDto): Promise<MatchPaginatedResponseDto> {
|
||||
async match(data: MatchRequestDto): Promise<MatchingPaginatedResponseDto> {
|
||||
try {
|
||||
const matches: MatchEntity[] = await this.queryBus.execute(
|
||||
const matchingResult: MatchingResult = await this.queryBus.execute(
|
||||
new MatchQuery(data, this.routeProvider),
|
||||
);
|
||||
return new MatchPaginatedResponseDto({
|
||||
data: matches.map((match: MatchEntity) =>
|
||||
return new MatchingPaginatedResponseDto({
|
||||
id: matchingResult.id,
|
||||
data: matchingResult.matches.map((match: MatchEntity) =>
|
||||
this.matchMapper.toResponse(match),
|
||||
),
|
||||
page: 1,
|
||||
perPage: 5,
|
||||
total: matches.length,
|
||||
page: matchingResult.page,
|
||||
perPage: matchingResult.perPage,
|
||||
total: matchingResult.total,
|
||||
});
|
||||
} catch (e) {
|
||||
throw new RpcException({
|
||||
|
|
|
@ -24,6 +24,8 @@ message MatchRequest {
|
|||
float maxDetourDistanceRatio = 15;
|
||||
float maxDetourDurationRatio = 16;
|
||||
int32 identifier = 22;
|
||||
optional int32 page = 23;
|
||||
optional int32 perPage = 24;
|
||||
}
|
||||
|
||||
message ScheduleItem {
|
||||
|
@ -90,6 +92,9 @@ message Actor {
|
|||
}
|
||||
|
||||
message Matches {
|
||||
repeated Match data = 1;
|
||||
int32 total = 2;
|
||||
string id = 1;
|
||||
repeated Match data = 2;
|
||||
int32 total = 3;
|
||||
int32 page = 4;
|
||||
int32 perPage = 5;
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -1,17 +1,22 @@
|
|||
import {
|
||||
AD_REPOSITORY,
|
||||
INPUT_DATETIME_TRANSFORMER,
|
||||
MATCHING_REPOSITORY,
|
||||
PARAMS_PROVIDER,
|
||||
} from '@modules/ad/ad.di-tokens';
|
||||
import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.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 { 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 { Waypoint } from '@modules/ad/core/application/types/waypoint.type';
|
||||
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';
|
||||
|
||||
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 = {
|
||||
getParams: () => {
|
||||
return {
|
||||
|
@ -107,6 +133,10 @@ describe('Match Query Handler', () => {
|
|||
provide: AD_REPOSITORY,
|
||||
useValue: mockAdRepository,
|
||||
},
|
||||
{
|
||||
provide: MATCHING_REPOSITORY,
|
||||
useValue: mockMatchingRepository,
|
||||
},
|
||||
{
|
||||
provide: PARAMS_PROVIDER,
|
||||
useValue: mockDefaultParamsProvider,
|
||||
|
@ -125,7 +155,8 @@ describe('Match Query Handler', () => {
|
|||
expect(matchQueryHandler).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return a Match entity', async () => {
|
||||
it('should return a Matching', async () => {
|
||||
jest.spyOn(MatchingEntity, 'create');
|
||||
const matchQuery = new MatchQuery(
|
||||
{
|
||||
algorithmType: AlgorithmType.PASSENGER_ORIENTED,
|
||||
|
@ -146,7 +177,10 @@ describe('Match Query Handler', () => {
|
|||
},
|
||||
mockRouteProvider,
|
||||
);
|
||||
const matches: MatchEntity[] = await matchQueryHandler.execute(matchQuery);
|
||||
expect(matches.length).toBeGreaterThanOrEqual(0);
|
||||
const matching: MatchingResult = await matchQueryHandler.execute(
|
||||
matchQuery,
|
||||
);
|
||||
expect(matching.id).toHaveLength(36);
|
||||
expect(MatchingEntity.create).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -52,7 +52,7 @@ describe('Time Converter', () => {
|
|||
});
|
||||
|
||||
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 parisDate = '2023-06-22';
|
||||
const parisTime = '12:00';
|
||||
|
@ -64,7 +64,7 @@ describe('Time Converter', () => {
|
|||
);
|
||||
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 parisDate = '2023-02-02';
|
||||
const parisTime = '12:00';
|
||||
|
@ -72,6 +72,7 @@ describe('Time Converter', () => {
|
|||
parisDate,
|
||||
parisTime,
|
||||
'Europe/Paris',
|
||||
true,
|
||||
);
|
||||
expect(utcDate.toISOString()).toBe('2023-02-02T11:00:00.000Z');
|
||||
});
|
||||
|
@ -83,7 +84,6 @@ describe('Time Converter', () => {
|
|||
parisDate,
|
||||
parisTime,
|
||||
'Europe/Paris',
|
||||
false,
|
||||
);
|
||||
expect(utcDate.toISOString()).toBe('2023-06-22T11:00:00.000Z');
|
||||
});
|
||||
|
@ -148,6 +148,30 @@ describe('Time Converter', () => {
|
|||
});
|
||||
|
||||
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', () => {
|
||||
const timeConverter: TimeConverter = new TimeConverter();
|
||||
const utcDate = '2023-06-22';
|
||||
|
@ -157,29 +181,6 @@ describe('Time Converter', () => {
|
|||
utcTime,
|
||||
'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');
|
||||
});
|
||||
it('should convert a utc date to a tonga date isostring', () => {
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
||||
import { AD_ROUTE_PROVIDER } from '@modules/ad/ad.di-tokens';
|
||||
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 { Frequency, Role } from '@modules/ad/core/domain/ad.types';
|
||||
import { Target } from '@modules/ad/core/domain/candidate.types';
|
||||
import { MatchEntity } from '@modules/ad/core/domain/match.entity';
|
||||
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 { MatchingPaginatedResponseDto } from '@modules/ad/interface/dtos/matching.paginated.response.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 { MatchGrpcController } from '@modules/ad/interface/grpc-controllers/match.grpc-controller';
|
||||
|
@ -55,117 +57,126 @@ const recurrentMatchRequestDto: MatchRequestDto = {
|
|||
const mockQueryBus = {
|
||||
execute: jest
|
||||
.fn()
|
||||
.mockImplementationOnce(() => [
|
||||
MatchEntity.create({
|
||||
adId: '53a0bf71-4132-4f7b-a4cc-88c796b6bdf1',
|
||||
role: Role.DRIVER,
|
||||
frequency: Frequency.RECURRENT,
|
||||
distance: 356041,
|
||||
duration: 12647,
|
||||
initialDistance: 349251,
|
||||
initialDuration: 12103,
|
||||
journeys: [
|
||||
{
|
||||
firstDate: new Date('2023-09-01'),
|
||||
lastDate: new Date('2024-08-30'),
|
||||
journeyItems: [
|
||||
new JourneyItem({
|
||||
lat: 48.689445,
|
||||
lon: 6.17651,
|
||||
duration: 0,
|
||||
distance: 0,
|
||||
actorTimes: [
|
||||
new ActorTime({
|
||||
role: Role.DRIVER,
|
||||
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'),
|
||||
lastDatetime: new Date('2024-08-30 07:00'),
|
||||
lastMinDatetime: new Date('2024-08-30 06:45'),
|
||||
lastMaxDatetime: new Date('2024-08-30 07:15'),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new JourneyItem({
|
||||
lat: 48.369445,
|
||||
lon: 6.67487,
|
||||
duration: 2100,
|
||||
distance: 56878,
|
||||
actorTimes: [
|
||||
new ActorTime({
|
||||
role: Role.DRIVER,
|
||||
target: Target.NEUTRAL,
|
||||
firstDatetime: new Date('2023-09-01 07:35'),
|
||||
firstMinDatetime: new Date('2023-09-01 07:20'),
|
||||
firstMaxDatetime: new Date('2023-09-01 07:50'),
|
||||
lastDatetime: new Date('2024-08-30 07:35'),
|
||||
lastMinDatetime: new Date('2024-08-30 07:20'),
|
||||
lastMaxDatetime: new Date('2024-08-30 07:50'),
|
||||
}),
|
||||
new ActorTime({
|
||||
role: Role.PASSENGER,
|
||||
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'),
|
||||
lastDatetime: new Date('2024-08-30 07:32'),
|
||||
lastMinDatetime: new Date('2024-08-30 07:17'),
|
||||
lastMaxDatetime: new Date('2024-08-30 07:47'),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new JourneyItem({
|
||||
lat: 47.98487,
|
||||
lon: 6.9427,
|
||||
duration: 3840,
|
||||
distance: 76491,
|
||||
actorTimes: [
|
||||
new ActorTime({
|
||||
role: Role.DRIVER,
|
||||
target: Target.NEUTRAL,
|
||||
firstDatetime: new Date('2023-09-01 08:04'),
|
||||
firstMinDatetime: new Date('2023-09-01 07:51'),
|
||||
firstMaxDatetime: new Date('2023-09-01 08:19'),
|
||||
lastDatetime: new Date('2024-08-30 08:04'),
|
||||
lastMinDatetime: new Date('2024-08-30 07:51'),
|
||||
lastMaxDatetime: new Date('2024-08-30 08:19'),
|
||||
}),
|
||||
new ActorTime({
|
||||
role: Role.PASSENGER,
|
||||
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'),
|
||||
lastDatetime: new Date('2024-08-30 08:01'),
|
||||
lastMinDatetime: new Date('2024-08-30 07:46'),
|
||||
lastMaxDatetime: new Date('2024-08-30 08:16'),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new JourneyItem({
|
||||
lat: 47.365987,
|
||||
lon: 7.02154,
|
||||
duration: 4980,
|
||||
distance: 96475,
|
||||
actorTimes: [
|
||||
new ActorTime({
|
||||
role: Role.DRIVER,
|
||||
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'),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
.mockImplementationOnce(
|
||||
() =>
|
||||
<MatchingResult>{
|
||||
id: '43c83ae2-f4b0-4ac6-b8bf-8071801924d4',
|
||||
page: 1,
|
||||
perPage: 10,
|
||||
matches: [
|
||||
MatchEntity.create({
|
||||
adId: '53a0bf71-4132-4f7b-a4cc-88c796b6bdf1',
|
||||
role: Role.DRIVER,
|
||||
frequency: Frequency.RECURRENT,
|
||||
distance: 356041,
|
||||
duration: 12647,
|
||||
initialDistance: 349251,
|
||||
initialDuration: 12103,
|
||||
journeys: [
|
||||
{
|
||||
firstDate: new Date('2023-09-01'),
|
||||
lastDate: new Date('2024-08-30'),
|
||||
journeyItems: [
|
||||
new JourneyItem({
|
||||
lat: 48.689445,
|
||||
lon: 6.17651,
|
||||
duration: 0,
|
||||
distance: 0,
|
||||
actorTimes: [
|
||||
new ActorTime({
|
||||
role: Role.DRIVER,
|
||||
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'),
|
||||
lastDatetime: new Date('2024-08-30 07:00'),
|
||||
lastMinDatetime: new Date('2024-08-30 06:45'),
|
||||
lastMaxDatetime: new Date('2024-08-30 07:15'),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new JourneyItem({
|
||||
lat: 48.369445,
|
||||
lon: 6.67487,
|
||||
duration: 2100,
|
||||
distance: 56878,
|
||||
actorTimes: [
|
||||
new ActorTime({
|
||||
role: Role.DRIVER,
|
||||
target: Target.NEUTRAL,
|
||||
firstDatetime: new Date('2023-09-01 07:35'),
|
||||
firstMinDatetime: new Date('2023-09-01 07:20'),
|
||||
firstMaxDatetime: new Date('2023-09-01 07:50'),
|
||||
lastDatetime: new Date('2024-08-30 07:35'),
|
||||
lastMinDatetime: new Date('2024-08-30 07:20'),
|
||||
lastMaxDatetime: new Date('2024-08-30 07:50'),
|
||||
}),
|
||||
new ActorTime({
|
||||
role: Role.PASSENGER,
|
||||
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'),
|
||||
lastDatetime: new Date('2024-08-30 07:32'),
|
||||
lastMinDatetime: new Date('2024-08-30 07:17'),
|
||||
lastMaxDatetime: new Date('2024-08-30 07:47'),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new JourneyItem({
|
||||
lat: 47.98487,
|
||||
lon: 6.9427,
|
||||
duration: 3840,
|
||||
distance: 76491,
|
||||
actorTimes: [
|
||||
new ActorTime({
|
||||
role: Role.DRIVER,
|
||||
target: Target.NEUTRAL,
|
||||
firstDatetime: new Date('2023-09-01 08:04'),
|
||||
firstMinDatetime: new Date('2023-09-01 07:51'),
|
||||
firstMaxDatetime: new Date('2023-09-01 08:19'),
|
||||
lastDatetime: new Date('2024-08-30 08:04'),
|
||||
lastMinDatetime: new Date('2024-08-30 07:51'),
|
||||
lastMaxDatetime: new Date('2024-08-30 08:19'),
|
||||
}),
|
||||
new ActorTime({
|
||||
role: Role.PASSENGER,
|
||||
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'),
|
||||
lastDatetime: new Date('2024-08-30 08:01'),
|
||||
lastMinDatetime: new Date('2024-08-30 07:46'),
|
||||
lastMaxDatetime: new Date('2024-08-30 08:16'),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new JourneyItem({
|
||||
lat: 47.365987,
|
||||
lon: 7.02154,
|
||||
duration: 4980,
|
||||
distance: 96475,
|
||||
actorTimes: [
|
||||
new ActorTime({
|
||||
role: Role.DRIVER,
|
||||
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(() => {
|
||||
throw new Error();
|
||||
}),
|
||||
|
@ -319,12 +330,16 @@ describe('Match Grpc Controller', () => {
|
|||
expect(matchGrpcController).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return matches', async () => {
|
||||
it('should return a matching', async () => {
|
||||
jest.spyOn(mockQueryBus, 'execute');
|
||||
const matchPaginatedResponseDto = await matchGrpcController.match(
|
||||
recurrentMatchRequestDto,
|
||||
const matchingPaginatedResponseDto: MatchingPaginatedResponseDto =
|
||||
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);
|
||||
});
|
||||
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue