From 3c543089354e711a4082b42d9c99d4c7e98f9b26 Mon Sep 17 00:00:00 2001 From: sbriat Date: Fri, 21 Apr 2023 17:17:14 +0200 Subject: [PATCH] better testing --- .../matcher/adapters/secondaries/geodesic.ts | 6 +- .../secondaries/graphhopper-georouter.ts | 100 ++++++++--------- .../matcher/domain/dtos/match.request.ts | 6 +- .../entities/ecosystem/algorithm-settings.ts | 16 +-- .../domain/entities/ecosystem/geography.ts | 85 +++++++------- .../domain/entities/ecosystem/person.ts | 30 +++-- .../domain/entities/ecosystem/requirement.ts | 4 +- .../domain/entities/ecosystem/route.ts | 12 +- .../matcher/domain/entities/ecosystem/time.ts | 105 +++++++++--------- .../factory/algorithm-factory-creator.ts | 18 +++ .../factory/algorithm-factory.abstract.ts | 8 +- .../domain/entities/engine/factory/classic.ts | 14 +-- .../matcher/domain/entities/engine/matcher.ts | 19 ++-- .../completer/route.completer.processor.ts | 12 +- .../engine/processor/processor.abstract.ts | 4 +- .../engine/selector/selector.abstract.ts | 4 +- .../algorithm-settings-request.interface.ts | 4 +- .../matcher/domain/types/algorithm.enum.ts | 2 +- .../types/default-algorithm-settings.type.ts | 4 +- src/modules/matcher/matcher.module.ts | 2 + .../unit/domain/ecosystem/geography.spec.ts | 1 - .../tests/unit/domain/engine/matcher.spec.ts | 21 +++- .../tests/unit/domain/match.usecase.spec.ts | 4 +- .../tests/unit/queries/match.query.spec.ts | 10 +- 24 files changed, 260 insertions(+), 231 deletions(-) create mode 100644 src/modules/matcher/domain/entities/engine/factory/algorithm-factory-creator.ts diff --git a/src/modules/matcher/adapters/secondaries/geodesic.ts b/src/modules/matcher/adapters/secondaries/geodesic.ts index f2a9642..3743ac6 100644 --- a/src/modules/matcher/adapters/secondaries/geodesic.ts +++ b/src/modules/matcher/adapters/secondaries/geodesic.ts @@ -4,10 +4,10 @@ import { Geodesic, GeodesicClass } from 'geographiclib-geodesic'; @Injectable() export class MatcherGeodesic implements IGeodesic { - _geod: GeodesicClass; + private geod: GeodesicClass; constructor() { - this._geod = Geodesic.WGS84; + this.geod = Geodesic.WGS84; } inverse = ( @@ -16,7 +16,7 @@ export class MatcherGeodesic implements IGeodesic { lon2: number, lat2: number, ): { azimuth: number; distance: number } => { - const { azi2: azimuth, s12: distance } = this._geod.Inverse( + const { azi2: azimuth, s12: distance } = this.geod.Inverse( lat1, lon1, lat2, diff --git a/src/modules/matcher/adapters/secondaries/graphhopper-georouter.ts b/src/modules/matcher/adapters/secondaries/graphhopper-georouter.ts index f660632..33c79d8 100644 --- a/src/modules/matcher/adapters/secondaries/graphhopper-georouter.ts +++ b/src/modules/matcher/adapters/secondaries/graphhopper-georouter.ts @@ -16,77 +16,73 @@ import { @Injectable() export class GraphhopperGeorouter implements IGeorouter { - _url: string; - _urlArgs: Array; - _withTime: boolean; - _withPoints: boolean; - _withDistance: boolean; - _paths: Array; - _httpService: HttpService; - _geodesic: IGeodesic; + private url: string; + private urlArgs: Array; + private withTime: boolean; + private withPoints: boolean; + private withDistance: boolean; + private paths: Array; + private httpService: HttpService; + private geodesic: IGeodesic; constructor(url: string, httpService: HttpService, geodesic: IGeodesic) { - this._url = url + '/route?'; - this._httpService = httpService; - this._geodesic = geodesic; + this.url = url + '/route?'; + this.httpService = httpService; + this.geodesic = geodesic; } route = async ( paths: Array, settings: GeorouterSettings, ): Promise> => { - this._setDefaultUrlArgs(); - this._setWithTime(settings.withTime); - this._setWithPoints(settings.withPoints); - this._setWithDistance(settings.withDistance); - this._paths = paths; - return await this._getRoutes(); + this.setDefaultUrlArgs(); + this.setWithTime(settings.withTime); + this.setWithPoints(settings.withPoints); + this.setWithDistance(settings.withDistance); + this.paths = paths; + return await this.getRoutes(); }; - _setDefaultUrlArgs = (): void => { - this._urlArgs = [ - 'vehicle=car', - 'weighting=fastest', - 'points_encoded=false', - ]; + private setDefaultUrlArgs = (): void => { + this.urlArgs = ['vehicle=car', 'weighting=fastest', 'points_encoded=false']; }; - _setWithTime = (withTime: boolean): void => { - this._withTime = withTime; + private setWithTime = (withTime: boolean): void => { + this.withTime = withTime; if (withTime) { - this._urlArgs.push('details=time'); + this.urlArgs.push('details=time'); } }; - _setWithPoints = (withPoints: boolean): void => { - this._withPoints = withPoints; + private setWithPoints = (withPoints: boolean): void => { + this.withPoints = withPoints; if (!withPoints) { - this._urlArgs.push('calc_points=false'); + this.urlArgs.push('calc_points=false'); } }; - _setWithDistance = (withDistance: boolean): void => { - this._withDistance = withDistance; + private setWithDistance = (withDistance: boolean): void => { + this.withDistance = withDistance; if (withDistance) { - this._urlArgs.push('instructions=true'); + this.urlArgs.push('instructions=true'); } else { - this._urlArgs.push('instructions=false'); + this.urlArgs.push('instructions=false'); } }; - _getRoutes = async (): Promise> => { + private getRoutes = async (): Promise> => { const routes = Promise.all( - this._paths.map(async (path) => { + this.paths.map(async (path) => { const url: string = [ - this._getUrl(), + this.getUrl(), '&point=', path.points .map((point) => [point.lat, point.lon].join()) .join('&point='), ].join(''); const route = await lastValueFrom( - this._httpService.get(url).pipe( - map((res) => (res.data ? this._createRoute(res) : undefined)), + this.httpService.get(url).pipe( + map((res) => (res.data ? this.createRoute(res) : undefined)), catchError((error: AxiosError) => { throw new MatcherException( MatcherExceptionCode.INTERNAL, @@ -104,12 +100,14 @@ export class GraphhopperGeorouter implements IGeorouter { return routes; }; - _getUrl = (): string => { - return [this._url, this._urlArgs.join('&')].join(''); + private getUrl = (): string => { + return [this.url, this.urlArgs.join('&')].join(''); }; - _createRoute = (response: AxiosResponse): Route => { - const route = new Route(this._geodesic); + private createRoute = ( + response: AxiosResponse, + ): Route => { + const route = new Route(this.geodesic); if (response.data.paths && response.data.paths[0]) { const shortestPath = response.data.paths[0]; route.distance = shortestPath.distance ?? 0; @@ -131,7 +129,7 @@ export class GraphhopperGeorouter implements IGeorouter { if (shortestPath.instructions) instructions = shortestPath.instructions; route.setSpacetimePoints( - this._generateSpacetimePoints( + this.generateSpacetimePoints( shortestPath.points.coordinates, shortestPath.snapped_waypoints.coordinates, shortestPath.details.time, @@ -144,15 +142,15 @@ export class GraphhopperGeorouter implements IGeorouter { return route; }; - _generateSpacetimePoints = ( + private generateSpacetimePoints = ( points: Array>, snappedWaypoints: Array>, durations: Array>, instructions: Array, ): Array => { - const indices = this._getIndices(points, snappedWaypoints); - const times = this._getTimes(durations, indices); - const distances = this._getDistances(instructions, indices); + const indices = this.getIndices(points, snappedWaypoints); + const times = this.getTimes(durations, indices); + const distances = this.getDistances(instructions, indices); return indices.map( (index) => new SpacetimePoint( @@ -163,7 +161,7 @@ export class GraphhopperGeorouter implements IGeorouter { ); }; - _getIndices = ( + private getIndices = ( points: Array>, snappedWaypoints: Array>, ): Array => { @@ -195,7 +193,7 @@ export class GraphhopperGeorouter implements IGeorouter { .filter((element) => element.index == -1); for (const index in points) { for (const missedWaypoint of missedWaypoints) { - const inverse = this._geodesic.inverse( + const inverse = this.geodesic.inverse( missedWaypoint.waypoint[0], missedWaypoint.waypoint[1], points[index][0], @@ -213,7 +211,7 @@ export class GraphhopperGeorouter implements IGeorouter { return indices; }; - _getTimes = ( + private getTimes = ( durations: Array>, indices: Array, ): Array<{ index: number; duration: number }> => { @@ -263,7 +261,7 @@ export class GraphhopperGeorouter implements IGeorouter { return times; }; - _getDistances = ( + private getDistances = ( instructions: Array, indices: Array, ): Array<{ index: number; distance: number }> => { diff --git a/src/modules/matcher/domain/dtos/match.request.ts b/src/modules/matcher/domain/dtos/match.request.ts index 14d7339..4cb1ff2 100644 --- a/src/modules/matcher/domain/dtos/match.request.ts +++ b/src/modules/matcher/domain/dtos/match.request.ts @@ -13,7 +13,7 @@ import { AutoMap } from '@automapper/classes'; import { Point } from '../types/point.type'; import { Schedule } from '../types/schedule.type'; import { MarginDurations } from '../types/margin-durations.type'; -import { Algorithm } from '../types/algorithm.enum'; +import { AlgorithmType } from '../types/algorithm.enum'; import { IRequestTime } from '../interfaces/time-request.interface'; import { IRequestPerson } from '../interfaces/person-request.interface'; import { IRequestGeography } from '../interfaces/geography-request.interface'; @@ -89,9 +89,9 @@ export class MatchRequest strict: boolean; @IsOptional() - @IsEnum(Algorithm) + @IsEnum(AlgorithmType) @AutoMap() - algorithm: Algorithm; + algorithm: AlgorithmType; @IsOptional() @IsNumber() diff --git a/src/modules/matcher/domain/entities/ecosystem/algorithm-settings.ts b/src/modules/matcher/domain/entities/ecosystem/algorithm-settings.ts index aa12abf..a6d4963 100644 --- a/src/modules/matcher/domain/entities/ecosystem/algorithm-settings.ts +++ b/src/modules/matcher/domain/entities/ecosystem/algorithm-settings.ts @@ -1,14 +1,14 @@ import { IRequestAlgorithmSettings } from '../../interfaces/algorithm-settings-request.interface'; import { DefaultAlgorithmSettings } from '../../types/default-algorithm-settings.type'; -import { Algorithm } from '../../types/algorithm.enum'; +import { AlgorithmType } from '../../types/algorithm.enum'; import { TimingFrequency } from '../../types/timing'; import { ICreateGeorouter } from '../../interfaces/georouter-creator.interface'; import { IGeorouter } from '../../interfaces/georouter.interface'; export class AlgorithmSettings { - _algorithmSettingsRequest: IRequestAlgorithmSettings; - _strict: boolean; - algorithm: Algorithm; + private algorithmSettingsRequest: IRequestAlgorithmSettings; + private strict: boolean; + algorithmType: AlgorithmType; restrict: TimingFrequency; remoteness: number; useProportion: boolean; @@ -25,10 +25,10 @@ export class AlgorithmSettings { frequency: TimingFrequency, georouterCreator: ICreateGeorouter, ) { - this._algorithmSettingsRequest = algorithmSettingsRequest; - this.algorithm = + this.algorithmSettingsRequest = algorithmSettingsRequest; + this.algorithmType = algorithmSettingsRequest.algorithm ?? defaultAlgorithmSettings.algorithm; - this._strict = + this.strict = algorithmSettingsRequest.strict ?? defaultAlgorithmSettings.strict; this.remoteness = algorithmSettingsRequest.remoteness ? Math.abs(algorithmSettingsRequest.remoteness) @@ -55,7 +55,7 @@ export class AlgorithmSettings { defaultAlgorithmSettings.georouterType, defaultAlgorithmSettings.georouterUrl, ); - if (this._strict) { + if (this.strict) { this.restrict = frequency; } } diff --git a/src/modules/matcher/domain/entities/ecosystem/geography.ts b/src/modules/matcher/domain/entities/ecosystem/geography.ts index e7cf586..892e904 100644 --- a/src/modules/matcher/domain/entities/ecosystem/geography.ts +++ b/src/modules/matcher/domain/entities/ecosystem/geography.ts @@ -16,9 +16,9 @@ import { Step } from '../../types/step.enum'; import { Path } from '../../types/path.type'; export class Geography { - _geographyRequest: IRequestGeography; - _person: Person; - _points: Array; + private geographyRequest: IRequestGeography; + private person: Person; + private points: Array; originType: PointType; destinationType: PointType; timezones: Array; @@ -30,18 +30,18 @@ export class Geography { defaultTimezone: string, person: Person, ) { - this._geographyRequest = geographyRequest; - this._person = person; - this._points = []; + this.geographyRequest = geographyRequest; + this.person = person; + this.points = []; this.originType = undefined; this.destinationType = undefined; this.timezones = [defaultTimezone]; } init = (): void => { - this._validateWaypoints(); - this._setTimezones(); - this._setPointTypes(); + this.validateWaypoints(); + this.setTimezones(); + this.setPointTypes(); }; createRoutes = async ( @@ -52,14 +52,14 @@ export class Geography { let passengerWaypoints: Array = []; const paths: Array = []; if (roles.includes(Role.DRIVER) && roles.includes(Role.PASSENGER)) { - if (this._points.length == 2) { + if (this.points.length == 2) { // 2 points => same route for driver and passenger const commonPath: Path = { key: RouteKey.COMMON, - points: this._points, + points: this.points, }; - driverWaypoints = this._createWaypoints(commonPath.points, Role.DRIVER); - passengerWaypoints = this._createWaypoints( + driverWaypoints = this.createWaypoints(commonPath.points, Role.DRIVER); + passengerWaypoints = this.createWaypoints( commonPath.points, Role.PASSENGER, ); @@ -67,14 +67,14 @@ export class Geography { } else { const driverPath: Path = { key: RouteKey.DRIVER, - points: this._points, + points: this.points, }; - driverWaypoints = this._createWaypoints(driverPath.points, Role.DRIVER); + driverWaypoints = this.createWaypoints(driverPath.points, Role.DRIVER); const passengerPath: Path = { key: RouteKey.PASSENGER, - points: [this._points[0], this._points[this._points.length - 1]], + points: [this.points[0], this.points[this.points.length - 1]], }; - passengerWaypoints = this._createWaypoints( + passengerWaypoints = this.createWaypoints( passengerPath.points, Role.PASSENGER, ); @@ -83,16 +83,16 @@ export class Geography { } else if (roles.includes(Role.DRIVER)) { const driverPath: Path = { key: RouteKey.DRIVER, - points: this._points, + points: this.points, }; - driverWaypoints = this._createWaypoints(driverPath.points, Role.DRIVER); + driverWaypoints = this.createWaypoints(driverPath.points, Role.DRIVER); paths.push(driverPath); } else if (roles.includes(Role.PASSENGER)) { const passengerPath: Path = { key: RouteKey.PASSENGER, - points: [this._points[0], this._points[this._points.length - 1]], + points: [this.points[0], this.points[this.points.length - 1]], }; - passengerWaypoints = this._createWaypoints( + passengerWaypoints = this.createWaypoints( passengerPath.points, Role.PASSENGER, ); @@ -128,58 +128,61 @@ export class Geography { } }; - _validateWaypoints = (): void => { - if (this._geographyRequest.waypoints.length < 2) { + private validateWaypoints = (): void => { + if (this.geographyRequest.waypoints.length < 2) { throw new MatcherException( MatcherExceptionCode.INVALID_ARGUMENT, 'At least 2 waypoints are required', ); } - this._geographyRequest.waypoints.map((point) => { - if (!this._isValidPoint(point)) { + this.geographyRequest.waypoints.map((point) => { + if (!this.isValidPoint(point)) { throw new MatcherException( MatcherExceptionCode.INVALID_ARGUMENT, `Waypoint { Lon: ${point.lon}, Lat: ${point.lat} } is not valid`, ); } - this._points.push(point); + this.points.push(point); }); }; - _setTimezones = (): void => { + private setTimezones = (): void => { this.timezones = find( - this._geographyRequest.waypoints[0].lat, - this._geographyRequest.waypoints[0].lon, + this.geographyRequest.waypoints[0].lat, + this.geographyRequest.waypoints[0].lon, ); }; - _setPointTypes = (): void => { + private setPointTypes = (): void => { this.originType = - this._geographyRequest.waypoints[0].type ?? PointType.OTHER; + this.geographyRequest.waypoints[0].type ?? PointType.OTHER; this.destinationType = - this._geographyRequest.waypoints[ - this._geographyRequest.waypoints.length - 1 + this.geographyRequest.waypoints[ + this.geographyRequest.waypoints.length - 1 ].type ?? PointType.OTHER; }; - _isValidPoint = (point: Point): boolean => - this._isValidLongitude(point.lon) && this._isValidLatitude(point.lat); + private isValidPoint = (point: Point): boolean => + this.isValidLongitude(point.lon) && this.isValidLatitude(point.lat); - _isValidLongitude = (longitude: number): boolean => + private isValidLongitude = (longitude: number): boolean => longitude >= -180 && longitude <= 180; - _isValidLatitude = (latitude: number): boolean => + private isValidLatitude = (latitude: number): boolean => latitude >= -90 && latitude <= 90; - _createWaypoints = (points: Array, role: Role): Array => { + private createWaypoints = ( + points: Array, + role: Role, + ): Array => { return points.map((point, index) => { const waypoint = new Waypoint(point); if (index == 0) { - waypoint.addActor(new Actor(this._person, role, Step.START)); + waypoint.addActor(new Actor(this.person, role, Step.START)); } else if (index == points.length - 1) { - waypoint.addActor(new Actor(this._person, role, Step.FINISH)); + waypoint.addActor(new Actor(this.person, role, Step.FINISH)); } else { - waypoint.addActor(new Actor(this._person, role, Step.INTERMEDIATE)); + waypoint.addActor(new Actor(this.person, role, Step.INTERMEDIATE)); } return waypoint; }); diff --git a/src/modules/matcher/domain/entities/ecosystem/person.ts b/src/modules/matcher/domain/entities/ecosystem/person.ts index 7340d07..c6baa02 100644 --- a/src/modules/matcher/domain/entities/ecosystem/person.ts +++ b/src/modules/matcher/domain/entities/ecosystem/person.ts @@ -1,9 +1,9 @@ import { IRequestPerson } from '../../interfaces/person-request.interface'; export class Person { - _personRequest: IRequestPerson; - _defaultIdentifier: number; - _defaultMarginDuration: number; + private personRequest: IRequestPerson; + private defaultIdentifier: number; + private defaultMarginDuration: number; identifier: number; marginDurations: Array; @@ -12,23 +12,21 @@ export class Person { defaultIdentifier: number, defaultMarginDuration: number, ) { - this._personRequest = personRequest; - this._defaultIdentifier = defaultIdentifier; - this._defaultMarginDuration = defaultMarginDuration; + this.personRequest = personRequest; + this.defaultIdentifier = defaultIdentifier; + this.defaultMarginDuration = defaultMarginDuration; } init = (): void => { - this.setIdentifier( - this._personRequest.identifier ?? this._defaultIdentifier, - ); + this.setIdentifier(this.personRequest.identifier ?? this.defaultIdentifier); this.setMarginDurations([ - this._defaultMarginDuration, - this._defaultMarginDuration, - this._defaultMarginDuration, - this._defaultMarginDuration, - this._defaultMarginDuration, - this._defaultMarginDuration, - this._defaultMarginDuration, + this.defaultMarginDuration, + this.defaultMarginDuration, + this.defaultMarginDuration, + this.defaultMarginDuration, + this.defaultMarginDuration, + this.defaultMarginDuration, + this.defaultMarginDuration, ]); }; diff --git a/src/modules/matcher/domain/entities/ecosystem/requirement.ts b/src/modules/matcher/domain/entities/ecosystem/requirement.ts index 40db4c6..7100667 100644 --- a/src/modules/matcher/domain/entities/ecosystem/requirement.ts +++ b/src/modules/matcher/domain/entities/ecosystem/requirement.ts @@ -1,12 +1,12 @@ import { IRequestRequirement } from '../../interfaces/requirement-request.interface'; export class Requirement { - _requirementRequest: IRequestRequirement; + private requirementRequest: IRequestRequirement; seatsDriver: number; seatsPassenger: number; constructor(requirementRequest: IRequestRequirement, defaultSeats: number) { - this._requirementRequest = requirementRequest; + this.requirementRequest = requirementRequest; this.seatsDriver = requirementRequest.seatsDriver ?? defaultSeats; this.seatsPassenger = requirementRequest.seatsPassenger ?? 1; } diff --git a/src/modules/matcher/domain/entities/ecosystem/route.ts b/src/modules/matcher/domain/entities/ecosystem/route.ts index d468187..d2b1238 100644 --- a/src/modules/matcher/domain/entities/ecosystem/route.ts +++ b/src/modules/matcher/domain/entities/ecosystem/route.ts @@ -12,7 +12,7 @@ export class Route { waypoints: Array; points: Array; spacetimePoints: Array; - _geodesic: IGeodesic; + private geodesic: IGeodesic; constructor(geodesic: IGeodesic) { this.distance = undefined; @@ -23,25 +23,25 @@ export class Route { this.waypoints = []; this.points = []; this.spacetimePoints = []; - this._geodesic = geodesic; + this.geodesic = geodesic; } setWaypoints = (waypoints: Array): void => { this.waypoints = waypoints; - this._setAzimuth(waypoints.map((waypoint) => waypoint.point)); + this.setAzimuth(waypoints.map((waypoint) => waypoint.point)); }; setPoints = (points: Array): void => { this.points = points; - this._setAzimuth(points); + this.setAzimuth(points); }; setSpacetimePoints = (spacetimePoints: Array): void => { this.spacetimePoints = spacetimePoints; }; - _setAzimuth = (points: Array): void => { - const inverse = this._geodesic.inverse( + private setAzimuth = (points: Array): void => { + const inverse = this.geodesic.inverse( points[0].lon, points[0].lat, points[points.length - 1].lon, diff --git a/src/modules/matcher/domain/entities/ecosystem/time.ts b/src/modules/matcher/domain/entities/ecosystem/time.ts index 7195cf2..183a69a 100644 --- a/src/modules/matcher/domain/entities/ecosystem/time.ts +++ b/src/modules/matcher/domain/entities/ecosystem/time.ts @@ -8,9 +8,9 @@ import { TimingDays, TimingFrequency, Days } from '../../types/timing'; import { Schedule } from '../../types/schedule.type'; export class Time { - _timeRequest: IRequestTime; - _defaultMarginDuration: number; - _defaultValidityDuration: number; + private timeRequest: IRequestTime; + private defaultMarginDuration: number; + private defaultValidityDuration: number; frequency: TimingFrequency; fromDate: Date; toDate: Date; @@ -22,9 +22,9 @@ export class Time { defaultMarginDuration: number, defaultValidityDuration: number, ) { - this._timeRequest = timeRequest; - this._defaultMarginDuration = defaultMarginDuration; - this._defaultValidityDuration = defaultValidityDuration; + this.timeRequest = timeRequest; + this.defaultMarginDuration = defaultMarginDuration; + this.defaultValidityDuration = defaultValidityDuration; this.schedule = {}; this.marginDurations = { mon: defaultMarginDuration, @@ -38,16 +38,16 @@ export class Time { } init = (): void => { - this._validateBaseDate(); - this._validatePunctualRequest(); - this._validateRecurrentRequest(); - this._setPunctualRequest(); - this._setRecurrentRequest(); - this._setMargindurations(); + this.validateBaseDate(); + this.validatePunctualRequest(); + this.validateRecurrentRequest(); + this.setPunctualRequest(); + this.setRecurrentRequest(); + this.setMargindurations(); }; - _validateBaseDate = (): void => { - if (!this._timeRequest.departure && !this._timeRequest.fromDate) { + private validateBaseDate = (): void => { + if (!this.timeRequest.departure && !this.timeRequest.fromDate) { throw new MatcherException( MatcherExceptionCode.INVALID_ARGUMENT, 'departure or fromDate is required', @@ -55,10 +55,10 @@ export class Time { } }; - _validatePunctualRequest = (): void => { - if (this._timeRequest.departure) { - this.fromDate = this.toDate = new Date(this._timeRequest.departure); - if (!this._isDate(this.fromDate)) { + private validatePunctualRequest = (): void => { + if (this.timeRequest.departure) { + this.fromDate = this.toDate = new Date(this.timeRequest.departure); + if (!this.isDate(this.fromDate)) { throw new MatcherException( MatcherExceptionCode.INVALID_ARGUMENT, 'Wrong departure date', @@ -67,19 +67,19 @@ export class Time { } }; - _validateRecurrentRequest = (): void => { - if (this._timeRequest.fromDate) { - this.fromDate = new Date(this._timeRequest.fromDate); - if (!this._isDate(this.fromDate)) { + private validateRecurrentRequest = (): void => { + if (this.timeRequest.fromDate) { + this.fromDate = new Date(this.timeRequest.fromDate); + if (!this.isDate(this.fromDate)) { throw new MatcherException( MatcherExceptionCode.INVALID_ARGUMENT, 'Wrong fromDate', ); } } - if (this._timeRequest.toDate) { - this.toDate = new Date(this._timeRequest.toDate); - if (!this._isDate(this.toDate)) { + if (this.timeRequest.toDate) { + this.toDate = new Date(this.timeRequest.toDate); + if (!this.isDate(this.toDate)) { throw new MatcherException( MatcherExceptionCode.INVALID_ARGUMENT, 'Wrong toDate', @@ -92,20 +92,20 @@ export class Time { ); } } - if (this._timeRequest.fromDate) { - this._validateSchedule(); + if (this.timeRequest.fromDate) { + this.validateSchedule(); } }; - _validateSchedule = (): void => { - if (!this._timeRequest.schedule) { + private validateSchedule = (): void => { + if (!this.timeRequest.schedule) { throw new MatcherException( MatcherExceptionCode.INVALID_ARGUMENT, 'Schedule is required', ); } if ( - !Object.keys(this._timeRequest.schedule).some((elem) => + !Object.keys(this.timeRequest.schedule).some((elem) => Days.includes(elem), ) ) { @@ -114,9 +114,9 @@ export class Time { 'No valid day in the given schedule', ); } - Object.keys(this._timeRequest.schedule).map((day) => { - const time = new Date('1970-01-01 ' + this._timeRequest.schedule[day]); - if (!this._isDate(time)) { + Object.keys(this.timeRequest.schedule).map((day) => { + const time = new Date('1970-01-01 ' + this.timeRequest.schedule[day]); + if (!this.isDate(time)) { throw new MatcherException( MatcherExceptionCode.INVALID_ARGUMENT, `Wrong time for ${day} in schedule`, @@ -125,36 +125,33 @@ export class Time { }); }; - _setPunctualRequest = (): void => { - if (this._timeRequest.departure) { + private setPunctualRequest = (): void => { + if (this.timeRequest.departure) { this.frequency = TimingFrequency.FREQUENCY_PUNCTUAL; this.schedule[TimingDays[this.fromDate.getDay()]] = this.fromDate.getHours() + ':' + this.fromDate.getMinutes(); } }; - _setRecurrentRequest = (): void => { - if (this._timeRequest.fromDate) { + private setRecurrentRequest = (): void => { + if (this.timeRequest.fromDate) { this.frequency = TimingFrequency.FREQUENCY_RECURRENT; if (!this.toDate) { - this.toDate = this._addDays( - this.fromDate, - this._defaultValidityDuration, - ); + this.toDate = this.addDays(this.fromDate, this.defaultValidityDuration); } - this._setSchedule(); + this.setSchedule(); } }; - _setSchedule = (): void => { - Object.keys(this._timeRequest.schedule).map((day) => { - this.schedule[day] = this._timeRequest.schedule[day]; + private setSchedule = (): void => { + Object.keys(this.timeRequest.schedule).map((day) => { + this.schedule[day] = this.timeRequest.schedule[day]; }); }; - _setMargindurations = (): void => { - if (this._timeRequest.marginDuration) { - const duration = Math.abs(this._timeRequest.marginDuration); + private setMargindurations = (): void => { + if (this.timeRequest.marginDuration) { + const duration = Math.abs(this.timeRequest.marginDuration); this.marginDurations = { mon: duration, tue: duration, @@ -165,9 +162,9 @@ export class Time { sun: duration, }; } - if (this._timeRequest.marginDurations) { + if (this.timeRequest.marginDurations) { if ( - !Object.keys(this._timeRequest.marginDurations).some((elem) => + !Object.keys(this.timeRequest.marginDurations).some((elem) => Days.includes(elem), ) ) { @@ -176,19 +173,19 @@ export class Time { 'No valid day in the given margin durations', ); } - Object.keys(this._timeRequest.marginDurations).map((day) => { + Object.keys(this.timeRequest.marginDurations).map((day) => { this.marginDurations[day] = Math.abs( - this._timeRequest.marginDurations[day], + this.timeRequest.marginDurations[day], ); }); } }; - _isDate = (date: Date): boolean => { + private isDate = (date: Date): boolean => { return date instanceof Date && isFinite(+date); }; - _addDays = (date: Date, days: number): Date => { + private addDays = (date: Date, days: number): Date => { const result = new Date(date); result.setDate(result.getDate() + days); return result; diff --git a/src/modules/matcher/domain/entities/engine/factory/algorithm-factory-creator.ts b/src/modules/matcher/domain/entities/engine/factory/algorithm-factory-creator.ts new file mode 100644 index 0000000..585f96e --- /dev/null +++ b/src/modules/matcher/domain/entities/engine/factory/algorithm-factory-creator.ts @@ -0,0 +1,18 @@ +import { MatchQuery } from 'src/modules/matcher/queries/match.query'; +import { AlgorithmType } from '../../../types/algorithm.enum'; +import { AlgorithmFactory } from './algorithm-factory.abstract'; +import { ClassicAlgorithmFactory } from './classic'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class AlgorithmFactoryCreator { + create = (matchQuery: MatchQuery): AlgorithmFactory => { + let algorithm: AlgorithmFactory; + switch (matchQuery.algorithmSettings.algorithmType) { + case AlgorithmType.CLASSIC: + algorithm = new ClassicAlgorithmFactory(matchQuery); + break; + } + return algorithm; + }; +} diff --git a/src/modules/matcher/domain/entities/engine/factory/algorithm-factory.abstract.ts b/src/modules/matcher/domain/entities/engine/factory/algorithm-factory.abstract.ts index 6c9c1eb..0cc876d 100644 --- a/src/modules/matcher/domain/entities/engine/factory/algorithm-factory.abstract.ts +++ b/src/modules/matcher/domain/entities/engine/factory/algorithm-factory.abstract.ts @@ -4,12 +4,12 @@ import { Candidate } from '../candidate'; import { Selector } from '../selector/selector.abstract'; export abstract class AlgorithmFactory { - _matchQuery: MatchQuery; - _candidates: Array; + protected matchQuery: MatchQuery; + private candidates: Array; constructor(matchQuery: MatchQuery) { - this._matchQuery = matchQuery; - this._candidates = []; + this.matchQuery = matchQuery; + this.candidates = []; } abstract createSelector(): Selector; diff --git a/src/modules/matcher/domain/entities/engine/factory/classic.ts b/src/modules/matcher/domain/entities/engine/factory/classic.ts index d5fdcbe..54880b0 100644 --- a/src/modules/matcher/domain/entities/engine/factory/classic.ts +++ b/src/modules/matcher/domain/entities/engine/factory/classic.ts @@ -9,13 +9,13 @@ import { Selector } from '../selector/selector.abstract'; import { ClassicSelector } from '../selector/classic.selector'; export class ClassicAlgorithmFactory extends AlgorithmFactory { - createSelector = (): Selector => new ClassicSelector(this._matchQuery); + createSelector = (): Selector => new ClassicSelector(this.matchQuery); createProcessors = (): Array => [ - new ClassicWaypointsCompleter(this._matchQuery), - new RouteCompleter(this._matchQuery, true, true, true), - new ClassicGeoFilter(this._matchQuery), - new RouteCompleter(this._matchQuery), - new JourneyCompleter(this._matchQuery), - new ClassicTimeFilter(this._matchQuery), + new ClassicWaypointsCompleter(this.matchQuery), + new RouteCompleter(this.matchQuery, true, true, true), + new ClassicGeoFilter(this.matchQuery), + new RouteCompleter(this.matchQuery), + new JourneyCompleter(this.matchQuery), + new ClassicTimeFilter(this.matchQuery), ]; } diff --git a/src/modules/matcher/domain/entities/engine/matcher.ts b/src/modules/matcher/domain/entities/engine/matcher.ts index 9bce7f8..48648af 100644 --- a/src/modules/matcher/domain/entities/engine/matcher.ts +++ b/src/modules/matcher/domain/entities/engine/matcher.ts @@ -1,24 +1,23 @@ import { Injectable } from '@nestjs/common'; import { MatchQuery } from '../../../queries/match.query'; -import { Algorithm } from '../../types/algorithm.enum'; import { Match } from '../ecosystem/match'; import { Candidate } from './candidate'; import { AlgorithmFactory } from './factory/algorithm-factory.abstract'; -import { ClassicAlgorithmFactory } from './factory/classic'; +import { AlgorithmFactoryCreator } from './factory/algorithm-factory-creator'; @Injectable() export class Matcher { + constructor( + private readonly algorithmFactoryCreator: AlgorithmFactoryCreator, + ) {} + match = async (matchQuery: MatchQuery): Promise> => { - let algorithm: AlgorithmFactory; - switch (matchQuery.algorithmSettings.algorithm) { - case Algorithm.CLASSIC: - algorithm = new ClassicAlgorithmFactory(matchQuery); - break; - } - let candidates: Array = await algorithm + const algorithmFactory: AlgorithmFactory = + this.algorithmFactoryCreator.create(matchQuery); + let candidates: Array = await algorithmFactory .createSelector() .select(); - for (const processor of algorithm.createProcessors()) { + for (const processor of algorithmFactory.createProcessors()) { candidates = processor.execute(candidates); } const match = new Match(); diff --git a/src/modules/matcher/domain/entities/engine/processor/completer/route.completer.processor.ts b/src/modules/matcher/domain/entities/engine/processor/completer/route.completer.processor.ts index a95fff8..5d6c36a 100644 --- a/src/modules/matcher/domain/entities/engine/processor/completer/route.completer.processor.ts +++ b/src/modules/matcher/domain/entities/engine/processor/completer/route.completer.processor.ts @@ -3,9 +3,9 @@ import { Candidate } from '../../candidate'; import { Completer } from './completer.abstract'; export class RouteCompleter extends Completer { - _withPoints: boolean; - _withTime: boolean; - _withDistance: boolean; + private withPoints: boolean; + private withTime: boolean; + private withDistance: boolean; constructor( matchQuery: MatchQuery, @@ -14,9 +14,9 @@ export class RouteCompleter extends Completer { withDistance = false, ) { super(matchQuery); - this._withPoints = withPoints; - this._withTime = withTime; - this._withDistance = withDistance; + this.withPoints = withPoints; + this.withTime = withTime; + this.withDistance = withDistance; } // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/src/modules/matcher/domain/entities/engine/processor/processor.abstract.ts b/src/modules/matcher/domain/entities/engine/processor/processor.abstract.ts index ac679cf..eee4c0c 100644 --- a/src/modules/matcher/domain/entities/engine/processor/processor.abstract.ts +++ b/src/modules/matcher/domain/entities/engine/processor/processor.abstract.ts @@ -2,10 +2,10 @@ import { MatchQuery } from 'src/modules/matcher/queries/match.query'; import { Candidate } from '../candidate'; export abstract class Processor { - _matchQuery: MatchQuery; + private matchQuery: MatchQuery; constructor(matchQuery: MatchQuery) { - this._matchQuery = matchQuery; + this.matchQuery = matchQuery; } abstract execute(candidates: Array): Array; diff --git a/src/modules/matcher/domain/entities/engine/selector/selector.abstract.ts b/src/modules/matcher/domain/entities/engine/selector/selector.abstract.ts index e406925..b2b722e 100644 --- a/src/modules/matcher/domain/entities/engine/selector/selector.abstract.ts +++ b/src/modules/matcher/domain/entities/engine/selector/selector.abstract.ts @@ -2,10 +2,10 @@ import { MatchQuery } from 'src/modules/matcher/queries/match.query'; import { Candidate } from '../candidate'; export abstract class Selector { - _matchQuery: MatchQuery; + private matchQuery: MatchQuery; constructor(matchQuery: MatchQuery) { - this._matchQuery = matchQuery; + this.matchQuery = matchQuery; } abstract select(): Promise>; diff --git a/src/modules/matcher/domain/interfaces/algorithm-settings-request.interface.ts b/src/modules/matcher/domain/interfaces/algorithm-settings-request.interface.ts index 3ab0de8..484ec15 100644 --- a/src/modules/matcher/domain/interfaces/algorithm-settings-request.interface.ts +++ b/src/modules/matcher/domain/interfaces/algorithm-settings-request.interface.ts @@ -1,7 +1,7 @@ -import { Algorithm } from '../types/algorithm.enum'; +import { AlgorithmType } from '../types/algorithm.enum'; export interface IRequestAlgorithmSettings { - algorithm: Algorithm; + algorithm: AlgorithmType; strict: boolean; remoteness: number; useProportion: boolean; diff --git a/src/modules/matcher/domain/types/algorithm.enum.ts b/src/modules/matcher/domain/types/algorithm.enum.ts index 0ed0cbc..52f14bd 100644 --- a/src/modules/matcher/domain/types/algorithm.enum.ts +++ b/src/modules/matcher/domain/types/algorithm.enum.ts @@ -1,3 +1,3 @@ -export enum Algorithm { +export enum AlgorithmType { CLASSIC = 'CLASSIC', } diff --git a/src/modules/matcher/domain/types/default-algorithm-settings.type.ts b/src/modules/matcher/domain/types/default-algorithm-settings.type.ts index 89c0c93..a9edb47 100644 --- a/src/modules/matcher/domain/types/default-algorithm-settings.type.ts +++ b/src/modules/matcher/domain/types/default-algorithm-settings.type.ts @@ -1,7 +1,7 @@ -import { Algorithm } from './algorithm.enum'; +import { AlgorithmType } from './algorithm.enum'; export type DefaultAlgorithmSettings = { - algorithm: Algorithm; + algorithm: AlgorithmType; strict: boolean; remoteness: number; useProportion: boolean; diff --git a/src/modules/matcher/matcher.module.ts b/src/modules/matcher/matcher.module.ts index 4ceca6e..aacb6b2 100644 --- a/src/modules/matcher/matcher.module.ts +++ b/src/modules/matcher/matcher.module.ts @@ -16,6 +16,7 @@ import { GeorouterCreator } from './adapters/secondaries/georouter-creator'; import { HttpModule } from '@nestjs/axios'; import { MatcherGeodesic } from './adapters/secondaries/geodesic'; import { Matcher } from './domain/entities/engine/matcher'; +import { AlgorithmFactoryCreator } from './domain/entities/engine/factory/algorithm-factory-creator'; @Module({ imports: [ @@ -59,6 +60,7 @@ import { Matcher } from './domain/entities/engine/matcher'; GeorouterCreator, MatcherGeodesic, Matcher, + AlgorithmFactoryCreator, ], exports: [], }) diff --git a/src/modules/matcher/tests/unit/domain/ecosystem/geography.spec.ts b/src/modules/matcher/tests/unit/domain/ecosystem/geography.spec.ts index e4d20a1..9f93de6 100644 --- a/src/modules/matcher/tests/unit/domain/ecosystem/geography.spec.ts +++ b/src/modules/matcher/tests/unit/domain/ecosystem/geography.spec.ts @@ -107,7 +107,6 @@ describe('Geography entity', () => { person, ); geography.init(); - expect(geography._points.length).toBe(2); expect(geography.originType).toBe(PointType.LOCALITY); expect(geography.destinationType).toBe(PointType.LOCALITY); }); diff --git a/src/modules/matcher/tests/unit/domain/engine/matcher.spec.ts b/src/modules/matcher/tests/unit/domain/engine/matcher.spec.ts index 3a38ee8..0f1aca7 100644 --- a/src/modules/matcher/tests/unit/domain/engine/matcher.spec.ts +++ b/src/modules/matcher/tests/unit/domain/engine/matcher.spec.ts @@ -1,9 +1,22 @@ -import { Algorithm } from '../../../../domain/types/algorithm.enum'; +import { AlgorithmType } from '../../../../domain/types/algorithm.enum'; import { IDefaultParams } from '../../../../domain/types/default-params.type'; import { MatchRequest } from '../../../../domain/dtos/match.request'; import { MatchQuery } from '../../../../queries/match.query'; import { Matcher } from '../../../../domain/entities/engine/matcher'; +const mockAlgorithmFactoryCreator = { + create: jest.fn().mockReturnValue({ + createSelector: jest.fn().mockReturnValue({ + select: jest.fn(), + }), + createProcessors: jest.fn().mockReturnValue([ + { + execute: jest.fn(), + }, + ]), + }), +}; + const mockGeorouterCreator = { create: jest.fn().mockImplementation(), }; @@ -15,7 +28,7 @@ const defaultParams: IDefaultParams = { DEFAULT_TIMEZONE: 'Europe/Paris', DEFAULT_SEATS: 3, DEFAULT_ALGORITHM_SETTINGS: { - algorithm: Algorithm.CLASSIC, + algorithm: AlgorithmType.CLASSIC, strict: false, remoteness: 15000, useProportion: true, @@ -49,11 +62,11 @@ const matchQuery: MatchQuery = new MatchQuery( describe('Matcher', () => { it('should be defined', () => { - expect(new Matcher()).toBeDefined(); + expect(new Matcher(mockAlgorithmFactoryCreator)).toBeDefined(); }); it('should return matches', async () => { - const matcher = new Matcher(); + const matcher = new Matcher(mockAlgorithmFactoryCreator); const matches = await matcher.match(matchQuery); expect(matches.length).toBe(1); }); diff --git a/src/modules/matcher/tests/unit/domain/match.usecase.spec.ts b/src/modules/matcher/tests/unit/domain/match.usecase.spec.ts index d3d2b8c..c5e0fda 100644 --- a/src/modules/matcher/tests/unit/domain/match.usecase.spec.ts +++ b/src/modules/matcher/tests/unit/domain/match.usecase.spec.ts @@ -6,7 +6,7 @@ import { MatchQuery } from '../../../queries/match.query'; import { AutomapperModule } from '@automapper/nestjs'; import { classes } from '@automapper/classes'; import { IDefaultParams } from '../../../domain/types/default-params.type'; -import { Algorithm } from '../../../domain/types/algorithm.enum'; +import { AlgorithmType } from '../../../domain/types/algorithm.enum'; import { Matcher } from '../../../domain/entities/engine/matcher'; import { Match } from '../../../domain/entities/ecosystem/match'; import { @@ -41,7 +41,7 @@ const defaultParams: IDefaultParams = { DEFAULT_TIMEZONE: 'Europe/Paris', DEFAULT_SEATS: 3, DEFAULT_ALGORITHM_SETTINGS: { - algorithm: Algorithm.CLASSIC, + algorithm: AlgorithmType.CLASSIC, strict: false, remoteness: 15000, useProportion: true, diff --git a/src/modules/matcher/tests/unit/queries/match.query.spec.ts b/src/modules/matcher/tests/unit/queries/match.query.spec.ts index 8ed650b..f761bcf 100644 --- a/src/modules/matcher/tests/unit/queries/match.query.spec.ts +++ b/src/modules/matcher/tests/unit/queries/match.query.spec.ts @@ -3,7 +3,7 @@ import { Role } from '../../../domain/types/role.enum'; import { TimingFrequency } from '../../../domain/types/timing'; import { IDefaultParams } from '../../../domain/types/default-params.type'; import { MatchQuery } from '../../../queries/match.query'; -import { Algorithm } from '../../../domain/types/algorithm.enum'; +import { AlgorithmType } from '../../../domain/types/algorithm.enum'; const defaultParams: IDefaultParams = { DEFAULT_IDENTIFIER: 0, @@ -12,7 +12,7 @@ const defaultParams: IDefaultParams = { DEFAULT_TIMEZONE: 'Europe/Paris', DEFAULT_SEATS: 3, DEFAULT_ALGORITHM_SETTINGS: { - algorithm: Algorithm.CLASSIC, + algorithm: AlgorithmType.CLASSIC, strict: false, remoteness: 15000, useProportion: true, @@ -181,7 +181,7 @@ describe('Match query', () => { lon: 3.045432, }, ]; - matchRequest.algorithm = Algorithm.CLASSIC; + matchRequest.algorithm = AlgorithmType.CLASSIC; matchRequest.strict = true; matchRequest.useProportion = true; matchRequest.proportion = 0.45; @@ -195,7 +195,9 @@ describe('Match query', () => { defaultParams, mockGeorouterCreator, ); - expect(matchQuery.algorithmSettings.algorithm).toBe(Algorithm.CLASSIC); + expect(matchQuery.algorithmSettings.algorithmType).toBe( + AlgorithmType.CLASSIC, + ); expect(matchQuery.algorithmSettings.restrict).toBe( TimingFrequency.FREQUENCY_PUNCTUAL, );