basic match query
This commit is contained in:
parent
bca3374255
commit
a98e5b3c83
|
@ -2,7 +2,6 @@ import { Frequency } from '@modules/ad/core/domain/ad.types';
|
|||
|
||||
export type Ad = {
|
||||
id: string;
|
||||
userId: string;
|
||||
driver: boolean;
|
||||
passenger: boolean;
|
||||
frequency: Frequency;
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import { QueryBase } from '@mobicoop/ddd-library';
|
||||
import { Frequency } from '@modules/matcher/core/domain/match.types';
|
||||
import { ScheduleItem } from '../../types/schedule-item';
|
||||
import { Waypoint } from '../../types/waypoint';
|
||||
|
||||
export class MatchQuery extends QueryBase {
|
||||
readonly driver?: boolean;
|
||||
readonly passenger?: boolean;
|
||||
readonly frequency?: Frequency;
|
||||
readonly fromDate: string;
|
||||
readonly toDate: string;
|
||||
readonly schedule: ScheduleItem[];
|
||||
readonly strict?: boolean;
|
||||
readonly waypoints: Waypoint[];
|
||||
|
||||
constructor(props: MatchQuery) {
|
||||
super();
|
||||
this.driver = props.driver;
|
||||
this.passenger = props.passenger;
|
||||
this.frequency = props.frequency;
|
||||
this.fromDate = props.fromDate;
|
||||
this.toDate = props.toDate;
|
||||
this.schedule = props.schedule;
|
||||
this.strict = props.strict;
|
||||
this.waypoints = props.waypoints;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import { Coordinates } from './coordinates';
|
||||
|
||||
export type Address = {
|
||||
name?: string;
|
||||
houseNumber?: string;
|
||||
street?: string;
|
||||
locality?: string;
|
||||
postalCode?: string;
|
||||
country: string;
|
||||
} & Coordinates;
|
|
@ -0,0 +1,4 @@
|
|||
export type Coordinates = {
|
||||
lon: number;
|
||||
lat: number;
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
export type ScheduleItem = {
|
||||
day?: number;
|
||||
time: string;
|
||||
margin?: number;
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
import { Address } from './address';
|
||||
|
||||
export type Waypoint = {
|
||||
position?: number;
|
||||
} & Address;
|
|
@ -0,0 +1,8 @@
|
|||
export enum Frequency {
|
||||
PUNCTUAL = 'PUNCTUAL',
|
||||
RECURRENT = 'RECURRENT',
|
||||
}
|
||||
|
||||
export enum AlgorithmType {
|
||||
CLASSIC = 'CLASSIC',
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { PaginatedResponseDto } from '@mobicoop/ddd-library';
|
||||
import { MatchResponseDto } from './match.response.dto';
|
||||
|
||||
export class MatchPaginatedResponseDto extends PaginatedResponseDto<MatchResponseDto> {
|
||||
readonly data: readonly MatchResponseDto[];
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export class MatchResponseDto {
|
||||
adId: string;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import { IsOptional, IsString } from 'class-validator';
|
||||
import { CoordinatesDto as CoordinatesDto } from './coordinates.dto';
|
||||
|
||||
export class AddressDto extends CoordinatesDto {
|
||||
@IsOptional()
|
||||
name?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
houseNumber?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
street?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
locality?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
postalCode?: string;
|
||||
|
||||
@IsString()
|
||||
country: string;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import { IsLatitude, IsLongitude } from 'class-validator';
|
||||
|
||||
export class CoordinatesDto {
|
||||
@IsLongitude()
|
||||
lon: number;
|
||||
|
||||
@IsLatitude()
|
||||
lat: number;
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
import {
|
||||
AlgorithmType,
|
||||
Frequency,
|
||||
} from '@modules/matcher/core/domain/match.types';
|
||||
import {
|
||||
ArrayMinSize,
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsDecimal,
|
||||
IsEnum,
|
||||
IsISO8601,
|
||||
IsInt,
|
||||
IsOptional,
|
||||
Max,
|
||||
Min,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { HasDay } from './validators/decorators/has-day.decorator';
|
||||
import { IsAfterOrEqual } from './validators/decorators/is-after-or-equal.decorator';
|
||||
import { ScheduleItemDto } from './schedule-item.dto';
|
||||
import { WaypointDto } from './waypoint.dto';
|
||||
import { HasValidPositionIndexes } from './validators/decorators/has-valid-position-indexes.decorator';
|
||||
|
||||
export class MatchRequestDto {
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
driver?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
passenger?: boolean;
|
||||
|
||||
@IsEnum(Frequency)
|
||||
@HasDay('schedule', {
|
||||
message: 'At least a day is required for a recurrent ad',
|
||||
})
|
||||
frequency: Frequency;
|
||||
|
||||
@IsISO8601({
|
||||
strict: true,
|
||||
strictSeparator: true,
|
||||
})
|
||||
fromDate: string;
|
||||
|
||||
@IsISO8601({
|
||||
strict: true,
|
||||
strictSeparator: true,
|
||||
})
|
||||
@IsAfterOrEqual('fromDate', {
|
||||
message: 'toDate must be after or equal to fromDate',
|
||||
})
|
||||
toDate: string;
|
||||
|
||||
@Type(() => ScheduleItemDto)
|
||||
@IsArray()
|
||||
@ArrayMinSize(1)
|
||||
@ValidateNested({ each: true })
|
||||
schedule: ScheduleItemDto[];
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
seatsProposed?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
seatsRequested?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
strict?: boolean;
|
||||
|
||||
@Type(() => WaypointDto)
|
||||
@IsArray()
|
||||
@ArrayMinSize(2)
|
||||
@HasValidPositionIndexes()
|
||||
@ValidateNested({ each: true })
|
||||
waypoints: WaypointDto[];
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(AlgorithmType)
|
||||
algorithm?: AlgorithmType;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
remoteness?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
useProportion?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsDecimal()
|
||||
@Min(0)
|
||||
@Max(1)
|
||||
proportion?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
useAzimuth?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
@Max(359)
|
||||
azimuthMargin?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsDecimal()
|
||||
@Min(0)
|
||||
@Max(1)
|
||||
maxDetourDistanceRatio?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsDecimal()
|
||||
@Min(0)
|
||||
@Max(1)
|
||||
maxDetourDurationRatio?: number;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import { IsOptional, IsMilitaryTime, IsInt, Min, Max } from 'class-validator';
|
||||
|
||||
export class ScheduleItemDto {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
@Max(6)
|
||||
day?: number;
|
||||
|
||||
@IsMilitaryTime()
|
||||
time: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
margin?: number;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import {
|
||||
registerDecorator,
|
||||
ValidationOptions,
|
||||
ValidationArguments,
|
||||
} from 'class-validator';
|
||||
|
||||
export function HasDay(
|
||||
property: string,
|
||||
validationOptions?: ValidationOptions,
|
||||
) {
|
||||
return function (object: object, propertyName: string) {
|
||||
registerDecorator({
|
||||
name: 'hasDay',
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
constraints: [property],
|
||||
options: validationOptions,
|
||||
validator: {
|
||||
validate(value: any, args: ValidationArguments) {
|
||||
const [relatedPropertyName] = args.constraints;
|
||||
const relatedValue = (args.object as any)[relatedPropertyName];
|
||||
return (
|
||||
value == Frequency.PUNCTUAL ||
|
||||
(Array.isArray(relatedValue) &&
|
||||
relatedValue.some((scheduleItem) =>
|
||||
scheduleItem.hasOwnProperty('day'),
|
||||
))
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import { ValidateBy, ValidationOptions, buildMessage } from 'class-validator';
|
||||
import { hasValidPositionIndexes } from '../has-valid-position-indexes.validator';
|
||||
import { WaypointDto } from '../../waypoint.dto';
|
||||
|
||||
export const HasValidPositionIndexes = (
|
||||
validationOptions?: ValidationOptions,
|
||||
): PropertyDecorator =>
|
||||
ValidateBy(
|
||||
{
|
||||
name: '',
|
||||
constraints: [],
|
||||
validator: {
|
||||
validate: (waypoints: WaypointDto[]): boolean =>
|
||||
hasValidPositionIndexes(waypoints),
|
||||
defaultMessage: buildMessage(
|
||||
() => `invalid waypoints positions`,
|
||||
validationOptions,
|
||||
),
|
||||
},
|
||||
},
|
||||
validationOptions,
|
||||
);
|
|
@ -0,0 +1,43 @@
|
|||
import {
|
||||
registerDecorator,
|
||||
ValidationOptions,
|
||||
ValidationArguments,
|
||||
isISO8601,
|
||||
} from 'class-validator';
|
||||
|
||||
export function IsAfterOrEqual(
|
||||
property: string,
|
||||
validationOptions?: ValidationOptions,
|
||||
) {
|
||||
return function (object: object, propertyName: string) {
|
||||
registerDecorator({
|
||||
name: 'isAfterOrEqual',
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
constraints: [property],
|
||||
options: validationOptions,
|
||||
validator: {
|
||||
validate(value: any, args: ValidationArguments) {
|
||||
const [relatedPropertyName] = args.constraints;
|
||||
const relatedValue = (args.object as any)[relatedPropertyName];
|
||||
if (
|
||||
!(
|
||||
typeof value === 'string' &&
|
||||
typeof relatedValue === 'string' &&
|
||||
isISO8601(value, {
|
||||
strict: true,
|
||||
strictSeparator: true,
|
||||
}) &&
|
||||
isISO8601(relatedValue, {
|
||||
strict: true,
|
||||
strictSeparator: true,
|
||||
})
|
||||
)
|
||||
)
|
||||
return false;
|
||||
return new Date(value) >= new Date(relatedValue);
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import { WaypointDto } from '../waypoint.dto';
|
||||
|
||||
export const hasValidPositionIndexes = (waypoints: WaypointDto[]): boolean => {
|
||||
if (!waypoints) return false;
|
||||
if (waypoints.length == 0) return false;
|
||||
if (waypoints.every((waypoint) => waypoint.position === undefined))
|
||||
return false;
|
||||
if (waypoints.every((waypoint) => typeof waypoint.position === 'number')) {
|
||||
const positions = Array.from(waypoints, (waypoint) => waypoint.position);
|
||||
positions.sort();
|
||||
for (let i = 1; i < positions.length; i++)
|
||||
if (positions[i] != positions[i - 1] + 1) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
import { IsInt, IsOptional } from 'class-validator';
|
||||
import { AddressDto } from './address.dto';
|
||||
|
||||
export class WaypointDto extends AddressDto {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
position?: number;
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
import { Controller, UsePipes } from '@nestjs/common';
|
||||
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
||||
import { RpcValidationPipe } from '@mobicoop/ddd-library';
|
||||
import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
||||
import { MatchPaginatedResponseDto } from '../dtos/match.paginated.response.dto';
|
||||
import { QueryBus } from '@nestjs/cqrs';
|
||||
import { MatchRequestDto } from './dtos/match.request.dto';
|
||||
import { MatchQuery } from '@modules/matcher/core/application/queries/match/match.query';
|
||||
|
||||
@UsePipes(
|
||||
new RpcValidationPipe({
|
||||
whitelist: false,
|
||||
forbidUnknownValues: false,
|
||||
}),
|
||||
)
|
||||
@Controller()
|
||||
export class MatchGrpcController {
|
||||
constructor(private readonly queryBus: QueryBus) {}
|
||||
|
||||
@GrpcMethod('MatcherService', 'Match')
|
||||
async match(data: MatchRequestDto): Promise<MatchPaginatedResponseDto> {
|
||||
try {
|
||||
const matches = await this.queryBus.execute(new MatchQuery(data));
|
||||
return {
|
||||
data: matches,
|
||||
page: 1,
|
||||
perPage: 5,
|
||||
total: 1,
|
||||
};
|
||||
} catch (e) {
|
||||
throw new RpcException({
|
||||
code: RpcExceptionCode.UNKNOWN,
|
||||
message: e.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import { ScheduleItemDto } from '@modules/matcher/interface/grpc-controllers/dtos/schedule-item.dto';
|
||||
import { HasDay } from '@modules/matcher/interface/grpc-controllers/dtos/validators/decorators/has-day.decorator';
|
||||
import { Validator } from 'class-validator';
|
||||
|
||||
describe('Has day decorator', () => {
|
||||
class MyClass {
|
||||
@HasDay('schedule', {
|
||||
message: 'At least a day is required for a recurrent ad',
|
||||
})
|
||||
frequency: Frequency;
|
||||
|
||||
schedule: ScheduleItemDto[];
|
||||
}
|
||||
|
||||
it('should return a property decorator has a function', () => {
|
||||
const hasDay = HasDay('someProperty');
|
||||
expect(typeof hasDay).toBe('function');
|
||||
});
|
||||
|
||||
it('should validate a punctual frequency associated with a valid schedule', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.frequency = Frequency.PUNCTUAL;
|
||||
myClassInstance.schedule = [
|
||||
{
|
||||
time: '07:15',
|
||||
},
|
||||
];
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should validate a recurrent frequency associated with a valid schedule', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.frequency = Frequency.RECURRENT;
|
||||
myClassInstance.schedule = [
|
||||
{
|
||||
time: '07:15',
|
||||
day: 1,
|
||||
},
|
||||
];
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should not validate a recurrent frequency associated with an invalid schedule', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.frequency = Frequency.RECURRENT;
|
||||
myClassInstance.schedule = [
|
||||
{
|
||||
time: '07:15',
|
||||
},
|
||||
];
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,62 @@
|
|||
import { HasValidPositionIndexes } from '@modules/matcher/interface/grpc-controllers/dtos/validators/decorators/has-valid-position-indexes.decorator';
|
||||
import { WaypointDto } from '@modules/matcher/interface/grpc-controllers/dtos/waypoint.dto';
|
||||
import { Validator } from 'class-validator';
|
||||
|
||||
describe('valid position indexes decorator', () => {
|
||||
class MyClass {
|
||||
@HasValidPositionIndexes()
|
||||
waypoints: WaypointDto[];
|
||||
}
|
||||
it('should return a property decorator has a function', () => {
|
||||
const hasValidPositionIndexes = HasValidPositionIndexes();
|
||||
expect(typeof hasValidPositionIndexes).toBe('function');
|
||||
});
|
||||
it('should validate an array of waypoints with valid positions', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.waypoints = [
|
||||
{
|
||||
position: 0,
|
||||
lon: 48.8566,
|
||||
lat: 2.3522,
|
||||
locality: 'Paris',
|
||||
postalCode: '75000',
|
||||
country: 'France',
|
||||
},
|
||||
{
|
||||
position: 1,
|
||||
lon: 49.2628,
|
||||
lat: 4.0347,
|
||||
locality: 'Reims',
|
||||
postalCode: '51454',
|
||||
country: 'France',
|
||||
},
|
||||
];
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(0);
|
||||
});
|
||||
it('should not validate an array of waypoints with invalid positions', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.waypoints = [
|
||||
{
|
||||
position: 1,
|
||||
lon: 48.8566,
|
||||
lat: 2.3522,
|
||||
locality: 'Paris',
|
||||
postalCode: '75000',
|
||||
country: 'France',
|
||||
},
|
||||
{
|
||||
position: 1,
|
||||
lon: 49.2628,
|
||||
lat: 4.0347,
|
||||
locality: 'Reims',
|
||||
postalCode: '51454',
|
||||
country: 'France',
|
||||
},
|
||||
];
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,75 @@
|
|||
import { hasValidPositionIndexes } from '@modules/matcher/interface/grpc-controllers/dtos/validators/has-valid-position-indexes.validator';
|
||||
import { WaypointDto } from '@modules/matcher/interface/grpc-controllers/dtos/waypoint.dto';
|
||||
|
||||
describe('addresses position validator', () => {
|
||||
const mockAddress1: WaypointDto = {
|
||||
lon: 48.689445,
|
||||
lat: 6.17651,
|
||||
houseNumber: '5',
|
||||
street: 'Avenue Foch',
|
||||
locality: 'Nancy',
|
||||
postalCode: '54000',
|
||||
country: 'France',
|
||||
};
|
||||
const mockAddress2: WaypointDto = {
|
||||
lon: 48.8566,
|
||||
lat: 2.3522,
|
||||
locality: 'Paris',
|
||||
postalCode: '75000',
|
||||
country: 'France',
|
||||
};
|
||||
const mockAddress3: WaypointDto = {
|
||||
lon: 49.2628,
|
||||
lat: 4.0347,
|
||||
locality: 'Reims',
|
||||
postalCode: '51454',
|
||||
country: 'France',
|
||||
};
|
||||
|
||||
it('should not validate if no position is defined', () => {
|
||||
expect(
|
||||
hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
|
||||
).toBeFalsy();
|
||||
});
|
||||
it('should not validate if only one position is defined', () => {
|
||||
mockAddress1.position = 0;
|
||||
expect(
|
||||
hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
|
||||
).toBeFalsy();
|
||||
});
|
||||
it('should not validate if positions are partially defined', () => {
|
||||
mockAddress1.position = 0;
|
||||
mockAddress2.position = null;
|
||||
mockAddress3.position = undefined;
|
||||
expect(
|
||||
hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should not validate if multiple positions have same value', () => {
|
||||
mockAddress1.position = 0;
|
||||
mockAddress2.position = 1;
|
||||
mockAddress3.position = 1;
|
||||
expect(
|
||||
hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
|
||||
).toBeFalsy();
|
||||
});
|
||||
it('should validate if all positions are ordered', () => {
|
||||
mockAddress1.position = 0;
|
||||
mockAddress2.position = 1;
|
||||
mockAddress3.position = 2;
|
||||
expect(
|
||||
hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
|
||||
).toBeTruthy();
|
||||
mockAddress1.position = 1;
|
||||
mockAddress2.position = 2;
|
||||
mockAddress3.position = 3;
|
||||
expect(
|
||||
hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
|
||||
).toBeTruthy();
|
||||
});
|
||||
it('should not validate if no waypoints are defined', () => {
|
||||
expect(hasValidPositionIndexes(undefined)).toBeFalsy();
|
||||
expect(hasValidPositionIndexes([])).toBeFalsy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
import { IsAfterOrEqual } from '@modules/matcher/interface/grpc-controllers/dtos/validators/decorators/is-after-or-equal.decorator';
|
||||
import { Validator } from 'class-validator';
|
||||
|
||||
describe('Is after or equal decorator', () => {
|
||||
class MyClass {
|
||||
firstDate: string;
|
||||
|
||||
@IsAfterOrEqual('firstDate', {
|
||||
message: 'secondDate must be after or equal to firstDate',
|
||||
})
|
||||
secondDate: string;
|
||||
}
|
||||
|
||||
it('should return a property decorator has a function', () => {
|
||||
const isAfterOrEqual = IsAfterOrEqual('someProperty');
|
||||
expect(typeof isAfterOrEqual).toBe('function');
|
||||
});
|
||||
|
||||
it('should validate a secondDate posterior to firstDate', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.firstDate = '2023-07-20';
|
||||
myClassInstance.secondDate = '2023-07-30';
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should not validate a secondDate prior to firstDate', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.firstDate = '2023-07-20';
|
||||
myClassInstance.secondDate = '2023-07-19';
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should not validate if dates are invalid', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.firstDate = '2023-07-40';
|
||||
myClassInstance.secondDate = '2023-07-19';
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,82 @@
|
|||
import { Frequency } from '@modules/matcher/core/domain/match.types';
|
||||
import { MatchRequestDto } from '@modules/matcher/interface/grpc-controllers/dtos/match.request.dto';
|
||||
import { WaypointDto } from '@modules/matcher/interface/grpc-controllers/dtos/waypoint.dto';
|
||||
import { MatchGrpcController } from '@modules/matcher/interface/grpc-controllers/match.grpc-controller';
|
||||
import { QueryBus } from '@nestjs/cqrs';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
const originWaypoint: WaypointDto = {
|
||||
position: 0,
|
||||
lat: 48.689445,
|
||||
lon: 6.17651,
|
||||
houseNumber: '5',
|
||||
street: 'Avenue Foch',
|
||||
locality: 'Nancy',
|
||||
postalCode: '54000',
|
||||
country: 'France',
|
||||
};
|
||||
const destinationWaypoint: WaypointDto = {
|
||||
position: 1,
|
||||
lat: 48.8566,
|
||||
lon: 2.3522,
|
||||
locality: 'Paris',
|
||||
postalCode: '75000',
|
||||
country: 'France',
|
||||
};
|
||||
|
||||
const punctualMatchRequestDto: MatchRequestDto = {
|
||||
passenger: true,
|
||||
frequency: Frequency.PUNCTUAL,
|
||||
fromDate: '2023-08-15',
|
||||
toDate: '2023-08-15',
|
||||
schedule: [
|
||||
{
|
||||
time: '07:00',
|
||||
},
|
||||
],
|
||||
waypoints: [originWaypoint, destinationWaypoint],
|
||||
};
|
||||
|
||||
const mockQueryBus = {
|
||||
execute: jest.fn().mockImplementation(() => [
|
||||
{
|
||||
adId: 1,
|
||||
},
|
||||
{
|
||||
adId: 2,
|
||||
},
|
||||
]),
|
||||
};
|
||||
|
||||
describe('Match Grpc Controller', () => {
|
||||
let matchGrpcController: MatchGrpcController;
|
||||
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
MatchGrpcController,
|
||||
{
|
||||
provide: QueryBus,
|
||||
useValue: mockQueryBus,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
matchGrpcController = module.get<MatchGrpcController>(MatchGrpcController);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(matchGrpcController).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return matches', async () => {
|
||||
const matchPaginatedResponseDto = await matchGrpcController.match(
|
||||
punctualMatchRequestDto,
|
||||
);
|
||||
expect(matchPaginatedResponseDto.data).toHaveLength(2);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue