create ad WIP, extract geography methods to dedicated module

This commit is contained in:
sbriat 2023-04-25 17:49:47 +02:00
parent ca693087d2
commit aeead7fb62
53 changed files with 824 additions and 222 deletions

View File

@ -1,65 +0,0 @@
-- CreateExtension
CREATE EXTENSION IF NOT EXISTS "postgis";
-- Required to use postgis extension :
-- set the search_path to both public and territory (where is postgis) AND the current schema
SET search_path TO matcher, territory, public;
-- CreateTable
CREATE TABLE "ad" (
"uuid" UUID NOT NULL,
"driver" BOOLEAN NOT NULL,
"passenger" BOOLEAN NOT NULL,
"frequency" INTEGER NOT NULL,
"from_date" DATE NOT NULL,
"to_date" DATE NOT NULL,
"mon_time" TIMESTAMPTZ NOT NULL,
"tue_time" TIMESTAMPTZ NOT NULL,
"wed_time" TIMESTAMPTZ NOT NULL,
"thu_time" TIMESTAMPTZ NOT NULL,
"fri_time" TIMESTAMPTZ NOT NULL,
"sat_time" TIMESTAMPTZ NOT NULL,
"sun_time" TIMESTAMPTZ NOT NULL,
"mon_margin" INTEGER NOT NULL,
"tue_margin" INTEGER NOT NULL,
"wed_margin" INTEGER NOT NULL,
"thu_margin" INTEGER NOT NULL,
"fri_margin" INTEGER NOT NULL,
"sat_margin" INTEGER NOT NULL,
"sun_margin" INTEGER NOT NULL,
"driver_duration" INTEGER NOT NULL,
"driver_distance" INTEGER NOT NULL,
"passenger_duration" INTEGER NOT NULL,
"passenger_distance" INTEGER NOT NULL,
"origin_type" SMALLINT NOT NULL,
"destination_type" SMALLINT NOT NULL,
"waypoints" geography(LINESTRING) NOT NULL,
"direction" geography(LINESTRING) NOT NULL,
"fwd_azimuth" INTEGER NOT NULL,
"back_azimuth" INTEGER NOT NULL,
"seats_driver" SMALLINT NOT NULL,
"seats_passenger" SMALLINT NOT NULL,
"seats_used" SMALLINT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "ad_pkey" PRIMARY KEY ("uuid")
);
-- CreateIndex
CREATE INDEX "ad_driver_idx" ON "ad"("driver");
-- CreateIndex
CREATE INDEX "ad_passenger_idx" ON "ad"("passenger");
-- CreateIndex
CREATE INDEX "ad_from_date_idx" ON "ad"("from_date");
-- CreateIndex
CREATE INDEX "ad_to_date_idx" ON "ad"("to_date");
-- CreateIndex
CREATE INDEX "ad_fwd_azimuth_idx" ON "ad"("fwd_azimuth");
-- CreateIndex
CREATE INDEX "direction_idx" ON "ad" USING GIST ("direction");

View File

@ -0,0 +1,65 @@
-- CreateExtension
CREATE EXTENSION IF NOT EXISTS "postgis";
-- Required to use postgis extension :
-- set the search_path to both public and territory (where is postgis) AND the current schema
SET search_path TO matcher, territory, public;
-- CreateTable
CREATE TABLE "ad" (
"uuid" UUID NOT NULL,
"driver" BOOLEAN NOT NULL,
"passenger" BOOLEAN NOT NULL,
"frequency" INTEGER NOT NULL,
"fromDate" DATE NOT NULL,
"toDate" DATE NOT NULL,
"monTime" TIMESTAMPTZ NOT NULL,
"tueTime" TIMESTAMPTZ NOT NULL,
"wedTime" TIMESTAMPTZ NOT NULL,
"thuTime" TIMESTAMPTZ NOT NULL,
"friTime" TIMESTAMPTZ NOT NULL,
"satTime" TIMESTAMPTZ NOT NULL,
"sunTime" TIMESTAMPTZ NOT NULL,
"monMargin" INTEGER NOT NULL,
"tueMargin" INTEGER NOT NULL,
"wedMargin" INTEGER NOT NULL,
"thuMargin" INTEGER NOT NULL,
"friMargin" INTEGER NOT NULL,
"satMargin" INTEGER NOT NULL,
"sunMargin" INTEGER NOT NULL,
"driverDuration" INTEGER NOT NULL,
"driverDistance" INTEGER NOT NULL,
"passengerDuration" INTEGER NOT NULL,
"passengerDistance" INTEGER NOT NULL,
"originType" SMALLINT NOT NULL,
"destinationType" SMALLINT NOT NULL,
"waypoints" geography(LINESTRING),
"direction" geography(LINESTRING),
"fwdAzimuth" INTEGER NOT NULL,
"backAzimuth" INTEGER NOT NULL,
"seatsDriver" SMALLINT NOT NULL,
"seatsPassenger" SMALLINT NOT NULL,
"seatsUsed" SMALLINT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "ad_pkey" PRIMARY KEY ("uuid")
);
-- CreateIndex
CREATE INDEX "ad_driver_idx" ON "ad"("driver");
-- CreateIndex
CREATE INDEX "ad_passenger_idx" ON "ad"("passenger");
-- CreateIndex
CREATE INDEX "ad_fromDate_idx" ON "ad"("fromDate");
-- CreateIndex
CREATE INDEX "ad_toDate_idx" ON "ad"("toDate");
-- CreateIndex
CREATE INDEX "ad_fwdAzimuth_idx" ON "ad"("fwdAzimuth");
-- CreateIndex
CREATE INDEX "direction_idx" ON "ad" USING GIST ("direction");

View File

