time request

This commit is contained in:
sbriat 2023-04-11 15:07:38 +02:00
parent 2ffa40aa53
commit 56bdd11970
17 changed files with 338 additions and 98 deletions

View File

@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { IDefaultParams } from '../../domain/interfaces/default-params.interface';
import { IDefaultParams } from '../../domain/interfaces/default-params.type';
@Injectable()
export class DefaultParamsProvider {
@ -8,9 +8,11 @@ export class DefaultParamsProvider {
getParams(): IDefaultParams {
return {
DEFAULT_IDENTIFIER: this.configService.get<number>('DEFAULT_IDENTIFIER'),
MARGIN_DURATION: this.configService.get<number>('MARGIN_DURATION'),
VALIDITY_DURATION: this.configService.get<number>('VALIDITY_DURATION'),
DEFAULT_IDENTIFIER: parseInt(
this.configService.get('DEFAULT_IDENTIFIER'),
),
MARGIN_DURATION: parseInt(this.configService.get('MARGIN_DURATION')),
VALIDITY_DURATION: parseInt(this.configService.get('VALIDITY_DURATION')),
};
}
}

View File

@ -1,9 +0,0 @@
export type MarginDurations = {
mon: number;
tue: number;
wed: number;
thu: number;
fri: number;
sat: number;
sun: number;
};

View File

@ -11,16 +11,13 @@ import {
} from 'class-validator';
import { AutoMap } from '@automapper/classes';
import { Point } from '../entities/point.type';
import { Schedule } from './schedule.type';
import { MarginDurations } from './margin-durations.type';
import { Schedule } from '../entities/schedule.type';
import { MarginDurations } from '../entities/margin-durations.type';
import { Algorithm } from './algorithm.enum';
import { IRequestTime } from '../interfaces/time-request.interface';
import { IRequestRole } from '../interfaces/role-request.interface';
import { IRequestPerson } from '../interfaces/person-request.interface';
export class MatchRequest
implements IRequestTime, IRequestRole, IRequestPerson
{
export class MatchRequest implements IRequestTime, IRequestPerson {
@IsArray()
@AutoMap()
waypoints: Array<Point>;
@ -50,9 +47,9 @@ export class MatchRequest
passenger: boolean;
@IsOptional()
@IsInt()
@IsString()
@AutoMap()
toDate: number;
toDate: string;
@IsOptional()
@IsInt()

View File

@ -1,9 +0,0 @@
export type Schedule = {
mon: string;
tue: string;
wed: string;
thu: string;
fri: string;
sat: string;
sun: string;
};

View File

@ -0,0 +1,9 @@
export type MarginDurations = {
mon?: number;
tue?: number;
wed?: number;
thu?: number;
fri?: number;
sat?: number;
sun?: number;
};

View File

@ -0,0 +1,9 @@
export type Schedule = {
mon?: string;
tue?: string;
wed?: string;
thu?: string;
fri?: string;
sat?: string;
sun?: string;
};

View File

@ -1,6 +1,10 @@
import { MatcherException } from '../../exceptions/matcher.exception';
import { MarginDurations } from './margin-durations.type';
import { IRequestTime } from '../interfaces/time-request.interface';
import { TimingFrequency } from './timing';
import { TimingDays, TimingFrequency } from './timing';
import { Schedule } from './schedule.type';
const days = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];
export class Time {
_timeRequest: IRequestTime;
@ -9,8 +13,8 @@ export class Time {
frequency: TimingFrequency;
fromDate: Date;
toDate: Date;
schedule: Array<Date>;
marginDurations: Array<number>;
schedule: Schedule;
marginDurations: MarginDurations;
constructor(
timeRequest: IRequestTime,
@ -20,12 +24,25 @@ export class Time {
this._timeRequest = timeRequest;
this._defaultMarginDuration = defaultMarginDuration;
this._defaultValidityDuration = defaultValidityDuration;
this.schedule = {};
this.marginDurations = {
mon: defaultMarginDuration,
tue: defaultMarginDuration,
wed: defaultMarginDuration,
thu: defaultMarginDuration,
fri: defaultMarginDuration,
sat: defaultMarginDuration,
sun: defaultMarginDuration,
};
}
init() {
this._validateBaseDate();
this._validatePunctualDate();
this._validateRecurrentDate();
this._validatePunctualRequest();
this._validateRecurrentRequest();
this._setPunctualRequest();
this._setRecurrentRequest();
this._setMargindurations();
}
_validateBaseDate() {
@ -34,25 +51,118 @@ export class Time {
}
}
_validatePunctualDate() {
_validatePunctualRequest() {
if (this._timeRequest.departure) {
this.fromDate = new Date(this._timeRequest.departure);
this.fromDate = this.toDate = new Date(this._timeRequest.departure);
if (!this._isDate(this.fromDate)) {
throw new MatcherException(3, 'Wrong departure date');
}
}
}
_validateRecurrentDate() {
_validateRecurrentRequest() {
if (this._timeRequest.fromDate) {
this.fromDate = new Date(this._timeRequest.fromDate);
if (!this._isDate(this.fromDate)) {
throw new MatcherException(3, 'Wrong fromDate');
}
}
if (this._timeRequest.toDate) {
this.toDate = new Date(this._timeRequest.toDate);
if (!this._isDate(this.toDate)) {
throw new MatcherException(3, 'Wrong toDate');
}
}
if (this._timeRequest.fromDate) {
this._validateSchedule();
}
}
_isDate(date: Date) {
return date instanceof Date && isFinite(+date);
_validateSchedule() {
if (!this._timeRequest.schedule) {
throw new MatcherException(3, 'Schedule is required');
}
if (
!Object.keys(this._timeRequest.schedule).some((elem) =>
days.includes(elem),
)
) {
throw new MatcherException(3, '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)) {
throw new MatcherException(3, `Wrong time for ${day} in schedule`);
}
});
}
_setPunctualRequest() {
if (this._timeRequest.departure) {
this.frequency = TimingFrequency.FREQUENCY_PUNCTUAL;
this.schedule[TimingDays[this.fromDate.getDay()]] =
this.fromDate.getHours() + ':' + this.fromDate.getMinutes();
}
}
_setRecurrentRequest() {
if (this._timeRequest.fromDate) {
this.frequency = TimingFrequency.FREQUENCY_RECURRENT;
if (!this.toDate) {
this.toDate = this._addDays(
this.fromDate,
this._defaultValidityDuration,
);
}
this._setSchedule();
}
}
_setSchedule() {
Object.keys(this._timeRequest.schedule).map((day) => {
this.schedule[day] = this._timeRequest.schedule[day];
});
}
_setMargindurations() {
if (this._timeRequest.marginDuration) {
const duration = Math.abs(this._timeRequest.marginDuration);
this.marginDurations = {
mon: duration,
tue: duration,
wed: duration,
thu: duration,
fri: duration,
sat: duration,
sun: duration,
};
}
if (this._timeRequest.marginDurations) {
if (
!Object.keys(this._timeRequest.marginDurations).some((elem) =>
days.includes(elem),
)
) {
throw new MatcherException(
3,
'No valid day in the given margin durations',
);
}
Object.keys(this._timeRequest.marginDurations).map((day) => {
this.marginDurations[day] = Math.abs(
this._timeRequest.marginDurations[day],
);
});
}
}
_isDate = (date: Date): boolean => {
return date instanceof Date && isFinite(+date);
};
_addDays = (date: Date, days: number): Date => {
const result = new Date(date);
result.setDate(result.getDate() + days);
return result;
};
}

View File

@ -4,11 +4,11 @@ export enum TimingFrequency {
}
export enum TimingDays {
'sun',
'mon',
'tue',
'wed',
'thu',
'fri',
'sat',
'sun',
}

View File

@ -1,5 +1,5 @@
export interface IDefaultParams {
export type IDefaultParams = {
DEFAULT_IDENTIFIER: number;
MARGIN_DURATION: number;
VALIDITY_DURATION: number;
}
};

View File

@ -1,3 +1,3 @@
export interface IRequestPerson {
identifier: number;
identifier?: number;
}

View File

@ -1,4 +0,0 @@
export interface IRequestRole {
driver: boolean;
passenger: boolean;
}

View File

@ -1,7 +1,11 @@
import { Schedule } from '../dtos/schedule.type';
import { MarginDurations } from '../entities/margin-durations.type';
import { Schedule } from '../entities/schedule.type';
export interface IRequestTime {
departure?: string;
fromDate?: string;
toDate?: string;
schedule?: Schedule;
marginDuration?: number;
marginDurations?: MarginDurations;
}

View File

@ -5,36 +5,30 @@ import { Requirement } from '../domain/entities/requirement';
import { Role } from '../domain/entities/role.enum';
import { Settings } from '../domain/entities/settings';
import { Time } from '../domain/entities/time';
import { IDefaultParams } from '../domain/interfaces/default-params.interface';
import { IDefaultParams } from '../domain/interfaces/default-params.type';
export class MatchQuery {
private readonly _matchRequest: MatchRequest;
private readonly _defaultParams: IDefaultParams;
person: Person;
exclusions: Array<number>;
time: Time;
geography: Geography;
roles: Array<Role>;
time: Time;
exclusions: Array<number>;
geography: Geography;
requirement: Requirement;
settings: Settings;
constructor(matchRequest: MatchRequest, defaultParams: IDefaultParams) {
this._matchRequest = matchRequest;
this._defaultParams = defaultParams;
this._initialize();
this._setPerson();
this._setExclusions();
this._setRoles();
this._setTime();
// console.log(this);
this._initialize();
this._setExclusions();
}
_initialize() {
if (
this._matchRequest.driver === undefined &&
this._matchRequest.passenger === undefined
)
this._matchRequest.passenger = true;
this.geography = new Geography();
this.requirement = new Requirement();
this.settings = new Settings();
@ -49,18 +43,11 @@ export class MatchQuery {
this.person.init();
}
_setExclusions() {
this.exclusions = [];
if (this._matchRequest.identifier)
this.exclusions.push(this._matchRequest.identifier);
if (this._matchRequest.exclusions)
this.exclusions.push(...this._matchRequest.exclusions);
}
_setRoles() {
this.roles = [];
if (this._matchRequest.driver) this.roles.push(Role.DRIVER);
if (this._matchRequest.passenger) this.roles.push(Role.PASSENGER);
if (this.roles.length == 0) this.roles.push(Role.PASSENGER);
}
_setTime() {
@ -71,4 +58,12 @@ export class MatchQuery {
);
this.time.init();
}
_setExclusions() {
this.exclusions = [];
if (this._matchRequest.identifier)
this.exclusions.push(this._matchRequest.identifier);
if (this._matchRequest.exclusions)
this.exclusions.push(...this._matchRequest.exclusions);
}
}

View File

@ -1,7 +1,7 @@
import { ConfigService } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import { DefaultParamsProvider } from '../../adapters/secondaries/default-params.provider';
import { IDefaultParams } from '../../domain/interfaces/default-params.interface';
import { IDefaultParams } from '../../domain/interfaces/default-params.type';
const mockConfigService = {
get: jest.fn().mockImplementationOnce(() => 99),

View File

@ -6,7 +6,7 @@ import { MatchQuery } from '../../queries/match.query';
import { AdRepository } from '../../adapters/secondaries/ad.repository';
import { AutomapperModule } from '@automapper/nestjs';
import { classes } from '@automapper/classes';
import { IDefaultParams } from '../../domain/interfaces/default-params.interface';
import { IDefaultParams } from '../../domain/interfaces/default-params.type';
const mockAdRepository = {};

View File

@ -0,0 +1,40 @@
import { Person } from '../../domain/entities/person';
const DEFAULT_IDENTIFIER = 0;
const MARGIN_DURATION = 900;
describe('Person entity', () => {
it('should be defined', () => {
const person = new Person(
{
identifier: 1,
},
DEFAULT_IDENTIFIER,
MARGIN_DURATION,
);
expect(person).toBeDefined();
});
describe('init', () => {
it('should initialize a person with an identifier', () => {
const person = new Person(
{
identifier: 1,
},
DEFAULT_IDENTIFIER,
MARGIN_DURATION,
);
person.init();
expect(person.identifier).toBe(1);
expect(person.marginDurations[0]).toBe(900);
expect(person.marginDurations[6]).toBe(900);
});
it('should initialize a person without an identifier', () => {
const person = new Person({}, DEFAULT_IDENTIFIER, MARGIN_DURATION);
person.init();
expect(person.identifier).toBe(0);
expect(person.marginDurations[0]).toBe(900);
expect(person.marginDurations[6]).toBe(900);
});
});
});

View File

@ -1,31 +1,14 @@
import { Time } from '../../domain/entities/time';
import { IRequestTime } from '../../domain/interfaces/time-request.interface';
const MARGIN_DURATION = 900;
const VALIDITY_DURATION = 365;
const punctualTimeRequest: IRequestTime = {
departure: '2023-04-01 12:24:00',
};
const invalidPunctualTimeRequest: IRequestTime = {
departure: '2023-15-01 12:24:00',
};
const recurrentTimeRequest: IRequestTime = {
fromDate: '2023-04-01',
};
const invalidRecurrentTimeRequest: IRequestTime = {
fromDate: '2023-15-01',
};
const expectedPunctualFromDate = new Date(punctualTimeRequest.departure);
describe('Time entity', () => {
it('should be defined', () => {
const time = new Time(
punctualTimeRequest,
{
departure: '2023-04-01 12:24:00',
},
MARGIN_DURATION,
VALIDITY_DURATION,
);
@ -35,24 +18,74 @@ describe('Time entity', () => {
describe('init', () => {
it('should initialize a punctual time request', () => {
const time = new Time(
punctualTimeRequest,
{
departure: '2023-04-01 12:24:00',
},
MARGIN_DURATION,
VALIDITY_DURATION,
);
time.init();
expect(time.fromDate.getFullYear()).toBe(
expectedPunctualFromDate.getFullYear(),
new Date('2023-04-01 12:24:00').getFullYear(),
);
});
it('should initialize a punctual time request with specific single margin duration', () => {
const time = new Time(
{
departure: '2023-04-01 12:24:00',
marginDuration: 300,
},
MARGIN_DURATION,
VALIDITY_DURATION,
);
time.init();
expect(time.marginDurations['tue']).toBe(300);
});
it('should initialize a punctual time request with specific margin durations', () => {
const time = new Time(
{
departure: '2023-04-01 12:24:00',
marginDurations: {
sat: 350,
},
},
MARGIN_DURATION,
VALIDITY_DURATION,
);
time.init();
expect(time.marginDurations['tue']).toBe(900);
expect(time.marginDurations['sat']).toBe(350);
});
it('should initialize a punctual time request with specific single margin duration and margin durations', () => {
const time = new Time(
{
departure: '2023-04-01 12:24:00',
marginDuration: 500,
marginDurations: {
sat: 350,
},
},
MARGIN_DURATION,
VALIDITY_DURATION,
);
time.init();
expect(time.marginDurations['tue']).toBe(500);
expect(time.marginDurations['sat']).toBe(350);
});
it('should initialize a recurrent time request', () => {
const time = new Time(
recurrentTimeRequest,
{
fromDate: '2023-04-01',
schedule: {
mon: '12:00',
},
},
MARGIN_DURATION,
VALIDITY_DURATION,
);
time.init();
expect(time.fromDate.getFullYear()).toBe(
expectedPunctualFromDate.getFullYear(),
new Date('2023-04-01').getFullYear(),
);
});
it('should throw an exception if no date is provided', () => {
@ -61,19 +94,82 @@ describe('Time entity', () => {
});
it('should throw an exception if punctual date is invalid', () => {
const time = new Time(
invalidPunctualTimeRequest,
{
departure: '2023-15-01 12:24:00',
},
MARGIN_DURATION,
VALIDITY_DURATION,
);
expect(() => time.init()).toThrow();
});
it('should throw an exception if recuurent date is invalid', () => {
it('should throw an exception if recurrent fromDate is invalid', () => {
const time = new Time(
invalidRecurrentTimeRequest,
{
fromDate: '2023-15-01',
},
MARGIN_DURATION,
VALIDITY_DURATION,
);
expect(() => time.init()).toThrow();
});
it('should throw an exception if recurrent toDate is invalid', () => {
const time = new Time(
{
fromDate: '2023-04-01',
toDate: '2023-13-01',
},
MARGIN_DURATION,
VALIDITY_DURATION,
);
expect(() => time.init()).toThrow();
});
it('should throw an exception if schedule is missing', () => {
const time = new Time(
{
fromDate: '2023-04-01',
toDate: '2024-03-31',
},
MARGIN_DURATION,
VALIDITY_DURATION,
);
expect(() => time.init()).toThrow();
});
it('should throw an exception if schedule is empty', () => {
const time = new Time(
{
fromDate: '2023-04-01',
toDate: '2024-03-31',
schedule: {},
},
MARGIN_DURATION,
VALIDITY_DURATION,
);
expect(() => time.init()).toThrow();
});
it('should throw an exception if schedule is invalid', () => {
const time = new Time(
{
fromDate: '2023-04-01',
toDate: '2024-03-31',
schedule: {
mon: '32:78',
},
},
MARGIN_DURATION,
VALIDITY_DURATION,
);
expect(() => time.init()).toThrow();
});
});
it('should throw an exception if margin durations is provided but empty', () => {
const time = new Time(
{
departure: '2023-04-01 12:24:00',
marginDurations: {},
},
MARGIN_DURATION,
VALIDITY_DURATION,
);
expect(() => time.init()).toThrow();
});
});