From efea6fe13c82a4e3cd7b42ff9ad3c3f914170cb3 Mon Sep 17 00:00:00 2001 From: sbriat Date: Tue, 19 Sep 2023 10:54:23 +0200 Subject: [PATCH] improve tests --- .../match/completer/route.completer.ts | 2 +- .../ad/infrastructure/route-provider.ts | 9 +- .../unit/core/algorithm.abstract.spec.ts | 117 +++++++++++++ .../tests/unit/core/route.completer.spec.ts | 154 ++++++++++++++++++ .../infrastructure/route-provider.spec.ts | 42 ++++- .../get-detailed-route.controller.spec.ts | 63 +++++++ 6 files changed, 383 insertions(+), 4 deletions(-) create mode 100644 src/modules/ad/tests/unit/core/algorithm.abstract.spec.ts create mode 100644 src/modules/ad/tests/unit/core/route.completer.spec.ts create mode 100644 src/modules/geography/tests/unit/interface/get-detailed-route.controller.spec.ts 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 c8e61a9..3a61a20 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 @@ -29,7 +29,7 @@ export class RouteCompleter extends Completer { break; case RouteCompleterType.DETAILED: const detailedCandidateRoute = - await this.query.routeProvider.getBasic( + await this.query.routeProvider.getDetailed( (candidate.getProps().carpoolSteps as WayStep[]).map( (wayStep: WayStep) => wayStep.point, ), diff --git a/src/modules/ad/infrastructure/route-provider.ts b/src/modules/ad/infrastructure/route-provider.ts index dac1d05..ada7160 100644 --- a/src/modules/ad/infrastructure/route-provider.ts +++ b/src/modules/ad/infrastructure/route-provider.ts @@ -1,7 +1,10 @@ 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 } from '../ad.di-tokens'; +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() @@ -9,6 +12,8 @@ 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 => @@ -17,7 +22,7 @@ export class RouteProvider implements RouteProviderPort { }); getDetailed = async (waypoints: Point[]): Promise => - await this.getBasicRouteController.get({ + await this.getDetailedRouteController.get({ waypoints, }); } diff --git a/src/modules/ad/tests/unit/core/algorithm.abstract.spec.ts b/src/modules/ad/tests/unit/core/algorithm.abstract.spec.ts new file mode 100644 index 0000000..9c0c01d --- /dev/null +++ b/src/modules/ad/tests/unit/core/algorithm.abstract.spec.ts @@ -0,0 +1,117 @@ +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, +} from '@modules/ad/core/application/queries/match/algorithm.abstract'; +import { MatchQuery } from '@modules/ad/core/application/queries/match/match.query'; +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'; + +const originWaypoint: Waypoint = { + position: 0, + lat: 48.689445, + lon: 6.17651, + houseNumber: '5', + street: 'Avenue Foch', + locality: 'Nancy', + postalCode: '54000', + country: 'France', +}; +const destinationWaypoint: Waypoint = { + position: 1, + lat: 48.8566, + lon: 2.3522, + locality: 'Paris', + postalCode: '75000', + country: 'France', +}; + +const mockRouteProvider: RouteProviderPort = { + getBasic: jest.fn(), + getDetailed: jest.fn(), +}; + +const matchQuery = new MatchQuery( + { + frequency: Frequency.PUNCTUAL, + fromDate: '2023-08-28', + toDate: '2023-08-28', + schedule: [ + { + time: '01:05', + }, + ], + waypoints: [originWaypoint, destinationWaypoint], + }, + mockRouteProvider, +); + +const mockAdRepository: AdRepositoryPort = { + insertExtra: jest.fn(), + findOneById: jest.fn(), + findOne: jest.fn(), + insert: jest.fn(), + update: jest.fn(), + updateWhere: jest.fn(), + delete: jest.fn(), + count: jest.fn(), + healthCheck: jest.fn(), + getCandidateAds: jest.fn(), +}; + +class SomeSelector extends Selector { + select = async (): Promise => [ + CandidateEntity.create({ + id: 'cc260669-1c6d-441f-80a5-19cd59afb777', + role: Role.DRIVER, + driverWaypoints: [ + { + lat: 48.678454, + lon: 6.189745, + }, + { + lat: 48.84877, + lon: 2.398457, + }, + ], + passengerWaypoints: [ + { + lat: 48.849445, + lon: 6.68651, + }, + { + lat: 47.18746, + lon: 2.89742, + }, + ], + driverDistance: 350145, + driverDuration: 13548, + spacetimeDetourRatio: { + maxDistanceDetourRatio: 0.3, + maxDurationDetourRatio: 0.3, + }, + }), + ]; +} + +class SomeAlgorithm extends Algorithm { + constructor( + protected readonly query: MatchQuery, + protected readonly repository: AdRepositoryPort, + ) { + super(query, repository); + this.selector = new SomeSelector(query, repository); + this.processors = []; + } +} + +describe('Abstract Algorithm', () => { + it('should return matches', async () => { + const someAlgorithm = new SomeAlgorithm(matchQuery, mockAdRepository); + const matches: MatchEntity[] = await someAlgorithm.match(); + expect(matches).toHaveLength(1); + }); +}); diff --git a/src/modules/ad/tests/unit/core/route.completer.spec.ts b/src/modules/ad/tests/unit/core/route.completer.spec.ts new file mode 100644 index 0000000..5aff29e --- /dev/null +++ b/src/modules/ad/tests/unit/core/route.completer.spec.ts @@ -0,0 +1,154 @@ +import { + RouteCompleter, + RouteCompleterType, +} from '@modules/ad/core/application/queries/match/completer/route.completer'; +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, 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 { Point } from '@modules/ad/core/domain/value-objects/point.value-object'; + +const originWaypoint: Waypoint = { + position: 0, + lat: 48.689445, + lon: 6.17651, + houseNumber: '5', + street: 'Avenue Foch', + locality: 'Nancy', + postalCode: '54000', + country: 'France', +}; +const destinationWaypoint: Waypoint = { + position: 1, + lat: 48.8566, + lon: 2.3522, + locality: 'Paris', + postalCode: '75000', + country: 'France', +}; + +const matchQuery = new MatchQuery( + { + algorithmType: AlgorithmType.PASSENGER_ORIENTED, + driver: true, + passenger: true, + frequency: Frequency.PUNCTUAL, + fromDate: '2023-08-28', + toDate: '2023-08-28', + schedule: [ + { + time: '07:05', + }, + ], + 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: [], + })), + }, +); + +const candidate: CandidateEntity = CandidateEntity.create({ + id: 'cc260669-1c6d-441f-80a5-19cd59afb777', + role: Role.DRIVER, + driverWaypoints: [ + { + lat: 48.678454, + lon: 6.189745, + }, + { + lat: 48.84877, + lon: 2.398457, + }, + ], + passengerWaypoints: [ + { + lat: 48.689445, + lon: 6.17651, + }, + { + lat: 48.8566, + lon: 2.3522, + }, + ], + driverDistance: 350145, + driverDuration: 13548, + spacetimeDetourRatio: { + maxDistanceDetourRatio: 0.3, + maxDurationDetourRatio: 0.3, + }, +}).setCarpoolPath([ + { + point: new Point({ + lat: 48.689445, + lon: 6.17651, + }), + actors: [ + new Actor({ + role: Role.DRIVER, + target: Target.START, + }), + new Actor({ + role: Role.PASSENGER, + target: Target.START, + }), + ], + }, + { + point: new Point({ + lat: 48.8566, + lon: 2.3522, + }), + actors: [ + new Actor({ + role: Role.DRIVER, + target: Target.FINISH, + }), + new Actor({ + role: Role.PASSENGER, + target: Target.FINISH, + }), + ], + }, +]); + +describe('Route completer', () => { + it('should complete candidates with basic setting', async () => { + const routeCompleter: RouteCompleter = new RouteCompleter( + matchQuery, + RouteCompleterType.BASIC, + ); + const completedCandidates: CandidateEntity[] = + await routeCompleter.complete([candidate]); + expect(completedCandidates.length).toBe(1); + expect(completedCandidates[0].getProps().distance).toBe(350101); + }); + it('should complete candidates with detailed setting', async () => { + const routeCompleter: RouteCompleter = new RouteCompleter( + matchQuery, + RouteCompleterType.DETAILED, + ); + const completedCandidates: CandidateEntity[] = + await routeCompleter.complete([candidate]); + expect(completedCandidates.length).toBe(1); + expect(completedCandidates[0].getProps().distance).toBe(350102); + }); +}); diff --git a/src/modules/ad/tests/unit/infrastructure/route-provider.spec.ts b/src/modules/ad/tests/unit/infrastructure/route-provider.spec.ts index 641d5b3..6e8ec9a 100644 --- a/src/modules/ad/tests/unit/infrastructure/route-provider.spec.ts +++ b/src/modules/ad/tests/unit/infrastructure/route-provider.spec.ts @@ -1,4 +1,7 @@ -import { AD_GET_BASIC_ROUTE_CONTROLLER } from '@modules/ad/ad.di-tokens'; +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 { RouteProvider } from '@modules/ad/infrastructure/route-provider'; import { GetRouteControllerPort } from '@modules/geography/core/application/ports/get-route-controller.port'; @@ -38,6 +41,30 @@ const mockGetBasicRouteController: GetRouteControllerPort = { })), }; +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; @@ -49,6 +76,10 @@ describe('Route provider', () => { provide: AD_GET_BASIC_ROUTE_CONTROLLER, useValue: mockGetBasicRouteController, }, + { + provide: AD_GET_DETAILED_ROUTE_CONTROLLER, + useValue: mockGetDetailedRouteController, + }, ], }).compile(); @@ -67,4 +98,13 @@ describe('Route provider', () => { 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/geography/tests/unit/interface/get-detailed-route.controller.spec.ts b/src/modules/geography/tests/unit/interface/get-detailed-route.controller.spec.ts new file mode 100644 index 0000000..e61e04e --- /dev/null +++ b/src/modules/geography/tests/unit/interface/get-detailed-route.controller.spec.ts @@ -0,0 +1,63 @@ +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); + }); +});