@ -13,47 +13,47 @@ datasource db {
}
model Ad {
uuid String @id @default(uuid()) @db.Uuid
driver Boolean
passenger Boolean
frequency Int
from_date DateTime @db.Date
to_date DateTime @db.Date
mon_time DateTime @db.Timestamptz()
tue_time DateTime @db.Timestamptz()
wed_time DateTime @db.Timestamptz()
thu_time DateTime @db.Timestamptz()
fri_time DateTime @db.Timestamptz()
sat_time DateTime @db.Timestamptz()
sun_time DateTime @db.Timestamptz()
mon_margin Int
tue_margin Int
wed_margin Int
thu_margin Int
fri_margin Int
sat_margin Int
sun_margin Int
driver_duration Int
driver_distance Int
passenger_duration Int
passenger_distance Int
origin_type Int @db.SmallInt
destination_type Int @db.SmallInt
waypoints Unsupported("geography(LINESTRING)")
direction Unsupported("geography(LINESTRING)")
fwd_azimuth Int
back_azimuth Int
seats_driver Int @db.SmallInt
seats_passenger Int @db.SmallInt
seats_used Int @db.SmallInt
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
uuid String @id @default(uuid()) @db.Uuid
driver Boolean
passenger Boolean
frequency Int
fromDate DateTime @db.Date
toDate DateTime @db.Date
monTime DateTime @db.Timestamptz()
tueTime DateTime @db.Timestamptz()
wedTime DateTime @db.Timestamptz()
thuTime DateTime @db.Timestamptz()
friTime DateTime @db.Timestamptz()
satTime DateTime @db.Timestamptz()
sunTime DateTime @db.Timestamptz()
monMargin Int
tueMargin Int
wedMargin Int
thuMargin Int
friMargin Int
satMargin Int
sunMargin Int
driverDuration Int
driverDistance Int
passengerDuration Int
passengerDistance Int
originType Int @db.SmallInt
destinationType Int @db.SmallInt
waypoints Unsupported("geography(LINESTRING)")?
direction Unsupported("geography(LINESTRING)")?
fwdAzimuth Int
backAzimuth Int
seatsDriver Int @db.SmallInt
seatsPassenger Int @db.SmallInt
seatsUsed Int @db.SmallInt
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
@@index([driver])
@@index([passenger])
@@index([from_date])
@@index([to_date])
@@index([fwd_azimuth])
@@index([fromDate])
@@index([toDate])
@@index([fwdAzimuth])
@@index([direction], name: "direction_idx", type: Gist)
@@map("ad")
}

View File

@ -2,9 +2,16 @@ import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq';
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AdMessagerController } from './adapters/primaries/ad-messager.controller';
import { AdProfile } from './mappers/ad.profile';
import { CreateAdUseCase } from './domain/usecases/create-ad.usecase';
import { AdRepository } from './adapters/secondaries/ad.repository';
import { DatabaseModule } from '../database/database.module';
import { CqrsModule } from '@nestjs/cqrs';
@Module({
imports: [
DatabaseModule,
CqrsModule,
RabbitMQModule.forRootAsync(RabbitMQModule, {
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
@ -18,9 +25,7 @@ import { AdMessagerController } from './adapters/primaries/ad-messager.controlle
adCreated: {
exchange: configService.get<string>('RMQ_EXCHANGE'),
routingKey: 'ad.created',
queue: `${configService.get<string>(
'RMQ_EXCHANGE',
)}-matcher-ad-created`,
queue: 'matcher-ad-created',
},
},
uri: configService.get<string>('RMQ_URI'),
@ -31,7 +36,7 @@ import { AdMessagerController } from './adapters/primaries/ad-messager.controlle
}),
],
controllers: [AdMessagerController],
providers: [],
providers: [AdProfile, AdRepository, CreateAdUseCase],
exports: [],
})
export class AdModule {}

View File

