From 96c30cb1cc0ac69c36fa9f244d56aa144a1382e6 Mon Sep 17 00:00:00 2001 From: Romain Thouvenin Date: Wed, 13 Mar 2024 17:54:34 +0100 Subject: [PATCH] Remove most of the geography module and delegate it to external gRPC microservice --- src/app.constants.ts | 2 + src/app.module.ts | 3 +- src/config/producer-services.config.ts | 8 + src/modules/ad/ad.di-tokens.ts | 1 + src/modules/ad/ad.module.ts | 37 +- .../commands/create-ad/create-ad.service.ts | 8 +- .../ports/georouter-provider.port.ts | 31 ++ .../application/ports/route-provider.port.ts | 19 - .../match/completer/route.completer.ts | 31 +- .../application/queries/match/match.query.ts | 10 +- .../ad/infrastructure/georouter-provider.ts | 35 ++ src/modules/ad/infrastructure/georouter.proto | 39 ++ .../ad/infrastructure/route-provider.ts | 28 - .../grpc-controllers/match.grpc-controller.ts | 4 +- .../unit/core/algorithm.abstract.spec.ts | 9 +- .../tests/unit/core/create-ad.service.spec.ts | 7 +- .../tests/unit/core/journey.completer.spec.ts | 20 +- .../ad/tests/unit/core/journey.filter.spec.ts | 6 +- .../unit/core/match.query-handler.spec.ts | 14 +- .../ad/tests/unit/core/match.query.spec.ts | 35 +- .../core/passenger-oriented-algorithm.spec.ts | 3 +- ...er-oriented-carpool-path-completer.spec.ts | 6 +- .../passenger-oriented-geo-filter.spec.ts | 6 +- .../core/passenger-oriented-selector.spec.ts | 6 +- .../tests/unit/core/route.completer.spec.ts | 33 +- src/modules/ad/tests/unit/georouter.mock.ts | 16 + .../unit/infrastructure/ad.repository.spec.ts | 9 +- .../infrastructure/route-provider.spec.ts | 110 ---- .../interface/match.grpc.controller.spec.ts | 9 +- .../core/application/ports/geodesic.port.ts | 13 - .../core/application/ports/georouter.port.ts | 6 - .../ports/get-route-controller.port.ts | 6 - .../get-route/get-route.query-handler.ts | 18 - .../queries/get-route/get-route.query.ts | 21 - .../types/georouter-settings.type.ts | 5 - .../geography/core/domain/route.entity.ts | 33 -- .../geography/core/domain/route.errors.ts | 21 - .../geography/core/domain/route.types.ts | 23 - .../value-objects/point.value-object.ts | 31 -- .../domain/value-objects/step.value-object.ts | 46 -- src/modules/geography/geography.constants.ts | 15 - src/modules/geography/geography.di-tokens.ts | 2 - src/modules/geography/geography.module.ts | 31 +- .../geography/infrastructure/geodesic.ts | 59 -- .../infrastructure/graphhopper-georouter.ts | 342 ------------ .../controllers/dtos/get-route.request.dto.ts | 5 - .../controllers/get-basic-route.controller.ts | 23 - .../get-detailed-route.controller.ts | 27 - .../interface/dtos/route.response.dto.ts | 11 - src/modules/geography/route.mapper.ts | 28 - .../unit/core/get-route.query-handler.spec.ts | 61 --- .../unit/core/point.value-object.spec.ts | 41 -- .../tests/unit/core/route.entity.spec.ts | 70 --- .../tests/unit/core/step.value-object.spec.ts | 76 --- .../unit/infrastructure/geodesic.spec.ts | 36 -- .../graphhopper-georouter.spec.ts | 508 ------------------ .../get-basic-route.controller.spec.ts | 63 --- .../get-detailed-route.controller.spec.ts | 63 --- .../geography/tests/unit/route.mapper.spec.ts | 45 -- 59 files changed, 237 insertions(+), 2037 deletions(-) create mode 100644 src/config/producer-services.config.ts create mode 100644 src/modules/ad/core/application/ports/georouter-provider.port.ts delete mode 100644 src/modules/ad/core/application/ports/route-provider.port.ts create mode 100644 src/modules/ad/infrastructure/georouter-provider.ts create mode 100644 src/modules/ad/infrastructure/georouter.proto delete mode 100644 src/modules/ad/infrastructure/route-provider.ts create mode 100644 src/modules/ad/tests/unit/georouter.mock.ts delete mode 100644 src/modules/ad/tests/unit/infrastructure/route-provider.spec.ts delete mode 100644 src/modules/geography/core/application/ports/geodesic.port.ts delete mode 100644 src/modules/geography/core/application/ports/georouter.port.ts delete mode 100644 src/modules/geography/core/application/ports/get-route-controller.port.ts delete mode 100644 src/modules/geography/core/application/queries/get-route/get-route.query-handler.ts delete mode 100644 src/modules/geography/core/application/queries/get-route/get-route.query.ts delete mode 100644 src/modules/geography/core/application/types/georouter-settings.type.ts delete mode 100644 src/modules/geography/core/domain/route.entity.ts delete mode 100644 src/modules/geography/core/domain/route.errors.ts delete mode 100644 src/modules/geography/core/domain/value-objects/point.value-object.ts delete mode 100644 src/modules/geography/core/domain/value-objects/step.value-object.ts delete mode 100644 src/modules/geography/geography.constants.ts delete mode 100644 src/modules/geography/infrastructure/geodesic.ts delete mode 100644 src/modules/geography/infrastructure/graphhopper-georouter.ts delete mode 100644 src/modules/geography/interface/controllers/dtos/get-route.request.dto.ts delete mode 100644 src/modules/geography/interface/controllers/get-basic-route.controller.ts delete mode 100644 src/modules/geography/interface/controllers/get-detailed-route.controller.ts delete mode 100644 src/modules/geography/interface/dtos/route.response.dto.ts delete mode 100644 src/modules/geography/route.mapper.ts delete mode 100644 src/modules/geography/tests/unit/core/get-route.query-handler.spec.ts delete mode 100644 src/modules/geography/tests/unit/core/point.value-object.spec.ts delete mode 100644 src/modules/geography/tests/unit/core/route.entity.spec.ts delete mode 100644 src/modules/geography/tests/unit/core/step.value-object.spec.ts delete mode 100644 src/modules/geography/tests/unit/infrastructure/geodesic.spec.ts delete mode 100644 src/modules/geography/tests/unit/infrastructure/graphhopper-georouter.spec.ts delete mode 100644 src/modules/geography/tests/unit/interface/get-basic-route.controller.spec.ts delete mode 100644 src/modules/geography/tests/unit/interface/get-detailed-route.controller.spec.ts delete mode 100644 src/modules/geography/tests/unit/route.mapper.spec.ts diff --git a/src/app.constants.ts b/src/app.constants.ts index 79e219c..990204b 100644 --- a/src/app.constants.ts +++ b/src/app.constants.ts @@ -3,6 +3,8 @@ export const SERVICE_NAME = 'matcher'; // grpc export const GRPC_PACKAGE_NAME = 'matcher'; +export const GRPC_GEOGRAPHY_PACKAGE_NAME = 'geocoder'; +export const GRPC_GEOROUTER_SERVICE_NAME = 'GeorouterService'; // messaging output export const MATCHER_AD_CREATED_ROUTING_KEY = 'matcher-ad.created'; diff --git a/src/app.module.ts b/src/app.module.ts index eeb4865..459700b 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -10,6 +10,7 @@ import { MESSAGE_PUBLISHER } from '@modules/messager/messager.di-tokens'; import { HealthModuleOptions } from '@mobicoop/health-module/dist/core/domain/types/health.types'; import { MessagePublisherPort } from '@mobicoop/ddd-library'; import { GeographyModule } from '@modules/geography/geography.module'; +import producerServicesConfig from './config/producer-services.config'; import { HEALTH_AD_REPOSITORY, HEALTH_CRITICAL_LOGGING_KEY, @@ -18,7 +19,7 @@ import { @Module({ imports: [ - ConfigModule.forRoot({ isGlobal: true }), + ConfigModule.forRoot({ isGlobal: true, load: [producerServicesConfig] }), EventEmitterModule.forRoot(), RequestContextModule, HealthModule.forRootAsync({ diff --git a/src/config/producer-services.config.ts b/src/config/producer-services.config.ts new file mode 100644 index 0000000..9867503 --- /dev/null +++ b/src/config/producer-services.config.ts @@ -0,0 +1,8 @@ +import { registerAs } from '@nestjs/config'; + +export default registerAs('producerServices', () => ({ + geographyUrl: process.env.GEOGRAPHY_SERVICE_URL ?? 'v3-geocoder-api', + geographyPort: process.env.GEOGRAPHY_SERVICE_PORT + ? parseInt(process.env.GEOGRAPHY_SERVICE_PORT, 10) + : 5007, +})); diff --git a/src/modules/ad/ad.di-tokens.ts b/src/modules/ad/ad.di-tokens.ts index 4943a4d..bc6dfb0 100644 --- a/src/modules/ad/ad.di-tokens.ts +++ b/src/modules/ad/ad.di-tokens.ts @@ -18,3 +18,4 @@ export const OUTPUT_DATETIME_TRANSFORMER = Symbol( export const AD_CONFIGURATION_REPOSITORY = Symbol( 'AD_CONFIGURATION_REPOSITORY', ); +export const GEOGRAPHY_PACKAGE = Symbol('GEOGRAPHY_PACKAGE'); diff --git a/src/modules/ad/ad.module.ts b/src/modules/ad/ad.module.ts index c1a6b37..5de16d4 100644 --- a/src/modules/ad/ad.module.ts +++ b/src/modules/ad/ad.module.ts @@ -5,14 +5,13 @@ import { AD_REPOSITORY, AD_DIRECTION_ENCODER, AD_ROUTE_PROVIDER, - AD_GET_BASIC_ROUTE_CONTROLLER, TIMEZONE_FINDER, TIME_CONVERTER, INPUT_DATETIME_TRANSFORMER, - AD_GET_DETAILED_ROUTE_CONTROLLER, OUTPUT_DATETIME_TRANSFORMER, MATCHING_REPOSITORY, AD_CONFIGURATION_REPOSITORY, + GEOGRAPHY_PACKAGE, } from './ad.di-tokens'; import { MessageBrokerPublisher } from '@mobicoop/message-broker-module'; import { AdRepository } from './infrastructure/ad.repository'; @@ -20,8 +19,6 @@ import { PrismaService } from './infrastructure/prisma.service'; import { AdMapper } from './ad.mapper'; import { AdCreatedMessageHandler } from './interface/message-handlers/ad-created.message-handler'; import { PostgresDirectionEncoder } from '@modules/geography/infrastructure/postgres-direction-encoder'; -import { GetBasicRouteController } from '@modules/geography/interface/controllers/get-basic-route.controller'; -import { RouteProvider } from './infrastructure/route-provider'; import { GeographyModule } from '@modules/geography/geography.module'; import { CreateAdService } from './core/application/commands/create-ad/create-ad.service'; import { MatchGrpcController } from './interface/grpc-controllers/match.grpc-controller'; @@ -29,7 +26,6 @@ import { MatchQueryHandler } from './core/application/queries/match/match.query- import { TimezoneFinder } from './infrastructure/timezone-finder'; import { TimeConverter } from './infrastructure/time-converter'; import { InputDateTimeTransformer } from './infrastructure/input-datetime-transformer'; -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'; @@ -44,9 +40,30 @@ import { } from '@songkeys/nestjs-redis'; import { ConfigurationRepository } from '@mobicoop/configuration-module'; import { PublishMessageWhenMatcherAdIsCreatedDomainEventHandler } from './core/application/event-handlers/publish-message-when-matcher-ad-is-created.domain-event-handler'; +import { GeorouterProvider } from './infrastructure/georouter-provider'; +import { ClientsModule, Transport } from '@nestjs/microservices'; +import { GRPC_GEOGRAPHY_PACKAGE_NAME } from '@src/app.constants'; +import { join } from 'path'; const imports = [ CqrsModule, + ClientsModule.registerAsync([ + { + name: GEOGRAPHY_PACKAGE, + imports: [ConfigModule], + inject: [ConfigService], + useFactory: async (configService: ConfigService) => ({ + transport: Transport.GRPC, + options: { + package: GRPC_GEOGRAPHY_PACKAGE_NAME, + protoPath: join(__dirname, '/infrastructure/georouter.proto'), + url: `${configService.get( + 'producerServices.geographyUrl', + )}:${configService.get('producerServices.geographyPort')}`, + }, + }), + }, + ]), CacheModule.registerAsync({ imports: [ConfigModule], useFactory: async (configService: ConfigService) => ({ @@ -122,15 +139,7 @@ const adapters: Provider[] = [ }, { provide: AD_ROUTE_PROVIDER, - useClass: RouteProvider, - }, - { - provide: AD_GET_BASIC_ROUTE_CONTROLLER, - useClass: GetBasicRouteController, - }, - { - provide: AD_GET_DETAILED_ROUTE_CONTROLLER, - useClass: GetDetailedRouteController, + useClass: GeorouterProvider, }, { provide: TIMEZONE_FINDER, diff --git a/src/modules/ad/core/application/commands/create-ad/create-ad.service.ts b/src/modules/ad/core/application/commands/create-ad/create-ad.service.ts index 6b061e9..ebe2031 100644 --- a/src/modules/ad/core/application/commands/create-ad/create-ad.service.ts +++ b/src/modules/ad/core/application/commands/create-ad/create-ad.service.ts @@ -14,7 +14,6 @@ import { MessagePublisherPort, } from '@mobicoop/ddd-library'; import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors'; -import { RouteProviderPort } from '../../ports/route-provider.port'; import { Role } from '@modules/ad/core/domain/ad.types'; import { Path, @@ -27,6 +26,7 @@ import { Point as PointValueObject } from '@modules/ad/core/domain/value-objects import { Point } from '@modules/geography/core/domain/route.types'; import { MatcherAdCreationFailedIntegrationEvent } from '../../events/matcher-ad-creation-failed.integration-event'; import { MATCHER_AD_CREATION_FAILED_ROUTING_KEY } from '@src/app.constants'; +import { GeorouterProviderPort } from '../../ports/georouter-provider.port'; @CommandHandler(CreateAdCommand) export class CreateAdService implements ICommandHandler { @@ -36,7 +36,7 @@ export class CreateAdService implements ICommandHandler { @Inject(AD_REPOSITORY) private readonly repository: AdRepositoryPort, @Inject(AD_ROUTE_PROVIDER) - private readonly routeProvider: RouteProviderPort, + private readonly routeProvider: GeorouterProviderPort, ) {} async execute(command: CreateAdCommand): Promise { @@ -69,7 +69,9 @@ export class CreateAdService implements ICommandHandler { typedRoutes = await Promise.all( pathCreator.getBasePaths().map(async (path: Path) => ({ type: path.type, - route: await this.routeProvider.getBasic(path.waypoints), + route: await this.routeProvider.getRoute({ + waypoints: path.waypoints, + }), })), ); } catch (e: any) { diff --git a/src/modules/ad/core/application/ports/georouter-provider.port.ts b/src/modules/ad/core/application/ports/georouter-provider.port.ts new file mode 100644 index 0000000..d43a510 --- /dev/null +++ b/src/modules/ad/core/application/ports/georouter-provider.port.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@nestjs/common'; + +export type Point = { + lon: number; + lat: number; +}; + +export type Step = Point & { + duration: number; + distance?: number; +}; + +export type RouteRequest = { + waypoints: Point[]; + detailsSettings?: { points: boolean; steps: boolean }; +}; + +export type RouteResponse = { + distance: number; + duration: number; + fwdAzimuth: number; + backAzimuth: number; + distanceAzimuth: number; + points: Point[]; + steps?: Step[]; +}; + +@Injectable() +export abstract class GeorouterProviderPort { + abstract getRoute(request: RouteRequest): Promise; +} diff --git a/src/modules/ad/core/application/ports/route-provider.port.ts b/src/modules/ad/core/application/ports/route-provider.port.ts deleted file mode 100644 index 2087fba..0000000 --- a/src/modules/ad/core/application/ports/route-provider.port.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Point } from '../types/point.type'; -import { Route } from '../types/route.type'; - -export interface RouteProviderPort { - /** - * Get a basic route : - * - simple points (coordinates only) - * - overall duration - * - overall distance - */ - getBasic(waypoints: Point[]): Promise; - /** - * Get a detailed route : - * - detailed points (coordinates and time / distance to reach the point) - * - overall duration - * - overall distance - */ - getDetailed(waypoints: Point[]): Promise; -} diff --git a/src/modules/ad/core/application/queries/match/completer/route.completer.ts b/src/modules/ad/core/application/queries/match/completer/route.completer.ts index 5a07e10..8bf28b8 100644 --- a/src/modules/ad/core/application/queries/match/completer/route.completer.ts +++ b/src/modules/ad/core/application/queries/match/completer/route.completer.ts @@ -3,6 +3,7 @@ import { Completer } from './completer.abstract'; import { MatchQuery } from '../match.query'; import { Step } from '../../../types/step.type'; import { CarpoolPathItem } from '@modules/ad/core/domain/value-objects/carpool-path-item.value-object'; +import { RouteResponse } from '../../../ports/georouter-provider.port'; export class RouteCompleter extends Completer { protected readonly type: RouteCompleterType; @@ -18,23 +19,20 @@ export class RouteCompleter extends Completer { candidates.map(async (candidate: CandidateEntity) => { switch (this.type) { case RouteCompleterType.BASIC: - const basicCandidateRoute = await this.query.routeProvider.getBasic( - (candidate.getProps().carpoolPath as CarpoolPathItem[]).map( - (carpoolPathItem: CarpoolPathItem) => carpoolPathItem, - ), - ); + const basicCandidateRoute = await this._getRoute(candidate, { + points: true, + steps: false, + }); candidate.setMetrics( basicCandidateRoute.distance, basicCandidateRoute.duration, ); break; case RouteCompleterType.DETAILED: - const detailedCandidateRoute = - await this.query.routeProvider.getDetailed( - (candidate.getProps().carpoolPath as CarpoolPathItem[]).map( - (carpoolPathItem: CarpoolPathItem) => carpoolPathItem, - ), - ); + const detailedCandidateRoute = await this._getRoute(candidate, { + points: true, + steps: true, + }); candidate.setSteps(detailedCandidateRoute.steps as Step[]); break; } @@ -43,6 +41,17 @@ export class RouteCompleter extends Completer { ); return candidates; }; + + _getRoute = async ( + candidate: CandidateEntity, + detailsSettings: { points: boolean; steps: boolean }, + ): Promise => + this.query.routeProvider.getRoute({ + waypoints: (candidate.getProps().carpoolPath as CarpoolPathItem[]).map( + (carpoolPathItem: CarpoolPathItem) => carpoolPathItem, + ), + detailsSettings: detailsSettings, + }); } export enum RouteCompleterType { diff --git a/src/modules/ad/core/application/queries/match/match.query.ts b/src/modules/ad/core/application/queries/match/match.query.ts index e9d1b6e..1958c45 100644 --- a/src/modules/ad/core/application/queries/match/match.query.ts +++ b/src/modules/ad/core/application/queries/match/match.query.ts @@ -4,7 +4,6 @@ import { Waypoint } from '../../types/waypoint.type'; import { Frequency, Role } from '@modules/ad/core/domain/ad.types'; import { MatchRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/match.request.dto'; import { DateTimeTransformerPort } from '../../ports/datetime-transformer.port'; -import { RouteProviderPort } from '../../ports/route-provider.port'; import { Path, PathCreator, @@ -13,6 +12,7 @@ import { } from '@modules/ad/core/domain/path-creator.service'; import { Point } from '@modules/ad/core/domain/value-objects/point.value-object'; import { Route } from '../../types/route.type'; +import { GeorouterProviderPort } from '../../ports/georouter-provider.port'; export class MatchQuery extends QueryBase { id?: string; @@ -40,10 +40,10 @@ export class MatchQuery extends QueryBase { passengerRoute?: Route; backAzimuth?: number; private readonly originWaypoint: Waypoint; - routeProvider: RouteProviderPort; + routeProvider: GeorouterProviderPort; // TODO: remove MatchRequestDto depency (here core domain depends on interface /!\) - constructor(props: MatchRequestDto, routeProvider: RouteProviderPort) { + constructor(props: MatchRequestDto, routeProvider: GeorouterProviderPort) { super(); this.id = props.id; this.driver = props.driver; @@ -208,7 +208,9 @@ export class MatchQuery extends QueryBase { await Promise.all( pathCreator.getBasePaths().map(async (path: Path) => ({ type: path.type, - route: await this.routeProvider.getBasic(path.waypoints), + route: await this.routeProvider.getRoute({ + waypoints: path.waypoints, + }), })), ) ).forEach((typedRoute: TypedRoute) => { diff --git a/src/modules/ad/infrastructure/georouter-provider.ts b/src/modules/ad/infrastructure/georouter-provider.ts new file mode 100644 index 0000000..4e68605 --- /dev/null +++ b/src/modules/ad/infrastructure/georouter-provider.ts @@ -0,0 +1,35 @@ +import { Observable, lastValueFrom } from 'rxjs'; +import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; +import { ClientGrpc } from '@nestjs/microservices'; +import { GRPC_GEOROUTER_SERVICE_NAME } from '@src/app.constants'; +import { + GeorouterProviderPort, + RouteRequest, + RouteResponse, +} from '../core/application/ports/georouter-provider.port'; +import { GEOGRAPHY_PACKAGE } from '../ad.di-tokens'; + +interface GeorouterService { + getRoute(request: RouteRequest): Observable; +} + +@Injectable() +export class GeorouterProvider implements GeorouterProviderPort, OnModuleInit { + private georouterService: GeorouterService; + + constructor(@Inject(GEOGRAPHY_PACKAGE) private readonly client: ClientGrpc) {} + + onModuleInit() { + this.georouterService = this.client.getService( + GRPC_GEOROUTER_SERVICE_NAME, + ); + } + + getRoute = async (request: RouteRequest): Promise => { + try { + return await lastValueFrom(this.georouterService.getRoute(request)); + } catch (error: any) { + throw error; + } + }; +} diff --git a/src/modules/ad/infrastructure/georouter.proto b/src/modules/ad/infrastructure/georouter.proto new file mode 100644 index 0000000..1b13653 --- /dev/null +++ b/src/modules/ad/infrastructure/georouter.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package geocoder; + +service GeorouterService { + rpc GetRoute(RouteRequest) returns (Route); +} + +message RouteRequest { + repeated Point waypoints = 1; + optional DetailsSettings detailsSettings = 2; +} + +message Point { + double lon = 1; + double lat = 2; +} + +message DetailsSettings { + bool points = 1; + bool steps = 2; +} + +message Route { + int32 distance = 1; + int32 duration = 2; + int32 fwdAzimuth = 3; + int32 backAzimuth = 4; + int32 distanceAzimuth = 5; + repeated Point points = 6; + repeated Step steps = 7; +} + +message Step { + double lon = 1; + double lat = 2; + int32 duration = 3; + int32 distance = 4; +} \ No newline at end of file diff --git a/src/modules/ad/infrastructure/route-provider.ts b/src/modules/ad/infrastructure/route-provider.ts deleted file mode 100644 index ada7160..0000000 --- a/src/modules/ad/infrastructure/route-provider.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { RouteProviderPort } from '../core/application/ports/route-provider.port'; -import { GetRouteControllerPort } from '@modules/geography/core/application/ports/get-route-controller.port'; -import { - AD_GET_BASIC_ROUTE_CONTROLLER, - AD_GET_DETAILED_ROUTE_CONTROLLER, -} from '../ad.di-tokens'; -import { Point, Route } from '@modules/geography/core/domain/route.types'; - -@Injectable() -export class RouteProvider implements RouteProviderPort { - constructor( - @Inject(AD_GET_BASIC_ROUTE_CONTROLLER) - private readonly getBasicRouteController: GetRouteControllerPort, - @Inject(AD_GET_DETAILED_ROUTE_CONTROLLER) - private readonly getDetailedRouteController: GetRouteControllerPort, - ) {} - - getBasic = async (waypoints: Point[]): Promise => - await this.getBasicRouteController.get({ - waypoints, - }); - - getDetailed = async (waypoints: Point[]): Promise => - await this.getDetailedRouteController.get({ - waypoints, - }); -} diff --git a/src/modules/ad/interface/grpc-controllers/match.grpc-controller.ts b/src/modules/ad/interface/grpc-controllers/match.grpc-controller.ts index a6bf5af..2ccbab6 100644 --- a/src/modules/ad/interface/grpc-controllers/match.grpc-controller.ts +++ b/src/modules/ad/interface/grpc-controllers/match.grpc-controller.ts @@ -8,10 +8,10 @@ import { MatchRequestDto } from './dtos/match.request.dto'; import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query'; import { MatchEntity } from '@modules/ad/core/domain/match.entity'; import { AD_ROUTE_PROVIDER } from '@modules/ad/ad.di-tokens'; -import { 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'; import { CacheInterceptor, CacheKey } from '@nestjs/cache-manager'; +import { GeorouterProviderPort } from '@modules/ad/core/application/ports/georouter-provider.port'; @UsePipes( new RpcValidationPipe({ @@ -24,7 +24,7 @@ export class MatchGrpcController { constructor( private readonly queryBus: QueryBus, @Inject(AD_ROUTE_PROVIDER) - private readonly routeProvider: RouteProviderPort, + private readonly routeProvider: GeorouterProviderPort, private readonly matchMapper: MatchMapper, ) {} diff --git a/src/modules/ad/tests/unit/core/algorithm.abstract.spec.ts b/src/modules/ad/tests/unit/core/algorithm.abstract.spec.ts index d3c4be4..629d50c 100644 --- a/src/modules/ad/tests/unit/core/algorithm.abstract.spec.ts +++ b/src/modules/ad/tests/unit/core/algorithm.abstract.spec.ts @@ -1,5 +1,4 @@ import { AdRepositoryPort } from '@modules/ad/core/application/ports/ad.repository.port'; -import { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port'; import { Algorithm, Selector, @@ -9,6 +8,7 @@ import { Waypoint } from '@modules/ad/core/application/types/waypoint.type'; import { Frequency, Role } from '@modules/ad/core/domain/ad.types'; import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity'; import { MatchEntity } from '@modules/ad/core/domain/match.entity'; +import { bareMockGeorouter } from '../georouter.mock'; const originWaypoint: Waypoint = { position: 0, @@ -29,11 +29,6 @@ const destinationWaypoint: Waypoint = { country: 'France', }; -const mockRouteProvider: RouteProviderPort = { - getBasic: jest.fn(), - getDetailed: jest.fn(), -}; - const matchQuery = new MatchQuery( { frequency: Frequency.PUNCTUAL, @@ -46,7 +41,7 @@ const matchQuery = new MatchQuery( ], waypoints: [originWaypoint, destinationWaypoint], }, - mockRouteProvider, + bareMockGeorouter, ); const mockAdRepository: AdRepositoryPort = { diff --git a/src/modules/ad/tests/unit/core/create-ad.service.spec.ts b/src/modules/ad/tests/unit/core/create-ad.service.spec.ts index c19beb0..9886287 100644 --- a/src/modules/ad/tests/unit/core/create-ad.service.spec.ts +++ b/src/modules/ad/tests/unit/core/create-ad.service.spec.ts @@ -11,8 +11,8 @@ import { CreateAdProps, Frequency } from '@modules/ad/core/domain/ad.types'; import { CreateAdService } from '@modules/ad/core/application/commands/create-ad/create-ad.service'; import { CreateAdCommand } from '@modules/ad/core/application/commands/create-ad/create-ad.command'; import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors'; -import { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port'; import { PointProps } from '@modules/ad/core/domain/value-objects/point.value-object'; +import { GeorouterProviderPort } from '@modules/ad/core/application/ports/georouter-provider.port'; const originWaypoint: PointProps = { lat: 48.689445, @@ -62,8 +62,8 @@ const mockAdRepository = { }), }; -const mockRouteProvider: RouteProviderPort = { - getBasic: jest +const mockRouteProvider: GeorouterProviderPort = { + getRoute: jest .fn() .mockImplementationOnce(() => { throw new Error(); @@ -97,7 +97,6 @@ const mockRouteProvider: RouteProviderPort = { }, ], })), - getDetailed: jest.fn(), }; const mockMessagePublisher = { diff --git a/src/modules/ad/tests/unit/core/journey.completer.spec.ts b/src/modules/ad/tests/unit/core/journey.completer.spec.ts index b084679..cf1e203 100644 --- a/src/modules/ad/tests/unit/core/journey.completer.spec.ts +++ b/src/modules/ad/tests/unit/core/journey.completer.spec.ts @@ -6,6 +6,7 @@ import { Frequency, Role } from '@modules/ad/core/domain/ad.types'; import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity'; import { Target } from '@modules/ad/core/domain/candidate.types'; import { Actor } from '@modules/ad/core/domain/value-objects/actor.value-object'; +import { simpleMockGeorouter } from '../georouter.mock'; const originWaypoint: Waypoint = { position: 0, @@ -42,24 +43,7 @@ const matchQuery = new MatchQuery( strict: false, waypoints: [originWaypoint, destinationWaypoint], }, - { - getBasic: jest.fn().mockImplementation(() => ({ - distance: 350101, - duration: 14422, - fwdAzimuth: 273, - backAzimuth: 93, - distanceAzimuth: 336544, - points: [], - })), - getDetailed: jest.fn().mockImplementation(() => ({ - distance: 350102, - duration: 14423, - fwdAzimuth: 273, - backAzimuth: 93, - distanceAzimuth: 336544, - points: [], - })), - }, + simpleMockGeorouter, ); const candidate: CandidateEntity = CandidateEntity.create({ diff --git a/src/modules/ad/tests/unit/core/journey.filter.spec.ts b/src/modules/ad/tests/unit/core/journey.filter.spec.ts index 239f0f4..0e53946 100644 --- a/src/modules/ad/tests/unit/core/journey.filter.spec.ts +++ b/src/modules/ad/tests/unit/core/journey.filter.spec.ts @@ -4,6 +4,7 @@ import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.type import { Waypoint } from '@modules/ad/core/application/types/waypoint.type'; import { Frequency, Role } from '@modules/ad/core/domain/ad.types'; import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity'; +import { bareMockGeorouter } from '../georouter.mock'; const originWaypoint: Waypoint = { position: 0, @@ -40,10 +41,7 @@ const matchQuery = new MatchQuery( strict: false, waypoints: [originWaypoint, destinationWaypoint], }, - { - getBasic: jest.fn(), - getDetailed: jest.fn(), - }, + bareMockGeorouter, ); const candidate: CandidateEntity = CandidateEntity.create({ diff --git a/src/modules/ad/tests/unit/core/match.query-handler.spec.ts b/src/modules/ad/tests/unit/core/match.query-handler.spec.ts index 3243c20..ee60fa3 100644 --- a/src/modules/ad/tests/unit/core/match.query-handler.spec.ts +++ b/src/modules/ad/tests/unit/core/match.query-handler.spec.ts @@ -19,7 +19,6 @@ import { } from '@modules/ad/ad.di-tokens'; import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.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, @@ -43,6 +42,7 @@ import { PAGINATION_CONFIG_PER_PAGE, } from '@modules/ad/match.constants'; import { Test, TestingModule } from '@nestjs/testing'; +import { simpleMockGeorouter } from '../georouter.mock'; const originWaypoint: Waypoint = { position: 0, @@ -351,17 +351,7 @@ const mockInputDateTimeTransformer: DateTimeTransformerPort = { time: jest.fn(), }; -const mockRouteProvider: RouteProviderPort = { - getBasic: jest.fn().mockImplementation(() => ({ - distance: 350101, - duration: 14422, - fwdAzimuth: 273, - backAzimuth: 93, - distanceAzimuth: 336544, - points: [], - })), - getDetailed: jest.fn(), -}; +const mockRouteProvider = simpleMockGeorouter; describe('Match Query Handler', () => { let matchQueryHandler: MatchQueryHandler; diff --git a/src/modules/ad/tests/unit/core/match.query.spec.ts b/src/modules/ad/tests/unit/core/match.query.spec.ts index 8ae8bda..3d5c0ce 100644 --- a/src/modules/ad/tests/unit/core/match.query.spec.ts +++ b/src/modules/ad/tests/unit/core/match.query.spec.ts @@ -1,9 +1,10 @@ import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.port'; -import { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port'; +import { GeorouterProviderPort } from '@modules/ad/core/application/ports/georouter-provider.port'; import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query'; import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.types'; import { Waypoint } from '@modules/ad/core/application/types/waypoint.type'; import { Frequency } from '@modules/ad/core/domain/ad.types'; +import { simpleMockGeorouter } from '../georouter.mock'; const originWaypoint: Waypoint = { position: 0, @@ -57,17 +58,10 @@ const mockInputDateTimeTransformer: DateTimeTransformerPort = { time: jest.fn().mockImplementation(() => '23:05'), }; -const mockRouteProvider: RouteProviderPort = { - getBasic: jest +const mockRouteProvider: GeorouterProviderPort = { + getRoute: jest .fn() - .mockImplementationOnce(() => ({ - distance: 350101, - duration: 14422, - fwdAzimuth: 273, - backAzimuth: 93, - distanceAzimuth: 336544, - points: [], - })) + .mockImplementationOnce(simpleMockGeorouter.getRoute) .mockImplementationOnce(() => ({ distance: 340102, duration: 13423, @@ -76,22 +70,8 @@ const mockRouteProvider: RouteProviderPort = { distanceAzimuth: 336544, points: [], })) - .mockImplementationOnce(() => ({ - distance: 350101, - duration: 14422, - fwdAzimuth: 273, - backAzimuth: 93, - distanceAzimuth: 336544, - points: [], - })) - .mockImplementationOnce(() => ({ - distance: 350101, - duration: 14422, - fwdAzimuth: 273, - backAzimuth: 93, - distanceAzimuth: 336544, - points: [], - })) + .mockImplementationOnce(simpleMockGeorouter.getRoute) + .mockImplementationOnce(simpleMockGeorouter.getRoute) .mockImplementationOnce(() => ({ distance: 340102, duration: 13423, @@ -103,7 +83,6 @@ const mockRouteProvider: RouteProviderPort = { .mockImplementationOnce(() => { throw new Error(); }), - getDetailed: jest.fn(), }; describe('Match Query', () => { diff --git a/src/modules/ad/tests/unit/core/passenger-oriented-algorithm.spec.ts b/src/modules/ad/tests/unit/core/passenger-oriented-algorithm.spec.ts index c363e1a..01895c3 100644 --- a/src/modules/ad/tests/unit/core/passenger-oriented-algorithm.spec.ts +++ b/src/modules/ad/tests/unit/core/passenger-oriented-algorithm.spec.ts @@ -42,11 +42,10 @@ const matchQuery = new MatchQuery( waypoints: [originWaypoint, destinationWaypoint], }, { - getBasic: jest.fn().mockImplementation(() => ({ + getRoute: jest.fn().mockImplementation(() => ({ duration: 6500, distance: 89745, })), - getDetailed: jest.fn(), }, ); diff --git a/src/modules/ad/tests/unit/core/passenger-oriented-carpool-path-completer.spec.ts b/src/modules/ad/tests/unit/core/passenger-oriented-carpool-path-completer.spec.ts index 524f982..805315d 100644 --- a/src/modules/ad/tests/unit/core/passenger-oriented-carpool-path-completer.spec.ts +++ b/src/modules/ad/tests/unit/core/passenger-oriented-carpool-path-completer.spec.ts @@ -4,6 +4,7 @@ import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.type import { Waypoint } from '@modules/ad/core/application/types/waypoint.type'; import { Frequency, Role } from '@modules/ad/core/domain/ad.types'; import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity'; +import { bareMockGeorouter } from '../georouter.mock'; const originWaypoint: Waypoint = { position: 0, @@ -40,10 +41,7 @@ const matchQuery = new MatchQuery( strict: false, waypoints: [originWaypoint, destinationWaypoint], }, - { - getBasic: jest.fn(), - getDetailed: jest.fn(), - }, + bareMockGeorouter, ); const candidates: CandidateEntity[] = [ diff --git a/src/modules/ad/tests/unit/core/passenger-oriented-geo-filter.spec.ts b/src/modules/ad/tests/unit/core/passenger-oriented-geo-filter.spec.ts index 6c8bf7b..1c1aa9a 100644 --- a/src/modules/ad/tests/unit/core/passenger-oriented-geo-filter.spec.ts +++ b/src/modules/ad/tests/unit/core/passenger-oriented-geo-filter.spec.ts @@ -4,6 +4,7 @@ import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.type import { Waypoint } from '@modules/ad/core/application/types/waypoint.type'; import { Frequency, Role } from '@modules/ad/core/domain/ad.types'; import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity'; +import { bareMockGeorouter } from '../georouter.mock'; const originWaypoint: Waypoint = { position: 0, @@ -40,10 +41,7 @@ const matchQuery = new MatchQuery( strict: false, waypoints: [originWaypoint, destinationWaypoint], }, - { - getBasic: jest.fn(), - getDetailed: jest.fn(), - }, + bareMockGeorouter, ); const candidate: CandidateEntity = CandidateEntity.create({ diff --git a/src/modules/ad/tests/unit/core/passenger-oriented-selector.spec.ts b/src/modules/ad/tests/unit/core/passenger-oriented-selector.spec.ts index 2b8635f..7cd83b8 100644 --- a/src/modules/ad/tests/unit/core/passenger-oriented-selector.spec.ts +++ b/src/modules/ad/tests/unit/core/passenger-oriented-selector.spec.ts @@ -5,6 +5,7 @@ import { AlgorithmType } from '@modules/ad/core/application/types/algorithm.type import { Waypoint } from '@modules/ad/core/application/types/waypoint.type'; import { Frequency } from '@modules/ad/core/domain/ad.types'; import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity'; +import { bareMockGeorouter } from '../georouter.mock'; const originWaypoint: Waypoint = { position: 0, @@ -47,10 +48,7 @@ const matchQuery = new MatchQuery( strict: false, waypoints: [originWaypoint, destinationWaypoint], }, - { - getBasic: jest.fn(), - getDetailed: jest.fn(), - }, + bareMockGeorouter, ); matchQuery.driverRoute = { distance: 150120, diff --git a/src/modules/ad/tests/unit/core/route.completer.spec.ts b/src/modules/ad/tests/unit/core/route.completer.spec.ts index 4a39362..6d3f433 100644 --- a/src/modules/ad/tests/unit/core/route.completer.spec.ts +++ b/src/modules/ad/tests/unit/core/route.completer.spec.ts @@ -1,3 +1,7 @@ +import { + RouteRequest, + RouteResponse, +} from '@modules/ad/core/application/ports/georouter-provider.port'; import { RouteCompleter, RouteCompleterType, @@ -9,6 +13,8 @@ import { Frequency, Role } from '@modules/ad/core/domain/ad.types'; import { CandidateEntity } from '@modules/ad/core/domain/candidate.entity'; import { Target } from '@modules/ad/core/domain/candidate.types'; import { Actor } from '@modules/ad/core/domain/value-objects/actor.value-object'; +import { Step } from '@modules/geography/core/domain/route.types'; +import { simpleMockGeorouter } from '../georouter.mock'; const originWaypoint: Waypoint = { position: 0, @@ -46,23 +52,16 @@ const matchQuery = new MatchQuery( waypoints: [originWaypoint, destinationWaypoint], }, { - getBasic: jest.fn().mockImplementation(() => ({ - distance: 350101, - duration: 14422, - fwdAzimuth: 273, - backAzimuth: 93, - distanceAzimuth: 336544, - points: [], - })), - getDetailed: jest.fn().mockImplementation(() => ({ - distance: 350101, - duration: 14422, - fwdAzimuth: 273, - backAzimuth: 93, - distanceAzimuth: 336544, - points: [], - steps: [jest.fn(), jest.fn(), jest.fn(), jest.fn()], - })), + getRoute: jest + .fn() + .mockImplementation(async (req: RouteRequest): Promise => { + const response = await simpleMockGeorouter.getRoute(req); + if (req.detailsSettings?.steps) { + const step: Step = { lon: 0, lat: 0, duration: 0 }; + response.steps = [step, step, step, step]; + } + return response; + }), }, ); diff --git a/src/modules/ad/tests/unit/georouter.mock.ts b/src/modules/ad/tests/unit/georouter.mock.ts new file mode 100644 index 0000000..33b942a --- /dev/null +++ b/src/modules/ad/tests/unit/georouter.mock.ts @@ -0,0 +1,16 @@ +import { GeorouterProviderPort } from '@modules/ad/core/application/ports/georouter-provider.port'; + +export const bareMockGeorouter: GeorouterProviderPort = { + getRoute: jest.fn(), +}; + +export const simpleMockGeorouter: GeorouterProviderPort = { + getRoute: jest.fn().mockImplementation(() => ({ + distance: 350101, + duration: 14422, + fwdAzimuth: 273, + backAzimuth: 93, + distanceAzimuth: 336544, + points: [], + })), +}; diff --git a/src/modules/ad/tests/unit/infrastructure/ad.repository.spec.ts b/src/modules/ad/tests/unit/infrastructure/ad.repository.spec.ts index 3c361e0..b0aca47 100644 --- a/src/modules/ad/tests/unit/infrastructure/ad.repository.spec.ts +++ b/src/modules/ad/tests/unit/infrastructure/ad.repository.spec.ts @@ -4,7 +4,6 @@ import { AD_ROUTE_PROVIDER, } from '@modules/ad/ad.di-tokens'; import { AdMapper } from '@modules/ad/ad.mapper'; -import { RouteProviderPort } from '@modules/ad/core/application/ports/route-provider.port'; import { AdEntity } from '@modules/ad/core/domain/ad.entity'; import { Frequency } from '@modules/ad/core/domain/ad.types'; import { AdRepository } from '@modules/ad/infrastructure/ad.repository'; @@ -12,6 +11,7 @@ import { PrismaService } from '@modules/ad/infrastructure/prisma.service'; import { DirectionEncoderPort } from '@modules/geography/core/application/ports/direction-encoder.port'; import { EventEmitterModule } from '@nestjs/event-emitter'; import { Test, TestingModule } from '@nestjs/testing'; +import { bareMockGeorouter } from '../georouter.mock'; const mockMessagePublisher = { publish: jest.fn().mockImplementation(), @@ -73,11 +73,6 @@ const mockDirectionEncoder: DirectionEncoderPort = { ]), }; -const mockRouteProvider: RouteProviderPort = { - getBasic: jest.fn(), - getDetailed: jest.fn(), -}; - const mockPrismaService = { $queryRawUnsafe: jest .fn() @@ -239,7 +234,7 @@ describe('Ad repository', () => { }, { provide: AD_ROUTE_PROVIDER, - useValue: mockRouteProvider, + useValue: bareMockGeorouter, }, { provide: AD_MESSAGE_PUBLISHER, diff --git a/src/modules/ad/tests/unit/infrastructure/route-provider.spec.ts b/src/modules/ad/tests/unit/infrastructure/route-provider.spec.ts deleted file mode 100644 index 9d2bf51..0000000 --- a/src/modules/ad/tests/unit/infrastructure/route-provider.spec.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { - AD_GET_BASIC_ROUTE_CONTROLLER, - AD_GET_DETAILED_ROUTE_CONTROLLER, -} from '@modules/ad/ad.di-tokens'; -import { Point } from '@modules/ad/core/application/types/point.type'; -import { Route } from '@modules/ad/core/application/types/route.type'; -import { RouteProvider } from '@modules/ad/infrastructure/route-provider'; -import { GetRouteControllerPort } from '@modules/geography/core/application/ports/get-route-controller.port'; -import { Test, TestingModule } from '@nestjs/testing'; - -const originPoint: Point = { - lat: 48.689445, - lon: 6.17651, -}; -const destinationPoint: Point = { - lat: 48.8566, - lon: 2.3522, -}; - -const mockGetBasicRouteController: GetRouteControllerPort = { - get: jest.fn().mockImplementationOnce(() => ({ - distance: 350101, - duration: 14422, - fwdAzimuth: 273, - backAzimuth: 93, - distanceAzimuth: 336544, - points: [ - { - lon: 6.1765102, - lat: 48.689445, - }, - { - lon: 4.984578, - lat: 48.725687, - }, - { - lon: 2.3522, - lat: 48.8566, - }, - ], - })), -}; - -const mockGetDetailedRouteController: GetRouteControllerPort = { - get: jest.fn().mockImplementationOnce(() => ({ - distance: 350102, - duration: 14423, - fwdAzimuth: 273, - backAzimuth: 93, - distanceAzimuth: 336544, - points: [ - { - lon: 6.1765102, - lat: 48.689445, - }, - { - lon: 4.984578, - lat: 48.725687, - }, - { - lon: 2.3522, - lat: 48.8566, - }, - ], - })), -}; - -describe('Route provider', () => { - let routeProvider: RouteProvider; - - beforeAll(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - RouteProvider, - { - provide: AD_GET_BASIC_ROUTE_CONTROLLER, - useValue: mockGetBasicRouteController, - }, - { - provide: AD_GET_DETAILED_ROUTE_CONTROLLER, - useValue: mockGetDetailedRouteController, - }, - ], - }).compile(); - - routeProvider = module.get(RouteProvider); - }); - - it('should be defined', () => { - expect(routeProvider).toBeDefined(); - }); - - it('should provide a basic route', async () => { - const route: Route = await routeProvider.getBasic([ - originPoint, - destinationPoint, - ]); - expect(route.distance).toBe(350101); - expect(route.duration).toBe(14422); - }); - - it('should provide a detailed route', async () => { - const route: Route = await routeProvider.getDetailed([ - originPoint, - destinationPoint, - ]); - expect(route.distance).toBe(350102); - expect(route.duration).toBe(14423); - }); -}); diff --git a/src/modules/ad/tests/unit/interface/match.grpc.controller.spec.ts b/src/modules/ad/tests/unit/interface/match.grpc.controller.spec.ts index cfafe0e..ecdd72c 100644 --- a/src/modules/ad/tests/unit/interface/match.grpc.controller.spec.ts +++ b/src/modules/ad/tests/unit/interface/match.grpc.controller.spec.ts @@ -1,6 +1,5 @@ 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'; @@ -17,6 +16,7 @@ import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { QueryBus } from '@nestjs/cqrs'; import { RpcException } from '@nestjs/microservices'; import { Test, TestingModule } from '@nestjs/testing'; +import { bareMockGeorouter } from '../georouter.mock'; const originWaypoint: WaypointDto = { position: 0, @@ -183,11 +183,6 @@ const mockQueryBus = { }), }; -const mockRouteProvider: RouteProviderPort = { - getBasic: jest.fn(), - getDetailed: jest.fn(), -}; - const mockMatchMapper = { toResponse: jest.fn().mockImplementation(() => ({ adId: '53a0bf71-4132-4f7b-a4cc-88c796b6bdf1', @@ -286,7 +281,7 @@ describe('Match Grpc Controller', () => { }, { provide: AD_ROUTE_PROVIDER, - useValue: mockRouteProvider, + useValue: bareMockGeorouter, }, { provide: MatchMapper, diff --git a/src/modules/geography/core/application/ports/geodesic.port.ts b/src/modules/geography/core/application/ports/geodesic.port.ts deleted file mode 100644 index 6da4a05..0000000 --- a/src/modules/geography/core/application/ports/geodesic.port.ts +++ /dev/null @@ -1,13 +0,0 @@ -export interface GeodesicPort { - inverse( - lon1: number, - lat1: number, - lon2: number, - lat2: number, - ): { - azimuth: number; - distance: number; - }; - distance(lon1: number, lat1: number, lon2: number, lat2: number): number; - azimuth(lon1: number, lat1: number, lon2: number, lat2: number): number; -} diff --git a/src/modules/geography/core/application/ports/georouter.port.ts b/src/modules/geography/core/application/ports/georouter.port.ts deleted file mode 100644 index fa78b58..0000000 --- a/src/modules/geography/core/application/ports/georouter.port.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Route, Point } from '../../domain/route.types'; -import { GeorouterSettings } from '../types/georouter-settings.type'; - -export interface GeorouterPort { - route(waypoints: Point[], settings: GeorouterSettings): Promise; -} diff --git a/src/modules/geography/core/application/ports/get-route-controller.port.ts b/src/modules/geography/core/application/ports/get-route-controller.port.ts deleted file mode 100644 index 0217143..0000000 --- a/src/modules/geography/core/application/ports/get-route-controller.port.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { GetRouteRequestDto } from '@modules/geography/interface/controllers/dtos/get-route.request.dto'; -import { RouteResponseDto } from '@modules/geography/interface/dtos/route.response.dto'; - -export interface GetRouteControllerPort { - get(data: GetRouteRequestDto): Promise; -} diff --git a/src/modules/geography/core/application/queries/get-route/get-route.query-handler.ts b/src/modules/geography/core/application/queries/get-route/get-route.query-handler.ts deleted file mode 100644 index 77d88ad..0000000 --- a/src/modules/geography/core/application/queries/get-route/get-route.query-handler.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; -import { GetRouteQuery } from './get-route.query'; -import { RouteEntity } from '@modules/geography/core/domain/route.entity'; -import { Inject } from '@nestjs/common'; -import { GEOROUTER } from '@modules/geography/geography.di-tokens'; -import { GeorouterPort } from '../../ports/georouter.port'; - -@QueryHandler(GetRouteQuery) -export class GetRouteQueryHandler implements IQueryHandler { - constructor(@Inject(GEOROUTER) private readonly georouter: GeorouterPort) {} - - execute = async (query: GetRouteQuery): Promise => - await RouteEntity.create({ - waypoints: query.waypoints, - georouter: this.georouter, - georouterSettings: query.georouterSettings, - }); -} diff --git a/src/modules/geography/core/application/queries/get-route/get-route.query.ts b/src/modules/geography/core/application/queries/get-route/get-route.query.ts deleted file mode 100644 index 56e33d6..0000000 --- a/src/modules/geography/core/application/queries/get-route/get-route.query.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { QueryBase } from '@mobicoop/ddd-library'; -import { GeorouterSettings } from '../../types/georouter-settings.type'; -import { Point } from '@modules/geography/core/domain/route.types'; - -export class GetRouteQuery extends QueryBase { - readonly waypoints: Point[]; - readonly georouterSettings: GeorouterSettings; - - constructor( - waypoints: Point[], - georouterSettings: GeorouterSettings = { - detailedDistance: false, - detailedDuration: false, - points: true, - }, - ) { - super(); - this.waypoints = waypoints; - this.georouterSettings = georouterSettings; - } -} diff --git a/src/modules/geography/core/application/types/georouter-settings.type.ts b/src/modules/geography/core/application/types/georouter-settings.type.ts deleted file mode 100644 index ae5b391..0000000 --- a/src/modules/geography/core/application/types/georouter-settings.type.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type GeorouterSettings = { - points: boolean; - detailedDuration: boolean; - detailedDistance: boolean; -}; diff --git a/src/modules/geography/core/domain/route.entity.ts b/src/modules/geography/core/domain/route.entity.ts deleted file mode 100644 index be0613d..0000000 --- a/src/modules/geography/core/domain/route.entity.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library'; -import { CreateRouteProps, RouteProps, Route } from './route.types'; -import { v4 } from 'uuid'; -import { RouteNotFoundException } from './route.errors'; - -export class RouteEntity extends AggregateRoot { - protected readonly _id: AggregateID; - - static create = async (create: CreateRouteProps): Promise => { - const route: Route = await create.georouter.route( - create.waypoints, - create.georouterSettings, - ); - if (!route) throw new RouteNotFoundException(); - const routeProps: RouteProps = { - distance: route.distance, - duration: route.duration, - fwdAzimuth: route.fwdAzimuth, - backAzimuth: route.backAzimuth, - distanceAzimuth: route.distanceAzimuth, - points: route.points, - steps: route.steps, - }; - return new RouteEntity({ - id: v4(), - props: routeProps, - }); - }; - - validate(): void { - // entity business rules validation to protect it's invariant before saving entity to a database - } -} diff --git a/src/modules/geography/core/domain/route.errors.ts b/src/modules/geography/core/domain/route.errors.ts deleted file mode 100644 index 420f096..0000000 --- a/src/modules/geography/core/domain/route.errors.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ExceptionBase } from '@mobicoop/ddd-library'; - -export class RouteNotFoundException extends ExceptionBase { - static readonly message = 'Route not found'; - - public readonly code = 'ROUTE.NOT_FOUND'; - - constructor(cause?: Error, metadata?: unknown) { - super(RouteNotFoundException.message, cause, metadata); - } -} - -export class GeorouterUnavailableException extends ExceptionBase { - static readonly message = 'Georouter unavailable'; - - public readonly code = 'GEOROUTER.UNAVAILABLE'; - - constructor(cause?: Error, metadata?: unknown) { - super(GeorouterUnavailableException.message, cause, metadata); - } -} diff --git a/src/modules/geography/core/domain/route.types.ts b/src/modules/geography/core/domain/route.types.ts index e478575..bda6daa 100644 --- a/src/modules/geography/core/domain/route.types.ts +++ b/src/modules/geography/core/domain/route.types.ts @@ -1,26 +1,3 @@ -import { GeorouterPort } from '../application/ports/georouter.port'; -import { GeorouterSettings } from '../application/types/georouter-settings.type'; -import { PointProps } from './value-objects/point.value-object'; -import { StepProps } from './value-objects/step.value-object'; - -// All properties that a Route has -export interface RouteProps { - distance: number; - duration: number; - fwdAzimuth: number; - backAzimuth: number; - distanceAzimuth: number; - points: PointProps[]; - steps?: StepProps[]; -} - -// Properties that are needed for a Route creation -export interface CreateRouteProps { - waypoints: PointProps[]; - georouter: GeorouterPort; - georouterSettings: GeorouterSettings; -} - // Types used outside the domain export type Route = { distance: number; diff --git a/src/modules/geography/core/domain/value-objects/point.value-object.ts b/src/modules/geography/core/domain/value-objects/point.value-object.ts deleted file mode 100644 index 48e6564..0000000 --- a/src/modules/geography/core/domain/value-objects/point.value-object.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { - ArgumentOutOfRangeException, - ValueObject, -} from '@mobicoop/ddd-library'; - -/** Note: - * Value Objects with multiple properties can contain - * other Value Objects inside if needed. - * */ - -export interface PointProps { - lon: number; - lat: number; -} - -export class Point extends ValueObject { - get lon(): number { - return this.props.lon; - } - - get lat(): number { - return this.props.lat; - } - - validate(props: PointProps): void { - if (props.lon > 180 || props.lon < -180) - throw new ArgumentOutOfRangeException('lon must be between -180 and 180'); - if (props.lat > 90 || props.lat < -90) - throw new ArgumentOutOfRangeException('lat must be between -90 and 90'); - } -} diff --git a/src/modules/geography/core/domain/value-objects/step.value-object.ts b/src/modules/geography/core/domain/value-objects/step.value-object.ts deleted file mode 100644 index fbcc410..0000000 --- a/src/modules/geography/core/domain/value-objects/step.value-object.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { ArgumentInvalidException, ValueObject } from '@mobicoop/ddd-library'; -import { Point, PointProps } from './point.value-object'; - -/** Note: - * Value Objects with multiple properties can contain - * other Value Objects inside if needed. - * */ - -export interface StepProps extends PointProps { - duration: number; - distance?: number; -} - -export class Step extends ValueObject { - get duration(): number { - return this.props.duration; - } - - get distance(): number | undefined { - return this.props.distance; - } - - get lon(): number { - return this.props.lon; - } - - get lat(): number { - return this.props.lat; - } - - protected validate(props: StepProps): void { - // validate point props - new Point({ - lon: props.lon, - lat: props.lat, - }); - if (props.duration < 0) - throw new ArgumentInvalidException( - 'duration must be greater than or equal to 0', - ); - if (props.distance !== undefined && props.distance < 0) - throw new ArgumentInvalidException( - 'distance must be greater than or equal to 0', - ); - } -} diff --git a/src/modules/geography/geography.constants.ts b/src/modules/geography/geography.constants.ts deleted file mode 100644 index c61d0a8..0000000 --- a/src/modules/geography/geography.constants.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { KeyType, Type } from '@mobicoop/configuration-module'; - -export const GEOGRAPHY_CONFIG_GEOROUTER_TYPE = 'georouterType'; -export const GEOGRAPHY_CONFIG_GEOROUTER_URL = 'georouterUrl'; - -export const GeographyKeyTypes: KeyType[] = [ - { - key: GEOGRAPHY_CONFIG_GEOROUTER_TYPE, - type: Type.STRING, - }, - { - key: GEOGRAPHY_CONFIG_GEOROUTER_URL, - type: Type.STRING, - }, -]; diff --git a/src/modules/geography/geography.di-tokens.ts b/src/modules/geography/geography.di-tokens.ts index ce34b8b..7bee185 100644 --- a/src/modules/geography/geography.di-tokens.ts +++ b/src/modules/geography/geography.di-tokens.ts @@ -1,6 +1,4 @@ export const DIRECTION_ENCODER = Symbol('DIRECTION_ENCODER'); -export const GEOROUTER = Symbol('GEOROUTER'); -export const GEODESIC = Symbol('GEODESIC'); export const GEOGRAPHY_CONFIGURATION_REPOSITORY = Symbol( 'GEOGRAPHY_CONFIGURATION_REPOSITORY', ); diff --git a/src/modules/geography/geography.module.ts b/src/modules/geography/geography.module.ts index a206fe1..2552cfb 100644 --- a/src/modules/geography/geography.module.ts +++ b/src/modules/geography/geography.module.ts @@ -2,24 +2,12 @@ import { Module, Provider } from '@nestjs/common'; import { CqrsModule } from '@nestjs/cqrs'; import { DIRECTION_ENCODER, - GEODESIC, GEOGRAPHY_CONFIGURATION_REPOSITORY, - GEOROUTER, } from './geography.di-tokens'; import { PostgresDirectionEncoder } from './infrastructure/postgres-direction-encoder'; -import { GetBasicRouteController } from './interface/controllers/get-basic-route.controller'; -import { RouteMapper } from './route.mapper'; -import { Geodesic } from './infrastructure/geodesic'; -import { GraphhopperGeorouter } from './infrastructure/graphhopper-georouter'; import { HttpModule } from '@nestjs/axios'; -import { GetRouteQueryHandler } from './core/application/queries/get-route/get-route.query-handler'; -import { GetDetailedRouteController } from './interface/controllers/get-detailed-route.controller'; import { ConfigurationRepository } from '@mobicoop/configuration-module'; -const queryHandlers: Provider[] = [GetRouteQueryHandler]; - -const mappers: Provider[] = [RouteMapper]; - const adapters: Provider[] = [ { provide: GEOGRAPHY_CONFIGURATION_REPOSITORY, @@ -29,26 +17,11 @@ const adapters: Provider[] = [ provide: DIRECTION_ENCODER, useClass: PostgresDirectionEncoder, }, - { - provide: GEOROUTER, - useClass: GraphhopperGeorouter, - }, - { - provide: GEODESIC, - useClass: Geodesic, - }, - GetBasicRouteController, - GetDetailedRouteController, ]; @Module({ imports: [CqrsModule, HttpModule], - providers: [...queryHandlers, ...mappers, ...adapters], - exports: [ - RouteMapper, - DIRECTION_ENCODER, - GetBasicRouteController, - GetDetailedRouteController, - ], + providers: [...adapters], + exports: [DIRECTION_ENCODER], }) export class GeographyModule {} diff --git a/src/modules/geography/infrastructure/geodesic.ts b/src/modules/geography/infrastructure/geodesic.ts deleted file mode 100644 index 5655585..0000000 --- a/src/modules/geography/infrastructure/geodesic.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Geodesic as Geolib, GeodesicClass } from 'geographiclib-geodesic'; -import { GeodesicPort } from '../core/application/ports/geodesic.port'; - -@Injectable() -export class Geodesic implements GeodesicPort { - 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, - ); - if (!azimuth || !distance) - throw new Error( - `Inverse not found for coordinates ${lon1} ${lat1} / ${lon2} ${lat2}`, - ); - return { azimuth, distance }; - }; - - azimuth = ( - lon1: number, - lat1: number, - lon2: number, - lat2: number, - ): number => { - const { azi2: azimuth } = this.geod.Inverse(lat1, lon1, lat2, lon2); - if (!azimuth) - throw new Error( - `Azimuth not found for coordinates ${lon1} ${lat1} / ${lon2} ${lat2}`, - ); - return azimuth; - }; - - distance = ( - lon1: number, - lat1: number, - lon2: number, - lat2: number, - ): number => { - const { s12: distance } = this.geod.Inverse(lat1, lon1, lat2, lon2); - if (!distance) - throw new Error( - `Distance not found for coordinates ${lon1} ${lat1} / ${lon2} ${lat2}`, - ); - return distance; - }; -} diff --git a/src/modules/geography/infrastructure/graphhopper-georouter.ts b/src/modules/geography/infrastructure/graphhopper-georouter.ts deleted file mode 100644 index 49c72cb..0000000 --- a/src/modules/geography/infrastructure/graphhopper-georouter.ts +++ /dev/null @@ -1,342 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { HttpService } from '@nestjs/axios'; -import { GeorouterPort } from '../core/application/ports/georouter.port'; -import { GeorouterSettings } from '../core/application/types/georouter-settings.type'; -import { Route, Step, Point } from '../core/domain/route.types'; -import { - GEODESIC, - GEOGRAPHY_CONFIGURATION_REPOSITORY, -} from '../geography.di-tokens'; -import { catchError, lastValueFrom, map } from 'rxjs'; -import { AxiosError, AxiosResponse } from 'axios'; -import { - GeorouterUnavailableException, - RouteNotFoundException, -} from '../core/domain/route.errors'; -import { GeodesicPort } from '../core/application/ports/geodesic.port'; -import { - Domain, - Configurator, - GetConfigurationRepositoryPort, -} from '@mobicoop/configuration-module'; -import { - GEOGRAPHY_CONFIG_GEOROUTER_URL, - GeographyKeyTypes, -} from '../geography.constants'; - -@Injectable() -export class GraphhopperGeorouter implements GeorouterPort { - private url: string; - private urlArgs: string[]; - - constructor( - private readonly httpService: HttpService, - @Inject(GEOGRAPHY_CONFIGURATION_REPOSITORY) - private readonly configurationRepository: GetConfigurationRepositoryPort, - @Inject(GEODESIC) private readonly geodesic: GeodesicPort, - ) {} - - route = async ( - waypoints: Point[], - settings: GeorouterSettings, - ): Promise => { - const geographyConfigurator: Configurator = - await this.configurationRepository.mget( - Domain.GEOGRAPHY, - GeographyKeyTypes, - ); - this.url = [ - geographyConfigurator.get(GEOGRAPHY_CONFIG_GEOROUTER_URL), - '/route?', - ].join(''); - this._setDefaultUrlArgs(); - this._setSettings(settings); - return this._getRoute(waypoints); - }; - - private _setDefaultUrlArgs = (): void => { - this.urlArgs = ['profile=car', 'points_encoded=false']; - }; - - private _setSettings = (settings: GeorouterSettings): void => { - if (settings.detailedDuration) { - this.urlArgs.push('details=time'); - } - if (settings.detailedDistance) { - this.urlArgs.push('instructions=true'); - } else { - this.urlArgs.push('instructions=false'); - } - if (!settings.points) { - this.urlArgs.push('calc_points=false'); - } - }; - - private _getRoute = async (waypoints: Point[]): Promise => { - const url: string = [ - this.getUrl(), - '&point=', - waypoints - .map((point: Point) => [point.lat, point.lon].join('%2C')) - .join('&point='), - ].join(''); - return await lastValueFrom( - this.httpService.get(url).pipe( - map((response) => { - if (response.data) return this.createRoute(response); - throw new Error(); - }), - catchError((error: AxiosError) => { - if (error.code == AxiosError.ERR_BAD_REQUEST) { - throw new RouteNotFoundException( - error, - 'No route found for given coordinates', - ); - } - throw new GeorouterUnavailableException(error); - }), - ), - ); - }; - - private getUrl = (): string => [this.url, this.urlArgs.join('&')].join(''); - - private createRoute = ( - response: AxiosResponse, - ): Route => { - const route = {} as Route; - if (response.data.paths && response.data.paths[0]) { - const shortestPath = response.data.paths[0]; - route.distance = shortestPath.distance ?? 0; - route.duration = shortestPath.time ? shortestPath.time / 1000 : 0; - if (shortestPath.points && shortestPath.points.coordinates) { - route.points = shortestPath.points.coordinates.map((coordinate) => ({ - lon: coordinate[0], - lat: coordinate[1], - })); - const inverse = this.geodesic.inverse( - route.points[0].lon, - route.points[0].lat, - route.points[route.points.length - 1].lon, - route.points[route.points.length - 1].lat, - ); - route.fwdAzimuth = - inverse.azimuth >= 0 - ? inverse.azimuth - : 360 - Math.abs(inverse.azimuth); - route.backAzimuth = - route.fwdAzimuth > 180 - ? route.fwdAzimuth - 180 - : route.fwdAzimuth + 180; - route.distanceAzimuth = inverse.distance; - if ( - shortestPath.details && - shortestPath.details.time && - shortestPath.snapped_waypoints && - shortestPath.snapped_waypoints.coordinates - ) { - let instructions: GraphhopperInstruction[] = []; - if (shortestPath.instructions) - instructions = shortestPath.instructions; - route.steps = this.generateSteps( - shortestPath.points.coordinates, - shortestPath.snapped_waypoints.coordinates, - shortestPath.details.time, - instructions, - ); - } - } - } - return route; - }; - - private generateSteps = ( - points: [[number, number]], - snappedWaypoints: [[number, number]], - durations: [[number, number, number]], - instructions: GraphhopperInstruction[], - ): Step[] => { - const indices = this.getIndices(points, snappedWaypoints); - const times = this.getTimes(durations, indices); - const distances = this.getDistances(instructions, indices); - return indices.map((index) => { - const duration = times.find((time) => time.index == index); - if (!duration) - throw new Error(`Duration not found for waypoint #${index}`); - const distance = distances.find((distance) => distance.index == index); - if (!distance && instructions.length > 0) - throw new Error(`Distance not found for waypoint #${index}`); - return { - lon: points[index][1], - lat: points[index][0], - distance: distance?.distance, - duration: duration.duration, - }; - }); - }; - - private getIndices = ( - points: [[number, number]], - snappedWaypoints: [[number, number]], - ): number[] => { - const indices: number[] = snappedWaypoints.map( - (waypoint: [number, number]) => - points.findIndex( - (point) => point[0] == waypoint[0] && point[1] == waypoint[1], - ), - ); - if (indices.find((index: number) => index == -1) === undefined) - return indices; - const missedWaypoints = indices - .map( - (value, index) => - < - { - index: number; - originIndex: number; - waypoint: number[]; - nearest?: number; - distance: number; - } - >{ - index: value, - originIndex: index, - waypoint: snappedWaypoints[index], - nearest: undefined, - distance: 999999999, - }, - ) - .filter((element) => element.index == -1); - for (const index in points) { - for (const missedWaypoint of missedWaypoints) { - const distance = this.geodesic.distance( - missedWaypoint.waypoint[0], - missedWaypoint.waypoint[1], - points[index][0], - points[index][1], - ); - if (distance < missedWaypoint.distance) { - missedWaypoint.distance = distance; - missedWaypoint.nearest = parseInt(index); - } - } - } - for (const missedWaypoint of missedWaypoints) { - indices[missedWaypoint.originIndex] = missedWaypoint.nearest as number; - } - return indices; - }; - - private getTimes = ( - durations: [[number, number, number]], - indices: number[], - ): Array<{ index: number; duration: number }> => { - const times: Array<{ index: number; duration: number }> = []; - let duration = 0; - for (const [origin, destination, stepDuration] of durations) { - let indexFound = false; - const indexAsOrigin = indices.find((index) => index == origin); - if ( - indexAsOrigin !== undefined && - times.find((time) => origin == time.index) == undefined - ) { - times.push({ - index: indexAsOrigin, - duration: Math.round(stepDuration / 1000), - }); - indexFound = true; - } - if (!indexFound) { - const indexAsDestination = indices.find( - (index) => index == destination, - ); - if ( - indexAsDestination !== undefined && - times.find((time) => destination == time.index) == undefined - ) { - times.push({ - index: indexAsDestination, - duration: Math.round((duration + stepDuration) / 1000), - }); - indexFound = true; - } - } - if (!indexFound) { - const indexInBetween = indices.find( - (index) => origin < index && index < destination, - ); - if (indexInBetween !== undefined) { - times.push({ - index: indexInBetween, - duration: Math.round((duration + stepDuration / 2) / 1000), - }); - } - } - duration += stepDuration; - } - return times; - }; - - private getDistances = ( - instructions: GraphhopperInstruction[], - indices: number[], - ): Array<{ index: number; distance: number }> => { - let distance = 0; - const distances: Array<{ index: number; distance: number }> = [ - { - index: 0, - distance, - }, - ]; - for (const instruction of instructions) { - distance += instruction.distance; - if ( - (instruction.sign == GraphhopperSign.SIGN_WAYPOINT || - instruction.sign == GraphhopperSign.SIGN_FINISH) && - indices.find((index) => index == instruction.interval[0]) !== undefined - ) { - distances.push({ - index: instruction.interval[0], - distance: Math.round(distance), - }); - } - } - return distances; - }; -} - -type GraphhopperResponse = { - paths: [ - { - distance: number; - weight: number; - time: number; - points_encoded: boolean; - bbox: number[]; - points: GraphhopperCoordinates; - snapped_waypoints: GraphhopperCoordinates; - details: { - time: [[number, number, number]]; - }; - instructions: GraphhopperInstruction[]; - }, - ]; -}; - -type GraphhopperCoordinates = { - coordinates: [[number, number]]; -}; - -type GraphhopperInstruction = { - distance: number; - heading: number; - sign: GraphhopperSign; - interval: [number, number]; - text: string; -}; - -enum GraphhopperSign { - SIGN_START = 0, - SIGN_FINISH = 4, - SIGN_WAYPOINT = 5, -} diff --git a/src/modules/geography/interface/controllers/dtos/get-route.request.dto.ts b/src/modules/geography/interface/controllers/dtos/get-route.request.dto.ts deleted file mode 100644 index 651df23..0000000 --- a/src/modules/geography/interface/controllers/dtos/get-route.request.dto.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Point } from '@modules/geography/core/domain/route.types'; - -export type GetRouteRequestDto = { - waypoints: Point[]; -}; diff --git a/src/modules/geography/interface/controllers/get-basic-route.controller.ts b/src/modules/geography/interface/controllers/get-basic-route.controller.ts deleted file mode 100644 index b28b88e..0000000 --- a/src/modules/geography/interface/controllers/get-basic-route.controller.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { QueryBus } from '@nestjs/cqrs'; -import { RouteResponseDto } from '../dtos/route.response.dto'; -import { GetRouteRequestDto } from './dtos/get-route.request.dto'; -import { RouteEntity } from '@modules/geography/core/domain/route.entity'; -import { GetRouteQuery } from '@modules/geography/core/application/queries/get-route/get-route.query'; -import { RouteMapper } from '@modules/geography/route.mapper'; -import { Controller } from '@nestjs/common'; -import { GetRouteControllerPort } from '@modules/geography/core/application/ports/get-route-controller.port'; - -@Controller() -export class GetBasicRouteController implements GetRouteControllerPort { - constructor( - private readonly queryBus: QueryBus, - private readonly mapper: RouteMapper, - ) {} - - async get(data: GetRouteRequestDto): Promise { - const route: RouteEntity = await this.queryBus.execute( - new GetRouteQuery(data.waypoints), - ); - return this.mapper.toResponse(route); - } -} diff --git a/src/modules/geography/interface/controllers/get-detailed-route.controller.ts b/src/modules/geography/interface/controllers/get-detailed-route.controller.ts deleted file mode 100644 index 34cf693..0000000 --- a/src/modules/geography/interface/controllers/get-detailed-route.controller.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { QueryBus } from '@nestjs/cqrs'; -import { RouteResponseDto } from '../dtos/route.response.dto'; -import { GetRouteRequestDto } from './dtos/get-route.request.dto'; -import { RouteEntity } from '@modules/geography/core/domain/route.entity'; -import { GetRouteQuery } from '@modules/geography/core/application/queries/get-route/get-route.query'; -import { RouteMapper } from '@modules/geography/route.mapper'; -import { Controller } from '@nestjs/common'; -import { GetRouteControllerPort } from '@modules/geography/core/application/ports/get-route-controller.port'; - -@Controller() -export class GetDetailedRouteController implements GetRouteControllerPort { - constructor( - private readonly queryBus: QueryBus, - private readonly mapper: RouteMapper, - ) {} - - async get(data: GetRouteRequestDto): Promise { - const route: RouteEntity = await this.queryBus.execute( - new GetRouteQuery(data.waypoints, { - detailedDistance: true, - detailedDuration: true, - points: true, - }), - ); - return this.mapper.toResponse(route); - } -} diff --git a/src/modules/geography/interface/dtos/route.response.dto.ts b/src/modules/geography/interface/dtos/route.response.dto.ts deleted file mode 100644 index 21d2ec1..0000000 --- a/src/modules/geography/interface/dtos/route.response.dto.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Point, Step } from '@modules/geography/core/domain/route.types'; - -export class RouteResponseDto { - distance: number; - duration: number; - fwdAzimuth: number; - backAzimuth: number; - distanceAzimuth: number; - points: Point[]; - steps?: Step[]; -} diff --git a/src/modules/geography/route.mapper.ts b/src/modules/geography/route.mapper.ts deleted file mode 100644 index 43728f9..0000000 --- a/src/modules/geography/route.mapper.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Mapper } from '@mobicoop/ddd-library'; -import { Injectable } from '@nestjs/common'; -import { RouteEntity } from './core/domain/route.entity'; -import { RouteResponseDto } from './interface/dtos/route.response.dto'; - -/** - * Mapper constructs objects that are used in different layers: - * Record is an object that is stored in a database, - * Entity is an object that is used in application domain layer, - * and a ResponseDTO is an object returned to a user (usually as json). - */ - -@Injectable() -export class RouteMapper - implements Mapper -{ - toResponse = (entity: RouteEntity): RouteResponseDto => { - const response = new RouteResponseDto(); - response.distance = Math.round(entity.getProps().distance); - response.duration = Math.round(entity.getProps().duration); - response.fwdAzimuth = Math.round(entity.getProps().fwdAzimuth); - response.backAzimuth = Math.round(entity.getProps().backAzimuth); - response.distanceAzimuth = Math.round(entity.getProps().distanceAzimuth); - response.points = entity.getProps().points; - response.steps = entity.getProps().steps; - return response; - }; -} diff --git a/src/modules/geography/tests/unit/core/get-route.query-handler.spec.ts b/src/modules/geography/tests/unit/core/get-route.query-handler.spec.ts deleted file mode 100644 index ca8a367..0000000 --- a/src/modules/geography/tests/unit/core/get-route.query-handler.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { GeorouterPort } from '@modules/geography/core/application/ports/georouter.port'; -import { GetRouteQuery } from '@modules/geography/core/application/queries/get-route/get-route.query'; -import { GetRouteQueryHandler } from '@modules/geography/core/application/queries/get-route/get-route.query-handler'; -import { RouteEntity } from '@modules/geography/core/domain/route.entity'; -import { Point } from '@modules/geography/core/domain/route.types'; -import { GEOROUTER } from '@modules/geography/geography.di-tokens'; -import { Test, TestingModule } from '@nestjs/testing'; - -const originWaypoint: Point = { - lat: 48.689445, - lon: 6.17651, -}; -const destinationWaypoint: Point = { - lat: 48.8566, - lon: 2.3522, -}; - -const mockGeorouter: GeorouterPort = { - route: jest.fn(), -}; - -describe('Get route query handler', () => { - let getRoutequeryHandler: GetRouteQueryHandler; - - beforeAll(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - { - provide: GEOROUTER, - useValue: mockGeorouter, - }, - GetRouteQueryHandler, - ], - }).compile(); - - getRoutequeryHandler = - module.get(GetRouteQueryHandler); - }); - - it('should be defined', () => { - expect(getRoutequeryHandler).toBeDefined(); - }); - - describe('execution', () => { - it('should get a route', async () => { - const getRoutequery = new GetRouteQuery( - [originWaypoint, destinationWaypoint], - { - detailedDistance: false, - detailedDuration: false, - points: true, - }, - ); - RouteEntity.create = jest.fn().mockReturnValue({ - id: '047a6ecf-23d4-4d3e-877c-3225d560a8da', - }); - const result = await getRoutequeryHandler.execute(getRoutequery); - expect(result.id).toBe('047a6ecf-23d4-4d3e-877c-3225d560a8da'); - }); - }); -}); diff --git a/src/modules/geography/tests/unit/core/point.value-object.spec.ts b/src/modules/geography/tests/unit/core/point.value-object.spec.ts deleted file mode 100644 index bf11cd7..0000000 --- a/src/modules/geography/tests/unit/core/point.value-object.spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { ArgumentOutOfRangeException } from '@mobicoop/ddd-library'; -import { Point } from '@modules/geography/core/domain/value-objects/point.value-object'; - -describe('Point value object', () => { - it('should create a point value object', () => { - const pointVO = new Point({ - lat: 48.689445, - lon: 6.17651, - }); - expect(pointVO.lat).toBe(48.689445); - expect(pointVO.lon).toBe(6.17651); - }); - it('should throw an exception if longitude is invalid', () => { - expect(() => { - new Point({ - lat: 48.689445, - lon: 186.17651, - }); - }).toThrow(ArgumentOutOfRangeException); - expect(() => { - new Point({ - lat: 48.689445, - lon: -186.17651, - }); - }).toThrow(ArgumentOutOfRangeException); - }); - it('should throw an exception if latitude is invalid', () => { - expect(() => { - new Point({ - lat: 148.689445, - lon: 6.17651, - }); - }).toThrow(ArgumentOutOfRangeException); - expect(() => { - new Point({ - lat: -148.689445, - lon: 6.17651, - }); - }).toThrow(ArgumentOutOfRangeException); - }); -}); diff --git a/src/modules/geography/tests/unit/core/route.entity.spec.ts b/src/modules/geography/tests/unit/core/route.entity.spec.ts deleted file mode 100644 index 93226d5..0000000 --- a/src/modules/geography/tests/unit/core/route.entity.spec.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { GeorouterPort } from '@modules/geography/core/application/ports/georouter.port'; -import { RouteEntity } from '@modules/geography/core/domain/route.entity'; -import { RouteNotFoundException } from '@modules/geography/core/domain/route.errors'; -import { - Point, - CreateRouteProps, -} from '@modules/geography/core/domain/route.types'; - -const originPoint: Point = { - lat: 48.689445, - lon: 6.17651, -}; -const destinationPoint: Point = { - lat: 48.8566, - lon: 2.3522, -}; - -const mockGeorouter: GeorouterPort = { - route: jest - .fn() - .mockImplementationOnce(() => ({ - distance: 350101, - duration: 14422, - fwdAzimuth: 273, - backAzimuth: 93, - distanceAzimuth: 336544, - points: [ - { - lon: 6.1765102, - lat: 48.689445, - }, - { - lon: 4.984578, - lat: 48.725687, - }, - { - lon: 2.3522, - lat: 48.8566, - }, - ], - steps: [], - })) - .mockImplementationOnce(() => []), -}; - -const createRouteProps: CreateRouteProps = { - waypoints: [originPoint, destinationPoint], - georouter: mockGeorouter, - georouterSettings: { - points: true, - detailedDistance: false, - detailedDuration: false, - }, -}; - -describe('Route entity create', () => { - it('should create a new entity', async () => { - const route: RouteEntity = await RouteEntity.create(createRouteProps); - expect(route.id.length).toBe(36); - expect(route.getProps().duration).toBe(14422); - }); - - it('should throw an exception if route is not found', async () => { - try { - await RouteEntity.create(createRouteProps); - } catch (e: any) { - expect(e).toBeInstanceOf(RouteNotFoundException); - } - }); -}); diff --git a/src/modules/geography/tests/unit/core/step.value-object.spec.ts b/src/modules/geography/tests/unit/core/step.value-object.spec.ts deleted file mode 100644 index ed84ad8..0000000 --- a/src/modules/geography/tests/unit/core/step.value-object.spec.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { - ArgumentInvalidException, - ArgumentOutOfRangeException, -} from '@mobicoop/ddd-library'; -import { Step } from '@modules/geography/core/domain/value-objects/step.value-object'; - -describe('Step value object', () => { - it('should create a step value object', () => { - const stepVO = new Step({ - lat: 48.689445, - lon: 6.17651, - duration: 150, - distance: 12000, - }); - expect(stepVO.duration).toBe(150); - expect(stepVO.distance).toBe(12000); - expect(stepVO.lat).toBe(48.689445); - expect(stepVO.lon).toBe(6.17651); - }); - it('should throw an exception if longitude is invalid', () => { - expect(() => { - new Step({ - lat: 48.689445, - lon: 186.17651, - duration: 150, - distance: 12000, - }); - }).toThrow(ArgumentOutOfRangeException); - expect(() => { - new Step({ - lat: 48.689445, - lon: -186.17651, - duration: 150, - distance: 12000, - }); - }).toThrow(ArgumentOutOfRangeException); - }); - it('should throw an exception if latitude is invalid', () => { - expect(() => { - new Step({ - lat: 248.689445, - lon: 6.17651, - duration: 150, - distance: 12000, - }); - }).toThrow(ArgumentOutOfRangeException); - expect(() => { - new Step({ - lat: -148.689445, - lon: 6.17651, - duration: 150, - distance: 12000, - }); - }).toThrow(ArgumentOutOfRangeException); - }); - it('should throw an exception if distance is invalid', () => { - expect(() => { - new Step({ - lat: 48.689445, - lon: 6.17651, - duration: 150, - distance: -12000, - }); - }).toThrow(ArgumentInvalidException); - }); - it('should throw an exception if duration is invalid', () => { - expect(() => { - new Step({ - lat: 48.689445, - lon: 6.17651, - duration: -150, - distance: 12000, - }); - }).toThrow(ArgumentInvalidException); - }); -}); diff --git a/src/modules/geography/tests/unit/infrastructure/geodesic.spec.ts b/src/modules/geography/tests/unit/infrastructure/geodesic.spec.ts deleted file mode 100644 index 82f29f7..0000000 --- a/src/modules/geography/tests/unit/infrastructure/geodesic.spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Geodesic } from '@modules/geography/infrastructure/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 as number)).toBe(45); - expect(Math.round(inv.distance as number)).toBe(156900); - }); - it('should get azimuth value', () => { - const geodesic: Geodesic = new Geodesic(); - const azimuth = geodesic.azimuth(0, 0, 1, 1); - expect(Math.round(azimuth as number)).toBe(45); - }); - it('should get distance value', () => { - const geodesic: Geodesic = new Geodesic(); - const distance = geodesic.distance(0, 0, 1, 1); - expect(Math.round(distance as number)).toBe(156900); - }); - it('should throw an exception if inverse fails', () => { - const geodesic: Geodesic = new Geodesic(); - expect(() => { - geodesic.inverse(7.74547, 48.583035, 7.74547, 48.583036); - }).toThrow(); - }); - it('should throw an exception if azimuth fails', () => { - const geodesic: Geodesic = new Geodesic(); - expect(() => { - geodesic.azimuth(7.74547, 48.583035, 7.74547, 48.583036); - }).toThrow(); - }); -}); diff --git a/src/modules/geography/tests/unit/infrastructure/graphhopper-georouter.spec.ts b/src/modules/geography/tests/unit/infrastructure/graphhopper-georouter.spec.ts deleted file mode 100644 index 16bee13..0000000 --- a/src/modules/geography/tests/unit/infrastructure/graphhopper-georouter.spec.ts +++ /dev/null @@ -1,508 +0,0 @@ -import { - Domain, - KeyType, - Configurator, - GetConfigurationRepositoryPort, -} from '@mobicoop/configuration-module'; -import { GeodesicPort } from '@modules/geography/core/application/ports/geodesic.port'; -import { - GeorouterUnavailableException, - RouteNotFoundException, -} from '@modules/geography/core/domain/route.errors'; -import { Route, Step } from '@modules/geography/core/domain/route.types'; -import { GEOGRAPHY_CONFIG_GEOROUTER_URL } from '@modules/geography/geography.constants'; -import { - GEODESIC, - GEOGRAPHY_CONFIGURATION_REPOSITORY, -} from '@modules/geography/geography.di-tokens'; -import { GraphhopperGeorouter } from '@modules/geography/infrastructure/graphhopper-georouter'; -import { HttpService } from '@nestjs/axios'; -import { Test, TestingModule } from '@nestjs/testing'; -import { AxiosError } from 'axios'; -import { of, throwError } from 'rxjs'; - -const mockHttpService = { - get: jest - .fn() - .mockImplementationOnce(() => { - return throwError( - () => new AxiosError('Axios error', AxiosError.ERR_BAD_REQUEST), - ); - }) - .mockImplementationOnce(() => { - return throwError(() => 'Router unavailable'); - }) - .mockImplementationOnce(() => { - return of({ - status: 200, - }); - }) - .mockImplementationOnce(() => { - return of({ - status: 200, - data: { - paths: [ - { - distance: 50000, - time: 1800000, - snapped_waypoints: { - coordinates: [ - [0, 0], - [10, 10], - ], - }, - }, - ], - }, - }); - }) - .mockImplementationOnce(() => { - return of({ - status: 200, - data: { - paths: [ - { - distance: 50000, - time: 1800000, - points: { - coordinates: [ - [0, 0], - [1, 1], - [2, 2], - [3, 3], - [4, 4], - [5, 5], - [6, 6], - [7, 7], - [8, 8], - [9, 9], - [10, 10], - ], - }, - snapped_waypoints: { - coordinates: [ - [0, 0], - [10, 10], - ], - }, - }, - ], - }, - }); - }) - .mockImplementationOnce(() => { - return of({ - status: 200, - data: { - paths: [ - { - distance: 50000, - time: 1800000, - points: { - coordinates: [ - [0, 0], - [1, 1], - [2, 2], - [3, 3], - [4, 4], - [5, 5], - [6, 6], - [7, 7], - [8, 8], - [9, 9], - [10, 10], - ], - }, - details: { - time: [ - [0, 1, 180000], - [1, 2, 180000], - [2, 3, 180000], - [3, 4, 180000], - [4, 5, 180000], - [5, 6, 180000], - [6, 7, 180000], - [7, 9, 360000], - [9, 10, 180000], - ], - }, - snapped_waypoints: { - coordinates: [ - [0, 0], - [10, 10], - ], - }, - }, - ], - }, - }); - }) - .mockImplementationOnce(() => { - return of({ - status: 200, - data: { - paths: [ - { - distance: 50000, - time: 1800000, - points: { - coordinates: [ - [0, 0], - [1, 1], - [2, 2], - [3, 3], - [4, 4], - [7, 7], - [8, 8], - [9, 9], - [10, 10], - ], - }, - snapped_waypoints: { - coordinates: [ - [0, 0], - [5, 5], - [10, 10], - ], - }, - details: { - time: [ - [0, 1, 180000], - [1, 2, 180000], - [2, 3, 180000], - [3, 4, 180000], - [4, 7, 540000], - [7, 9, 360000], - [9, 10, 180000], - ], - }, - }, - ], - }, - }); - }) - .mockImplementationOnce(() => { - return of({ - status: 200, - data: { - paths: [ - { - distance: 50000, - time: 1800000, - points: { - coordinates: [ - [0, 0], - [1, 1], - [2, 2], - [3, 3], - [4, 4], - [5, 5], - [6, 6], - [7, 7], - [8, 8], - [9, 9], - [10, 10], - ], - }, - snapped_waypoints: { - coordinates: [ - [0, 0], - [5, 5], - [10, 10], - ], - }, - details: { - time: [ - [0, 1, 180000], - [1, 2, 180000], - [2, 3, 180000], - [3, 4, 180000], - [4, 7, 540000], - [7, 9, 360000], - [9, 10, 180000], - ], - }, - instructions: [ - { - distance: 25000, - sign: 0, - interval: [0, 5], - text: 'Some instructions', - time: 900000, - }, - { - distance: 0, - sign: 5, - interval: [5, 5], - text: 'Waypoint 1', - time: 0, - }, - { - distance: 25000, - sign: 2, - interval: [5, 10], - text: 'Some instructions', - time: 900000, - }, - { - distance: 0.0, - sign: 4, - interval: [10, 10], - text: 'Arrive at destination', - time: 0, - }, - ], - }, - ], - }, - }); - }), -}; - -const mockGeodesic: GeodesicPort = { - inverse: jest.fn().mockImplementation(() => ({ - azimuth: 45, - distance: 50000, - })), - azimuth: jest.fn().mockImplementation(() => 45), - distance: jest.fn().mockImplementation(() => 50000), -}; - -const mockConfigurationRepository: GetConfigurationRepositoryPort = { - get: jest.fn(), - mget: jest.fn().mockImplementation( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - (domain: Domain, keyTypes: KeyType[]) => { - switch (domain) { - case Domain.GEOGRAPHY: - return new Configurator(Domain.GEOGRAPHY, [ - { - domain: Domain.GEOGRAPHY, - key: GEOGRAPHY_CONFIG_GEOROUTER_URL, - value: 'http://localhost:8989', - }, - ]); - } - }, - ), -}; - -describe('Graphhopper Georouter', () => { - let graphhopperGeorouter: GraphhopperGeorouter; - - beforeAll(async () => { - const module: TestingModule = await Test.createTestingModule({ - imports: [], - providers: [ - GraphhopperGeorouter, - { - provide: HttpService, - useValue: mockHttpService, - }, - { - provide: GEOGRAPHY_CONFIGURATION_REPOSITORY, - useValue: mockConfigurationRepository, - }, - { - provide: GEODESIC, - useValue: mockGeodesic, - }, - ], - }).compile(); - - graphhopperGeorouter = - module.get(GraphhopperGeorouter); - }); - - it('should be defined', () => { - expect(graphhopperGeorouter).toBeDefined(); - }); - - it('should fail if route is not found', async () => { - await expect( - graphhopperGeorouter.route( - [ - { - lon: 0, - lat: 0, - }, - { - lon: 1, - lat: 1, - }, - ], - { - detailedDistance: false, - detailedDuration: false, - points: false, - }, - ), - ).rejects.toBeInstanceOf(RouteNotFoundException); - }); - - it('should fail if georouter is unavailable', async () => { - await expect( - graphhopperGeorouter.route( - [ - { - lon: 0, - lat: 0, - }, - { - lon: 1, - lat: 1, - }, - ], - { - detailedDistance: false, - detailedDuration: false, - points: false, - }, - ), - ).rejects.toBeInstanceOf(GeorouterUnavailableException); - }); - - it('should fail if georouter response is corrupted', async () => { - await expect( - graphhopperGeorouter.route( - [ - { - lon: 0, - lat: 0, - }, - { - lon: 1, - lat: 1, - }, - ], - { - detailedDistance: false, - detailedDuration: false, - points: false, - }, - ), - ).rejects.toBeInstanceOf(GeorouterUnavailableException); - }); - - it('should create a basic route', async () => { - const route: Route = await graphhopperGeorouter.route( - [ - { - lon: 0, - lat: 0, - }, - { - lon: 10, - lat: 10, - }, - ], - { - detailedDistance: false, - detailedDuration: false, - points: false, - }, - ); - expect(route.distance).toBe(50000); - }); - - it('should create a route with points', async () => { - const route: Route = await graphhopperGeorouter.route( - [ - { - lon: 0, - lat: 0, - }, - { - lon: 10, - lat: 10, - }, - ], - { - detailedDistance: false, - detailedDuration: false, - points: true, - }, - ); - expect(route.distance).toBe(50000); - expect(route.duration).toBe(1800); - expect(route.fwdAzimuth).toBe(45); - expect(route.backAzimuth).toBe(225); - expect(route.points).toHaveLength(11); - }); - - it('should create a route with points and time', async () => { - const route: Route = await graphhopperGeorouter.route( - [ - { - lon: 0, - lat: 0, - }, - { - lon: 10, - lat: 10, - }, - ], - { - detailedDistance: false, - detailedDuration: true, - points: true, - }, - ); - expect(route.steps).toHaveLength(2); - expect((route.steps as Step[])[1].duration).toBe(1800); - expect((route.steps as Step[])[1].distance).toBeUndefined(); - }); - - it('should create one route with points and missed waypoints extrapolations', async () => { - const route: Route = await graphhopperGeorouter.route( - [ - { - lon: 0, - lat: 0, - }, - { - lon: 5, - lat: 5, - }, - { - lon: 10, - lat: 10, - }, - ], - { - detailedDistance: false, - detailedDuration: true, - points: true, - }, - ); - expect(route.steps).toHaveLength(3); - expect(route.distance).toBe(50000); - expect(route.duration).toBe(1800); - expect(route.fwdAzimuth).toBe(45); - expect(route.backAzimuth).toBe(225); - expect(route.points.length).toBe(9); - }); - - it('should create a route with points, time and distance', async () => { - const route: Route = await graphhopperGeorouter.route( - [ - { - lon: 0, - lat: 0, - }, - { - lon: 10, - lat: 10, - }, - ], - { - detailedDistance: true, - detailedDuration: true, - points: true, - }, - ); - expect(route.steps).toHaveLength(3); - expect((route.steps as Step[])[1].duration).toBe(990); - expect((route.steps as Step[])[1].distance).toBe(25000); - }); -}); diff --git a/src/modules/geography/tests/unit/interface/get-basic-route.controller.spec.ts b/src/modules/geography/tests/unit/interface/get-basic-route.controller.spec.ts deleted file mode 100644 index 96484fc..0000000 --- a/src/modules/geography/tests/unit/interface/get-basic-route.controller.spec.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { GetBasicRouteController } from '@modules/geography/interface/controllers/get-basic-route.controller'; -import { RouteMapper } from '@modules/geography/route.mapper'; -import { QueryBus } from '@nestjs/cqrs'; -import { Test, TestingModule } from '@nestjs/testing'; - -const mockQueryBus = { - execute: jest.fn(), -}; - -const mockRouteMapper = { - toPersistence: jest.fn(), - toDomain: jest.fn(), - toResponse: jest.fn(), -}; - -describe('Get Basic Route Controller', () => { - let getBasicRouteController: GetBasicRouteController; - - beforeAll(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - { - provide: QueryBus, - useValue: mockQueryBus, - }, - { - provide: RouteMapper, - useValue: mockRouteMapper, - }, - GetBasicRouteController, - ], - }).compile(); - - getBasicRouteController = module.get( - GetBasicRouteController, - ); - }); - - afterEach(async () => { - jest.clearAllMocks(); - }); - - it('should be defined', () => { - expect(getBasicRouteController).toBeDefined(); - }); - - it('should get a route', async () => { - jest.spyOn(mockQueryBus, 'execute'); - await getBasicRouteController.get({ - waypoints: [ - { - lat: 48.689445, - lon: 6.17651, - }, - { - lat: 48.8566, - lon: 2.3522, - }, - ], - }); - expect(mockQueryBus.execute).toHaveBeenCalledTimes(1); - }); -}); diff --git a/src/modules/geography/tests/unit/interface/get-detailed-route.controller.spec.ts b/src/modules/geography/tests/unit/interface/get-detailed-route.controller.spec.ts deleted file mode 100644 index e61e04e..0000000 --- a/src/modules/geography/tests/unit/interface/get-detailed-route.controller.spec.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { GetDetailedRouteController } from '@modules/geography/interface/controllers/get-detailed-route.controller'; -import { RouteMapper } from '@modules/geography/route.mapper'; -import { QueryBus } from '@nestjs/cqrs'; -import { Test, TestingModule } from '@nestjs/testing'; - -const mockQueryBus = { - execute: jest.fn(), -}; - -const mockRouteMapper = { - toPersistence: jest.fn(), - toDomain: jest.fn(), - toResponse: jest.fn(), -}; - -describe('Get Detailed Route Controller', () => { - let getDetailedRouteController: GetDetailedRouteController; - - beforeAll(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - { - provide: QueryBus, - useValue: mockQueryBus, - }, - { - provide: RouteMapper, - useValue: mockRouteMapper, - }, - GetDetailedRouteController, - ], - }).compile(); - - getDetailedRouteController = module.get( - GetDetailedRouteController, - ); - }); - - afterEach(async () => { - jest.clearAllMocks(); - }); - - it('should be defined', () => { - expect(getDetailedRouteController).toBeDefined(); - }); - - it('should get a route', async () => { - jest.spyOn(mockQueryBus, 'execute'); - await getDetailedRouteController.get({ - waypoints: [ - { - lat: 48.689445, - lon: 6.17651, - }, - { - lat: 48.8566, - lon: 2.3522, - }, - ], - }); - expect(mockQueryBus.execute).toHaveBeenCalledTimes(1); - }); -}); diff --git a/src/modules/geography/tests/unit/route.mapper.spec.ts b/src/modules/geography/tests/unit/route.mapper.spec.ts deleted file mode 100644 index 0d0ccf5..0000000 --- a/src/modules/geography/tests/unit/route.mapper.spec.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { RouteEntity } from '@modules/geography/core/domain/route.entity'; -import { RouteMapper } from '@modules/geography/route.mapper'; -import { Test } from '@nestjs/testing'; - -describe('Route Mapper', () => { - let routeMapper: RouteMapper; - - beforeAll(async () => { - const module = await Test.createTestingModule({ - providers: [RouteMapper], - }).compile(); - routeMapper = module.get(RouteMapper); - }); - - it('should be defined', () => { - expect(routeMapper).toBeDefined(); - }); - - it('should map domain entity to response', async () => { - const now = new Date(); - const routeEntity: RouteEntity = new RouteEntity({ - id: '047a6ecf-23d4-4d3e-877c-3225d560a8da', - createdAt: now, - updatedAt: now, - props: { - distance: 23000, - duration: 900, - fwdAzimuth: 283, - backAzimuth: 93, - distanceAzimuth: 19840, - points: [ - { - lon: 6.1765103, - lat: 48.689446, - }, - { - lon: 2.3523, - lat: 48.8567, - }, - ], - }, - }); - expect(routeMapper.toResponse(routeEntity).distance).toBe(23000); - }); -});