@ -1,19 +1,32 @@
import { RabbitSubscribe } from '@golevelup/nestjs-rabbitmq';
import { Controller } from '@nestjs/common';
// import { Ad } from '../../domain/entities/ad';
import { Ad } from '../../domain/entities/ad';
import { InjectMapper } from '@automapper/nestjs';
import { Mapper } from '@automapper/core';
import { CommandBus } from '@nestjs/cqrs';
import { CreateAdCommand } from '../../commands/create-ad.command';
import { AdPresenter } from './ad.presenter';
import { CreateAdRequest } from '../../domain/dtos/create-ad.request';
@Controller()
export class AdMessagerController {
constructor(
private readonly _commandBus: CommandBus,
@InjectMapper() private readonly _mapper: Mapper,
) {}
@RabbitSubscribe({
name: 'adCreated',
})
public adCreatedHandler(message: string) {
console.log(JSON.parse(message));
// try {
// const createdAd: Ad = JSON.parse(message);
// console.log(createdAd);
// } catch (e) {
// console.log('error', e);
// }
async adCreatedHandler(message: string): Promise<AdPresenter> {
try {
const createAdRequest: CreateAdRequest = JSON.parse(message);
const ad: Ad = await this._commandBus.execute(
new CreateAdCommand(createAdRequest),
);
return this._mapper.map(ad, Ad, AdPresenter);
} catch (e) {
console.log('error', e);
}
}
}

View File

@ -1,6 +1,6 @@
import { AutoMap } from '@automapper/classes';
export class Ad {
export class AdPresenter {
@AutoMap()
uuid: string;
}

View File

@ -0,0 +1,79 @@
import { Injectable } from '@nestjs/common';
import { MatcherRepository } from '../../../database/src/domain/matcher-repository';
import { Ad } from '../../domain/entities/ad';
import { DatabaseException } from '../../../database/src/exceptions/database.exception';
@Injectable()
export class AdRepository extends MatcherRepository<Ad> {
protected _model = 'ad';
async createAd(ad: Partial<Ad>): Promise<Ad> {
try {
const affectedRowNumber = await this.createWithFields(
this.createFields(ad),
);
if (affectedRowNumber == 1) {
return this.findOneByUuid(ad.uuid);
}
throw new DatabaseException();
} catch (e) {
throw e;
}
}
private createFields(ad: Partial<Ad>): Partial<AdFields> {
return {
uuid: `'${ad.uuid}'`,
driver: ad.driver ? 'true' : 'false',
passenger: ad.passenger ? 'true' : 'false',
frequency: ad.frequency,
fromDate: `'${ad.fromDate}'`,
toDate: `'${ad.toDate}'`,
monTime: `'${ad.monTime}'`,
tueTime: `'${ad.tueTime}'`,
wedTime: `'${ad.wedTime}'`,
thuTime: `'${ad.thuTime}'`,
friTime: `'${ad.friTime}'`,
satTime: `'${ad.satTime}'`,
sunTime: `'${ad.sunTime}'`,
};
}
}
type AdFields = {
uuid: string;
driver: string;
passenger: string;
frequency: number;
fromDate: string;
toDate: string;
monTime: string;
tueTime: string;
wedTime: string;
thuTime: string;
friTime: string;
satTime: string;
sunTime: string;
monMargin: number;
tueMargin: number;
wedMargin: number;
thuMargin: number;
friMargin: number;
satMargin: number;
sunMargin: number;
driverDuration: number;
driverDistance: number;
passengerDuration: number;
passengerDistance: number;
originType: number;
destinationType: number;
waypoints: string;
direction: string;
fwdAzimuth: number;
backAzimuth: number;
seatsDriver: number;
seatsPassenger: number;
seatsUsed: number;
createdAt: string;
updatedAt: string;
};

View File

@ -0,0 +1,9 @@
import { CreateAdRequest } from '../domain/dtos/create-ad.request';
export class CreateAdCommand {
readonly createAdRequest: CreateAdRequest;
constructor(request: CreateAdRequest) {
this.createAdRequest = request;
}
}

View File

@ -0,0 +1,116 @@
import { AutoMap } from '@automapper/classes';
import { IsBoolean, IsNotEmpty, IsNumber, IsString } from 'class-validator';
export class CreateAdRequest {
@IsString()
@IsNotEmpty()
@AutoMap()
uuid: string;
@IsBoolean()
@AutoMap()
driver: boolean;
@IsBoolean()
@AutoMap()
passenger: boolean;
@IsNumber()
@AutoMap()
frequency: number;
@IsString()
@AutoMap()
fromDate: string;
@IsString()
@AutoMap()
toDate: string;
@IsString()
@AutoMap()
monTime: string;
@IsString()
@AutoMap()
tueTime: string;
@IsString()
@AutoMap()
wedTime: string;
@IsString()
@AutoMap()
thuTime: string;
@IsString()
@AutoMap()
friTime: string;
@IsString()
@AutoMap()
satTime: string;
@IsString()
@AutoMap()
sunTime: string;
@IsNumber()
@AutoMap()
monMargin: number;
@IsNumber()
@AutoMap()
tueMargin: number;
@IsNumber()
@AutoMap()
wedMargin: number;
@IsNumber()
@AutoMap()
thuMargin: number;
@IsNumber()
@AutoMap()
friMargin: number;
@IsNumber()
@AutoMap()
satMargin: number;
@IsNumber()
@AutoMap()
sunMargin: number;
@IsNumber()
@AutoMap()
originType: number;
@IsNumber()
@AutoMap()
destinationType: number;
@AutoMap()
waypoints: [];
@IsNumber()
@AutoMap()
seatsDriver: number;
@IsNumber()
@AutoMap()
seatsPassenger: number;
@IsNumber()
@AutoMap()
seatsUsed: number;
@IsString()
@AutoMap()
createdAt: string;
@IsString()
@AutoMap()
updatedAt: string;
}

View File

@ -4,38 +4,105 @@ export class Ad {
@AutoMap()
uuid: string;
@AutoMap()
driver: boolean;
@AutoMap()
passenger: boolean;
@AutoMap()
frequency: number;
@AutoMap()
fromDate: string;
@AutoMap()
toDate: string;
@AutoMap()
monTime: string;
@AutoMap()
tueTime: string;
@AutoMap()
wedTime: string;
@AutoMap()
thuTime: string;
@AutoMap()
friTime: string;
@AutoMap()
satTime: string;
@AutoMap()
sunTime: string;
@AutoMap()
monMargin: number;
@AutoMap()
tueMargin: number;
@AutoMap()
wedMargin: number;
@AutoMap()
thuMargin: number;
@AutoMap()
friMargin: number;
@AutoMap()
satMargin: number;
@AutoMap()
sunMargin: number;
@AutoMap()
driverDuration: number;
@AutoMap()
driverDistance: number;
@AutoMap()
passengerDuration: number;
@AutoMap()
passengerDistance: number;
@AutoMap()
originType: number;
@AutoMap()
destinationType: number;
@AutoMap()
waypoints: [];
@AutoMap()
direction: string;
@AutoMap()
fwdAzimuth: number;
@AutoMap()
backAzimuth: number;
@AutoMap()
seatsDriver: number;
@AutoMap()
seatsPassenger: number;
@AutoMap()
seatsUsed: number;
@AutoMap()
createdAt: string;
@AutoMap()
updatedAt: string;
}

View File

@ -0,0 +1,17 @@
import { CommandHandler } from '@nestjs/cqrs';
import { CreateAdCommand } from '../../commands/create-ad.command';
import { Ad } from '../entities/ad';
import { AdRepository } from '../../adapters/secondaries/ad.repository';
@CommandHandler(CreateAdCommand)
export class CreateAdUseCase {
constructor(private readonly adRepository: AdRepository) {}
async execute(command: CreateAdCommand): Promise<Ad> {
try {
return await this.adRepository.createAd(command.createAdRequest);
} catch (error) {
throw error;
}
}
}

View File

@ -0,0 +1,18 @@
import { createMap, Mapper } from '@automapper/core';
import { AutomapperProfile, InjectMapper } from '@automapper/nestjs';
import { Injectable } from '@nestjs/common';
import { Ad } from '../domain/entities/ad';
import { AdPresenter } from '../adapters/primaries/ad.presenter';
@Injectable()
export class AdProfile extends AutomapperProfile {
constructor(@InjectMapper() mapper: Mapper) {
super(mapper);
}
override get profile() {
return (mapper: any) => {
createMap(mapper, Ad, AdPresenter);
};
}
}

View File

@ -1,9 +1,9 @@
import { Module } from '@nestjs/common';
import { PrismaService } from './src/adapters/secondaries/prisma-service';
import { MatcherRepository } from './src/domain/matcher-repository';
import { AdRepository } from '../ad/adapters/secondaries/ad.repository';
@Module({
providers: [PrismaService, MatcherRepository],
exports: [PrismaService, MatcherRepository],
providers: [PrismaService, AdRepository],
exports: [PrismaService, AdRepository],
})
export class DatabaseModule {}

View File

@ -202,9 +202,9 @@ export abstract class PrismaRepository<T> implements IRepository<T> {
async createWithFields(fields: object): Promise<number> {
try {
const command = `INSERT INTO ${this._model} (${Object.keys(fields).join(
',',
)}) VALUES (${Object.values(fields).join(',')})`;
const command = `INSERT INTO ${this._model} ("${Object.keys(fields).join(
'","',
)}") VALUES (${Object.values(fields).join(',')})`;
return await this._prisma.$executeRawUnsafe(command);
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError) {
@ -219,7 +219,7 @@ export abstract class PrismaRepository<T> implements IRepository<T> {
}
}
async updateWithFields(uuid: string, entity: Partial<T>): Promise<number> {
async updateWithFields(uuid: string, entity: object): Promise<number> {
entity['"updatedAt"'] = `to_timestamp(${Date.now()} / 1000.0)`;
const values = Object.keys(entity).map((key) => `${key} = ${entity[key]}`);
try {

View File

@ -0,0 +1,8 @@
import { Injectable } from '@nestjs/common';
import { IFindTimezone } from '../../domain/interfaces/timezone-finder.interface';
import { find } from 'geo-tz';
@Injectable()
export class GeoTimezoneFinder implements IFindTimezone {
timezones = (lon: number, lat: number): Array<string> => find(lat, lon);
}

View File

@ -0,0 +1,27 @@
import { Injectable } from '@nestjs/common';
import { Geodesic as Geolib, GeodesicClass } from 'geographiclib-geodesic';
import { IGeodesic } from '../../domain/interfaces/geodesic.interface';
@Injectable()
export class Geodesic implements IGeodesic {
private geod: GeodesicClass;
constructor() {
this.geod = Geolib.WGS84;
}
inverse = (
lon1: number,
lat1: number,
lon2: number,
lat2: number,
): { azimuth: number; distance: number } => {
const { azi2: azimuth, s12: distance } = this.geod.Inverse(
lat1,
lon1,
lat2,
lon2,
);
return { azimuth, distance };
};
}

View File

@ -0,0 +1,3 @@
export interface IFindTimezone {
timezones(lon: number, lat: number): Array<string>;
}

View File

@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { GeoTimezoneFinder } from './adapters/secondaries/geo-timezone-finder';
import { Geodesic } from './adapters/secondaries/geodesic';
@Module({
providers: [GeoTimezoneFinder, Geodesic],
exports: [GeoTimezoneFinder, Geodesic],
})
export class GeographyModule {}

View File

@ -0,0 +1,14 @@
import { GeoTimezoneFinder } from '../../adapters/secondaries/geo-timezone-finder';
describe('Geo TZ Finder', () => {
it('should be defined', () => {
const timezoneFinder: GeoTimezoneFinder = new GeoTimezoneFinder();
expect(timezoneFinder).toBeDefined();
});
it('should get timezone for Nancy(France) as Europe/Paris', () => {
const timezoneFinder: GeoTimezoneFinder = new GeoTimezoneFinder();
const timezones = timezoneFinder.timezones(6.179373, 48.687913);
expect(timezones.length).toBe(1);
expect(timezones[0]).toBe('Europe/Paris');
});
});

View File

@ -0,0 +1,14 @@
import { Geodesic } from '../../adapters/secondaries/geodesic';
describe('Matcher geodesic', () => {
it('should be defined', () => {
const geodesic: Geodesic = new Geodesic();
expect(geodesic).toBeDefined();
});
it('should get inverse values', () => {
const geodesic: Geodesic = new Geodesic();
const inv = geodesic.inverse(0, 0, 1, 1);
expect(Math.round(inv.azimuth)).toBe(45);
expect(Math.round(inv.distance)).toBe(156900);
});
});

View File

@ -4,7 +4,7 @@ import {
HealthIndicator,
HealthIndicatorResult,
} from '@nestjs/terminus';
import { AdRepository } from '../../../matcher/adapters/secondaries/ad.repository';
import { AdRepository } from '../../../ad/adapters/secondaries/ad.repository';
@Injectable()
export class PrismaHealthIndicatorUseCase extends HealthIndicator {

View File

@ -7,7 +7,7 @@ import { TerminusModule } from '@nestjs/terminus';
import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { Messager } from './adapters/secondaries/messager';
import { AdRepository } from '../matcher/adapters/secondaries/ad.repository';
import { AdRepository } from '../ad/adapters/secondaries/ad.repository';
@Module({
imports: [

View File

@ -1,7 +1,7 @@
import { Test, TestingModule } from '@nestjs/testing';
import { PrismaHealthIndicatorUseCase } from '../../domain/usecases/prisma.health-indicator.usecase';
import { HealthCheckError, HealthIndicatorResult } from '@nestjs/terminus';
import { AdRepository } from '../../../matcher/adapters/secondaries/ad.repository';
import { AdRepository } from '../../../ad/adapters/secondaries/ad.repository';
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
const mockAdRepository = {

View File

@ -11,6 +11,7 @@ import { MatchPresenter } from '../secondaries/match.presenter';
import { DefaultParamsProvider } from '../secondaries/default-params.provider';
import { GeorouterCreator } from '../secondaries/georouter-creator';
import { Match } from '../../domain/entities/ecosystem/match';
import { GeoTimezoneFinder } from '../../../geography/adapters/secondaries/geo-timezone-finder';
@UsePipes(
new RpcValidationPipe({
@ -21,20 +22,22 @@ import { Match } from '../../domain/entities/ecosystem/match';
@Controller()
export class MatcherController {
constructor(
private readonly _queryBus: QueryBus,
private readonly _defaultParamsProvider: DefaultParamsProvider,
private readonly queryBus: QueryBus,
private readonly defaultParamsProvider: DefaultParamsProvider,
@InjectMapper() private readonly _mapper: Mapper,
private readonly _georouterCreator: GeorouterCreator,
private readonly georouterCreator: GeorouterCreator,
private readonly timezoneFinder: GeoTimezoneFinder,
) {}
@GrpcMethod('MatcherService', 'Match')
async match(data: MatchRequest): Promise<ICollection<Match>> {
try {
const matchCollection = await this._queryBus.execute(
const matchCollection = await this.queryBus.execute(
new MatchQuery(
data,
this._defaultParamsProvider.getParams(),
this._georouterCreator,
this.defaultParamsProvider.getParams(),
this.georouterCreator,
this.timezoneFinder,
),
);
return Promise.resolve({

View File

@ -1,8 +0,0 @@
import { Injectable } from '@nestjs/common';
import { MatcherRepository } from '../../../database/src/domain/matcher-repository';
import { Ad } from '../../domain/entities/ecosystem/ad';
@Injectable()
export class AdRepository extends MatcherRepository<Ad> {
protected _model = 'ad';
}

View File

@ -1,27 +1,16 @@
import { Injectable } from '@nestjs/common';
import { IGeodesic } from '../../domain/interfaces/geodesic.interface';
import { Geodesic, GeodesicClass } from 'geographiclib-geodesic';
import { Geodesic } from '../../../geography/adapters/secondaries/geodesic';
import { IGeodesic } from '../../../geography/domain/interfaces/geodesic.interface';
@Injectable()
export class MatcherGeodesic implements IGeodesic {
private geod: GeodesicClass;
constructor() {
this.geod = Geodesic.WGS84;
}
constructor(private readonly geodesic: Geodesic) {}
inverse = (
lon1: number,
lat1: number,
lon2: number,
lat2: number,
): { azimuth: number; distance: number } => {
const { azi2: azimuth, s12: distance } = this.geod.Inverse(
lat1,
lon1,
lat2,
lon2,
);
return { azimuth, distance };
};
): { azimuth: number; distance: number } =>
this.geodesic.inverse(lon1, lat1, lon2, lat2);
}

View File

@ -5,7 +5,7 @@ import { Path } from '../../domain/types/path.type';
import { Injectable } from '@nestjs/common';
import { catchError, lastValueFrom, map } from 'rxjs';
import { AxiosError, AxiosResponse } from 'axios';
import { IGeodesic } from '../../domain/interfaces/geodesic.interface';
import { IGeodesic } from '../../../geography/domain/interfaces/geodesic.interface';
import { NamedRoute } from '../../domain/entities/ecosystem/named-route';
import { Route } from '../../domain/entities/ecosystem/route';
import { SpacetimePoint } from '../../domain/entities/ecosystem/spacetime-point';

View File

@ -0,0 +1,11 @@
import { Injectable } from '@nestjs/common';
import { GeoTimezoneFinder } from '../../../geography/adapters/secondaries/geo-timezone-finder';
import { IFindTimezone } from '../../../geography/domain/interfaces/timezone-finder.interface';
@Injectable()
export class TimezoneFinder implements IFindTimezone {
constructor(private readonly geoTimezoneFinder: GeoTimezoneFinder) {}
timezones = (lon: number, lat: number): Array<string> =>
this.geoTimezoneFinder.timezones(lon, lat);
}

View File

@ -5,7 +5,6 @@ import {
import { IRequestGeography } from '../../interfaces/geography-request.interface';
import { PointType } from '../../types/geography.enum';
import { Point } from '../../types/point.type';
import { find } from 'geo-tz';
import { Route } from './route';
import { Role } from '../../types/role.enum';
import { IGeorouter } from '../../interfaces/georouter.interface';
@ -14,6 +13,8 @@ import { Actor } from './actor';
import { Person } from './person';
import { Step } from '../../types/step.enum';
import { Path } from '../../types/path.type';
import { IFindTimezone } from '../../../../geography/domain/interfaces/timezone-finder.interface';
import { Timezoner } from './timezoner';
export class Geography {
private geographyRequest: IRequestGeography;
@ -24,10 +25,11 @@ export class Geography {
timezones: Array<string>;
driverRoute: Route;
passengerRoute: Route;
timezoneFinder: IFindTimezone;
constructor(
geographyRequest: IRequestGeography,
defaultTimezone: string,
timezoner: Timezoner,
person: Person,
) {
this.geographyRequest = geographyRequest;
@ -35,7 +37,8 @@ export class Geography {
this.points = [];
this.originType = undefined;
this.destinationType = undefined;
this.timezones = [defaultTimezone];
this.timezones = [timezoner.timezone];
this.timezoneFinder = timezoner.finder;
}
init = (): void => {
@ -147,7 +150,7 @@ export class Geography {
};
private setTimezones = (): void => {
this.timezones = find(
this.timezones = this.timezoneFinder.timezones(
this.geographyRequest.waypoints[0].lat,
this.geographyRequest.waypoints[0].lon,
);

View File

@ -1,4 +1,4 @@
import { IGeodesic } from '../../interfaces/geodesic.interface';
import { IGeodesic } from '../../../../geography/domain/interfaces/geodesic.interface';
import { Point } from '../../types/point.type';
import { SpacetimePoint } from './spacetime-point';
import { Waypoint } from './waypoint';

View File

@ -0,0 +1,6 @@
import { IFindTimezone } from '../../../../geography/domain/interfaces/timezone-finder.interface';
export type Timezoner = {
timezone: string;
finder: IFindTimezone;
};

View File

@ -5,7 +5,6 @@ import { CqrsModule } from '@nestjs/cqrs';
import { DatabaseModule } from '../database/database.module';
import { MatcherController } from './adapters/primaries/matcher.controller';
import { MatchProfile } from './mappers/match.profile';
import { AdRepository } from './adapters/secondaries/ad.repository';
import { MatchUseCase } from './domain/usecases/match.usecase';
import { Messager } from './adapters/secondaries/messager';
import { CacheModule } from '@nestjs/cache-manager';
@ -17,9 +16,13 @@ import { HttpModule } from '@nestjs/axios';
import { MatcherGeodesic } from './adapters/secondaries/geodesic';
import { Matcher } from './domain/entities/engine/matcher';
import { AlgorithmFactoryCreator } from './domain/entities/engine/factory/algorithm-factory-creator';
import { TimezoneFinder } from './adapters/secondaries/timezone-finder';
import { GeoTimezoneFinder } from '../geography/adapters/secondaries/geo-timezone-finder';
import { GeographyModule } from '../geography/geography.module';
@Module({
imports: [
GeographyModule,
DatabaseModule,
CqrsModule,
HttpModule,
@ -53,14 +56,15 @@ import { AlgorithmFactoryCreator } from './domain/entities/engine/factory/algori
controllers: [MatcherController],
providers: [
MatchProfile,
AdRepository,
Messager,
DefaultParamsProvider,
MatchUseCase,
GeorouterCreator,
MatcherGeodesic,
TimezoneFinder,
Matcher,
AlgorithmFactoryCreator,
GeoTimezoneFinder,
],
exports: [],
})

View File

@ -8,11 +8,12 @@ import { Time } from '../domain/entities/ecosystem/time';
import { IDefaultParams } from '../domain/types/default-params.type';
import { IGeorouter } from '../domain/interfaces/georouter.interface';
import { ICreateGeorouter } from '../domain/interfaces/georouter-creator.interface';
import { IFindTimezone } from '../../geography/domain/interfaces/timezone-finder.interface';
export class MatchQuery {
private readonly _matchRequest: MatchRequest;
private readonly _defaultParams: IDefaultParams;
private readonly _georouterCreator: ICreateGeorouter;
private readonly matchRequest: MatchRequest;
private readonly defaultParams: IDefaultParams;
private readonly georouterCreator: ICreateGeorouter;
person: Person;
roles: Array<Role>;
time: Time;
@ -21,83 +22,89 @@ export class MatchQuery {
requirement: Requirement;
algorithmSettings: AlgorithmSettings;
georouter: IGeorouter;
timezoneFinder: IFindTimezone;
constructor(
matchRequest: MatchRequest,
defaultParams: IDefaultParams,
georouterCreator: ICreateGeorouter,
timezoneFinder: IFindTimezone,
) {
this._matchRequest = matchRequest;
this._defaultParams = defaultParams;
this._georouterCreator = georouterCreator;
this._setPerson();
this._setRoles();
this._setTime();
this._setGeography();
this._setRequirement();
this._setAlgorithmSettings();
this._setExclusions();
this.matchRequest = matchRequest;
this.defaultParams = defaultParams;
this.georouterCreator = georouterCreator;
this.timezoneFinder = timezoneFinder;
this.setPerson();
this.setRoles();
this.setTime();
this.setGeography();
this.setRequirement();
this.setAlgorithmSettings();
this.setExclusions();
}
createRoutes = (): void => {
this.geography.createRoutes(this.roles, this.algorithmSettings.georouter);
};
_setPerson = (): void => {
private setPerson = (): void => {
this.person = new Person(
this._matchRequest,
this._defaultParams.DEFAULT_IDENTIFIER,
this._defaultParams.MARGIN_DURATION,
this.matchRequest,
this.defaultParams.DEFAULT_IDENTIFIER,
this.defaultParams.MARGIN_DURATION,
);
this.person.init();
};
_setRoles = (): void => {
private setRoles = (): void => {
this.roles = [];
if (this._matchRequest.driver) this.roles.push(Role.DRIVER);
if (this._matchRequest.passenger) this.roles.push(Role.PASSENGER);
if (this.matchRequest.driver) this.roles.push(Role.DRIVER);
if (this.matchRequest.passenger) this.roles.push(Role.PASSENGER);
if (this.roles.length == 0) this.roles.push(Role.PASSENGER);
};
_setTime = (): void => {
private setTime = (): void => {
this.time = new Time(
this._matchRequest,
this._defaultParams.MARGIN_DURATION,
this._defaultParams.VALIDITY_DURATION,
this.matchRequest,
this.defaultParams.MARGIN_DURATION,
this.defaultParams.VALIDITY_DURATION,
);
this.time.init();
};
_setGeography = (): void => {
private setGeography = (): void => {
this.geography = new Geography(
this._matchRequest,
this._defaultParams.DEFAULT_TIMEZONE,
this.matchRequest,
{
timezone: this.defaultParams.DEFAULT_TIMEZONE,
finder: this.timezoneFinder,
},
this.person,
);
this.geography.init();
};
_setRequirement = (): void => {
private setRequirement = (): void => {
this.requirement = new Requirement(
this._matchRequest,
this._defaultParams.DEFAULT_SEATS,
this.matchRequest,
this.defaultParams.DEFAULT_SEATS,
);
};
_setAlgorithmSettings = (): void => {
private setAlgorithmSettings = (): void => {
this.algorithmSettings = new AlgorithmSettings(
this._matchRequest,
this._defaultParams.DEFAULT_ALGORITHM_SETTINGS,
this.matchRequest,
this.defaultParams.DEFAULT_ALGORITHM_SETTINGS,
this.time.frequency,
this._georouterCreator,
this.georouterCreator,
);
};
_setExclusions = (): void => {
private setExclusions = (): void => {
this.exclusions = [];
if (this._matchRequest.identifier)
this.exclusions.push(this._matchRequest.identifier);
if (this._matchRequest.exclusions)
this.exclusions.push(...this._matchRequest.exclusions);
if (this.matchRequest.identifier)
this.exclusions.push(this.matchRequest.identifier);
if (this.matchRequest.exclusions)
this.exclusions.push(...this.matchRequest.exclusions);
};
}

View File

@ -0,0 +1,35 @@
import { Test, TestingModule } from '@nestjs/testing';
import { TimezoneFinder } from '../../../../adapters/secondaries/timezone-finder';
import { GeoTimezoneFinder } from '../../../../../geography/adapters/secondaries/geo-timezone-finder';
const mockGeoTimezoneFinder = {
timezones: jest.fn().mockImplementation(() => ['Europe/Paris']),
};
describe('Geo TZ Finder', () => {
let timezoneFinder: TimezoneFinder;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [],
providers: [
TimezoneFinder,
{
provide: GeoTimezoneFinder,
useValue: mockGeoTimezoneFinder,
},
],
}).compile();
timezoneFinder = module.get<TimezoneFinder>(TimezoneFinder);
});
it('should be defined', () => {
expect(timezoneFinder).toBeDefined();
});
it('should get timezone for Nancy(France) as Europe/Paris', () => {
const timezones = timezoneFinder.timezones(6.179373, 48.687913);
expect(timezones.length).toBe(1);
expect(timezones[0]).toBe('Europe/Paris');
});
});

View File

@ -1,14 +1,38 @@
import { Test, TestingModule } from '@nestjs/testing';
import { MatcherGeodesic } from '../../../../adapters/secondaries/geodesic';
import { Geodesic } from '../../../../../geography/adapters/secondaries/geodesic';
const mockGeodesic = {
inverse: jest.fn().mockImplementation(() => ({
azimuth: 45,
distance: 50000,
})),
};
describe('Matcher geodesic', () => {
let matcherGeodesic: MatcherGeodesic;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [],
providers: [
MatcherGeodesic,
{
provide: Geodesic,
useValue: mockGeodesic,
},
],
}).compile();
matcherGeodesic = module.get<MatcherGeodesic>(MatcherGeodesic);
});
it('should be defined', () => {
const geodesic: MatcherGeodesic = new MatcherGeodesic();
expect(geodesic).toBeDefined();
expect(matcherGeodesic).toBeDefined();
});
it('should get inverse values', () => {
const geodesic: MatcherGeodesic = new MatcherGeodesic();
const inv = geodesic.inverse(0, 0, 1, 1);
const inv = matcherGeodesic.inverse(0, 0, 1, 1);
expect(Math.round(inv.azimuth)).toBe(45);
expect(Math.round(inv.distance)).toBe(156900);
expect(Math.round(inv.distance)).toBe(50000);
});
});

View File

@ -6,7 +6,7 @@ import {
import { Role } from '../../../../domain/types/role.enum';
import { NamedRoute } from '../../../../domain/entities/ecosystem/named-route';
import { Route } from '../../../../domain/entities/ecosystem/route';
import { IGeodesic } from '../../../../domain/interfaces/geodesic.interface';
import { IGeodesic } from '../../../../../geography/domain/interfaces/geodesic.interface';
import { PointType } from '../../../../domain/types/geography.enum';
const person: Person = new Person(
@ -65,6 +65,10 @@ const mockGeorouter = {
}),
};
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
describe('Geography entity', () => {
it('should be defined', () => {
const geography = new Geography(
@ -80,7 +84,10 @@ describe('Geography entity', () => {
},
],
},
'Europe/Paris',
{
timezone: 'Europe/Paris',
finder: mockTimezoneFinder,
},
person,
);
expect(geography).toBeDefined();
@ -103,7 +110,10 @@ describe('Geography entity', () => {
},
],
},
'Europe/Paris',
{
timezone: 'Europe/Paris',
finder: mockTimezoneFinder,
},
person,
);
geography.init();
@ -115,7 +125,10 @@ describe('Geography entity', () => {
{
waypoints: [],
},
'Europe/Paris',
{
timezone: 'Europe/Paris',
finder: mockTimezoneFinder,
},
person,
);
expect(() => geography.init()).toThrow();
@ -130,7 +143,10 @@ describe('Geography entity', () => {
},
],
},
'Europe/Paris',
{
timezone: 'Europe/Paris',
finder: mockTimezoneFinder,
},
person,
);
expect(() => geography.init()).toThrow();
@ -149,7 +165,10 @@ describe('Geography entity', () => {
},
],
},
'Europe/Paris',
{
timezone: 'Europe/Paris',
finder: mockTimezoneFinder,
},
person,
);
expect(() => geography.init()).toThrow();
@ -168,7 +187,10 @@ describe('Geography entity', () => {
},
],
},
'Europe/Paris',
{
timezone: 'Europe/Paris',
finder: mockTimezoneFinder,
},
person,
);
expect(() => geography.init()).toThrow();
@ -190,7 +212,10 @@ describe('Geography entity', () => {
},
],
},
'Europe/Paris',
{
timezone: 'Europe/Paris',
finder: mockTimezoneFinder,
},
person,
);
geography.init();
@ -220,7 +245,10 @@ describe('Geography entity', () => {
},
],
},
'Europe/Paris',
{
timezone: 'Europe/Paris',
finder: mockTimezoneFinder,
},
person,
);
geography.init();
@ -246,7 +274,10 @@ describe('Geography entity', () => {
},
],
},
'Europe/Paris',
{
timezone: 'Europe/Paris',
finder: mockTimezoneFinder,
},
person,
);
geography.init();
@ -269,7 +300,10 @@ describe('Geography entity', () => {
},
],
},
'Europe/Paris',
{
timezone: 'Europe/Paris',
finder: mockTimezoneFinder,
},
person,
);
geography.init();

View File

@ -9,6 +9,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(),
};
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900,
@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
);
describe('AlgorithmFactoryCreator', () => {

View File

@ -11,6 +11,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(),
};
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900,
@ -48,6 +52,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
);
class FakeSelector extends Selector {

View File

@ -9,6 +9,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(),
};
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900,
@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
);
describe('ClassicAlgorithmFactory', () => {

View File

@ -9,6 +9,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(),
};
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900,
@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
);
describe('ClassicGeoFilter', () => {

View File

@ -9,6 +9,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(),
};
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900,
@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
);
describe('ClassicTimeFilter', () => {

View File

@ -9,6 +9,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(),
};
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900,
@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
);
describe('ClassicWaypointCompleter', () => {

View File

@ -9,6 +9,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(),
};
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900,
@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
);
describe('ClassicSelector', () => {

View File

@ -9,6 +9,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(),
};
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900,
@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
);
class FakeCompleter extends Completer {

View File

@ -9,6 +9,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(),
};
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900,
@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
);
class FakeFilter extends Filter {

View File

@ -9,6 +9,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(),
};
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900,
@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
);
describe('JourneyCompleter', () => {

View File

@ -21,6 +21,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(),
};
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900,
@ -58,6 +62,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
);
describe('Matcher', () => {

View File

@ -9,6 +9,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(),
};
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900,
@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
);
class FakeProcessor extends Processor {

View File

@ -9,6 +9,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(),
};
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900,
@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
);
describe('RouteCompleter', () => {

View File

@ -9,6 +9,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(),
};
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900,
@ -46,6 +50,7 @@ const matchQuery: MatchQuery = new MatchQuery(
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
);
class FakeSelector extends Selector {

View File

@ -34,6 +34,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(),
};
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
const defaultParams: IDefaultParams = {
DEFAULT_IDENTIFIER: 0,
MARGIN_DURATION: 900,
@ -97,7 +101,12 @@ describe('MatchUseCase', () => {
describe('execute', () => {
it('should return matches', async () => {
const matches = await matchUseCase.execute(
new MatchQuery(matchRequest, defaultParams, mockGeorouterCreator),
new MatchQuery(
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
),
);
expect(matches.total).toBe(3);
});
@ -105,7 +114,12 @@ describe('MatchUseCase', () => {
it('should throw an exception when error occurs', async () => {
await expect(
matchUseCase.execute(
new MatchQuery(matchRequest, defaultParams, mockGeorouterCreator),
new MatchQuery(
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
),
),
).rejects.toBeInstanceOf(MatcherException);
});

View File

@ -30,6 +30,10 @@ const mockGeorouterCreator = {
create: jest.fn().mockImplementation(),
};
const mockTimezoneFinder = {
timezones: jest.fn().mockImplementation(),
};
describe('Match query', () => {
it('should be defined', () => {
const matchRequest: MatchRequest = new MatchRequest();
@ -48,6 +52,7 @@ describe('Match query', () => {
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
);
expect(matchQuery).toBeDefined();
});
@ -71,6 +76,7 @@ describe('Match query', () => {
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
);
expect(matchQuery.exclusions.length).toBe(4);
});
@ -93,6 +99,7 @@ describe('Match query', () => {
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
);
expect(matchQuery.roles).toEqual([Role.DRIVER]);
});
@ -115,6 +122,7 @@ describe('Match query', () => {
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
);
expect(matchQuery.roles).toEqual([Role.PASSENGER]);
});
@ -138,6 +146,7 @@ describe('Match query', () => {
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
);
expect(matchQuery.roles.length).toBe(2);
expect(matchQuery.roles).toContain(Role.PASSENGER);
@ -163,6 +172,7 @@ describe('Match query', () => {
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
);
expect(matchQuery.requirement.seatsDriver).toBe(1);
expect(matchQuery.requirement.seatsPassenger).toBe(2);
@ -194,6 +204,7 @@ describe('Match query', () => {
matchRequest,
defaultParams,
mockGeorouterCreator,
mockTimezoneFinder,
);
expect(matchQuery.algorithmSettings.algorithmType).toBe(
AlgorithmType.CLASSIC,