journey and journey item value objects

This commit is contained in:
sbriat 2023-09-21 12:44:39 +02:00
parent c9c682c1fc
commit dfc8dbcc51
16 changed files with 907 additions and 247 deletions

View File

@ -5,5 +5,7 @@ export class JourneyCompleter extends Completer {
complete = async (
candidates: CandidateEntity[],
): Promise<CandidateEntity[]> =>
candidates.map((candidate: CandidateEntity) => candidate.createJourney());
candidates.map((candidate: CandidateEntity) =>
candidate.createJourneys(this.query.fromDate, this.query.toDate),
);
}

View File

@ -8,6 +8,7 @@ import {
RouteCompleter,
RouteCompleterType,
} from './completer/route.completer';
import { JourneyCompleter } from './completer/journey.completer';
export class PassengerOrientedAlgorithm extends Algorithm {
constructor(
@ -21,6 +22,7 @@ export class PassengerOrientedAlgorithm extends Algorithm {
new RouteCompleter(query, RouteCompleterType.BASIC),
new PassengerOrientedGeoFilter(query),
new RouteCompleter(query, RouteCompleterType.DETAILED),
new JourneyCompleter(query),
];
}
}

View File

@ -0,0 +1,80 @@
import { ExceptionBase } from '@mobicoop/ddd-library';
export class CalendarTools {
/**
* Returns the first date corresponding to a week day (0 based monday)
* within a date range
*/
static firstDate = (
weekDay: number,
lowerDate: string,
higherDate: string,
): Date => {
if (weekDay < 0 || weekDay > 6)
throw new CalendarToolsException(
new Error('weekDay must be between 0 and 6'),
);
const lowerDateAsDate: Date = new Date(lowerDate);
const higherDateAsDate: Date = new Date(higherDate);
if (lowerDateAsDate.getDay() == weekDay) return lowerDateAsDate;
const nextDate: Date = new Date(lowerDateAsDate);
nextDate.setDate(
lowerDateAsDate.getDate() + (7 - (lowerDateAsDate.getDay() - weekDay)),
);
if (lowerDateAsDate.getDay() < weekDay) {
nextDate.setMonth(lowerDateAsDate.getMonth());
nextDate.setFullYear(lowerDateAsDate.getFullYear());
nextDate.setDate(
lowerDateAsDate.getDate() + (weekDay - lowerDateAsDate.getDay()),
);
}
if (nextDate <= higherDateAsDate) return nextDate;
throw new CalendarToolsException(
new Error('no available day for the given date range'),
);
};
/**
* Returns the last date corresponding to a week day (0 based monday)
* within a date range
*/
static lastDate = (
weekDay: number,
lowerDate: string,
higherDate: string,
): Date => {
if (weekDay < 0 || weekDay > 6)
throw new CalendarToolsException(
new Error('weekDay must be between 0 and 6'),
);
const lowerDateAsDate: Date = new Date(lowerDate);
const higherDateAsDate: Date = new Date(higherDate);
if (higherDateAsDate.getDay() == weekDay) return higherDateAsDate;
const previousDate: Date = new Date(higherDateAsDate);
previousDate.setDate(
higherDateAsDate.getDate() - (higherDateAsDate.getDay() - weekDay),
);
if (higherDateAsDate.getDay() < weekDay) {
previousDate.setMonth(higherDateAsDate.getMonth());
previousDate.setFullYear(higherDateAsDate.getFullYear());
previousDate.setDate(
higherDateAsDate.getDate() -
(7 + (higherDateAsDate.getDay() - weekDay)),
);
}
if (previousDate >= lowerDateAsDate) return previousDate;
throw new CalendarToolsException(
new Error('no available day for the given date range'),
);
};
}
export class CalendarToolsException extends ExceptionBase {
static readonly message = 'Calendar tools error';
public readonly code = 'CALENDAR.TOOLS';
constructor(cause?: Error, metadata?: unknown) {
super(CalendarToolsException.message, cause, metadata);
}
}

View File

@ -2,6 +2,10 @@ import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
import { CandidateProps, CreateCandidateProps } from './candidate.types';
import { CarpoolStepProps } from './value-objects/carpool-step.value-object';
import { StepProps } from './value-objects/step.value-object';
import { ScheduleItem } from './value-objects/schedule-item.value-object';
import { Journey } from './value-objects/journey.value-object';
import { CalendarTools } from './calendar-tools.service';
import { JourneyItem } from './value-objects/journey-item.value-object';
export class CandidateEntity extends AggregateRoot<CandidateProps> {
protected readonly _id: AggregateID;
@ -30,7 +34,23 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
isDetourValid = (): boolean =>
this._validateDistanceDetour() && this._validateDurationDetour();
createJourney = (): CandidateEntity => this;
createJourneys = (fromDate: string, toDate: string): CandidateEntity => {
this.props.driverJourneys = this.props.driverSchedule
.map((driverScheduleItem: ScheduleItem) =>
this._createJourney(fromDate, toDate, driverScheduleItem),
)
.filter(
(journey: Journey | undefined) => journey !== undefined,
) as Journey[];
this.props.passengerJourneys = this.props.passengerSchedule
.map((passengerScheduleItem: ScheduleItem) =>
this._createJourney(fromDate, toDate, passengerScheduleItem),
)
.filter(
(journey: Journey | undefined) => journey !== undefined,
) as Journey[];
return this;
};
private _validateDurationDetour = (): boolean =>
this.props.duration
@ -46,6 +66,22 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
(1 + this.props.spacetimeDetourRatio.maxDistanceDetourRatio)
: false;
private _createJourney = (
fromDate: string,
toDate: string,
scheduleItem: ScheduleItem,
): Journey | undefined =>
new Journey({
day: scheduleItem.day,
firstDate: CalendarTools.firstDate(scheduleItem.day, fromDate, toDate),
lastDate: CalendarTools.lastDate(scheduleItem.day, fromDate, toDate),
journeyItems: this._createJourneyItems(scheduleItem),
});
private _createJourneyItems = (
scheduleItem: ScheduleItem,
): JourneyItem[] => [];
validate(): void {
// entity business rules validation to protect it's invariant before saving entity to a database
}

View File

@ -18,7 +18,8 @@ export interface CandidateProps {
steps?: StepProps[];
driverSchedule: ScheduleItemProps[];
passengerSchedule: ScheduleItemProps[];
journeys?: JourneyProps[];
driverJourneys?: JourneyProps[];
passengerJourneys?: JourneyProps[];
spacetimeDetourRatio: SpacetimeDetourRatio;
}

View File

@ -86,7 +86,7 @@ export class CarpoolPathCreator {
});
if (
this.driverWaypoints.filter((driverWaypoint: Point) =>
passengerWaypoint.isSame(driverWaypoint),
passengerWaypoint.equals(driverWaypoint),
).length == 0
) {
carpoolStep.actors.push(
@ -233,7 +233,7 @@ export class CarpoolPathCreator {
const uniquePoints: Point[] = [];
carpoolSteps.forEach((carpoolStep: CarpoolStep) => {
if (
uniquePoints.find((point: Point) => point.isSame(carpoolStep.point)) ===
uniquePoints.find((point: Point) => point.equals(carpoolStep.point)) ===
undefined
)
uniquePoints.push(
@ -249,7 +249,7 @@ export class CarpoolPathCreator {
point,
actors: carpoolSteps
.filter((carpoolStep: CarpoolStep) =>
carpoolStep.point.isSame(point),
carpoolStep.point.equals(point),
)
.map((carpoolStep: CarpoolStep) => carpoolStep.actors)
.flat(),

View File

@ -9,24 +9,15 @@ import { Actor, ActorProps } from './actor.value-object';
* */
export interface ActorTimeProps extends ActorProps {
time: string;
minTime: string;
maxTime: string;
firstDatetime: Date;
firstMinDatetime: Date;
firstMaxDatetime: Date;
lastDatetime: Date;
lastMinDatetime: Date;
lastMaxDatetime: Date;
}
export class ActorTime extends ValueObject<ActorTimeProps> {
get time(): string {
return this.props.time;
}
get minTime(): string {
return this.props.minTime;
}
get maxTime(): string {
return this.props.maxTime;
}
get role(): Role {
return this.props.role;
}
@ -35,23 +26,59 @@ export class ActorTime extends ValueObject<ActorTimeProps> {
return this.props.target;
}
get firstDatetime(): Date {
return this.props.firstDatetime;
}
get firstMinDatetime(): Date {
return this.props.firstMinDatetime;
}
get firstMaxDatetime(): Date {
return this.props.firstMaxDatetime;
}
get lastDatetime(): Date {
return this.props.lastDatetime;
}
get lastMinDatetime(): Date {
return this.props.lastMinDatetime;
}
get lastMaxDatetime(): Date {
return this.props.lastMaxDatetime;
}
protected validate(props: ActorTimeProps): void {
// validate actor props
new Actor({
role: props.role,
target: props.target,
});
this._validateTime(props.time, 'time');
this._validateTime(props.minTime, 'minTime');
this._validateTime(props.maxTime, 'maxTime');
}
private _validateTime(time: string, property: string): void {
if (time.split(':').length != 2)
throw new ArgumentInvalidException(`${property} is invalid`);
if (parseInt(time.split(':')[0]) < 0 || parseInt(time.split(':')[0]) > 23)
throw new ArgumentInvalidException(`${property} is invalid`);
if (parseInt(time.split(':')[1]) < 0 || parseInt(time.split(':')[1]) > 59)
throw new ArgumentInvalidException(`${property} is invalid`);
if (props.firstDatetime.getDay() != props.lastDatetime.getDay())
throw new ArgumentInvalidException(
'firstDatetime week day must be equal to lastDatetime week day',
);
if (props.firstDatetime > props.lastDatetime)
throw new ArgumentInvalidException(
'firstDatetime must be before or equal to lastDatetime',
);
if (props.firstMinDatetime > props.firstDatetime)
throw new ArgumentInvalidException(
'firstMinDatetime must be before or equal to firstDatetime',
);
if (props.firstDatetime > props.firstMaxDatetime)
throw new ArgumentInvalidException(
'firstDatetime must be before or equal to firstMaxDatetime',
);
if (props.lastMinDatetime > props.lastDatetime)
throw new ArgumentInvalidException(
'lastMinDatetime must be before or equal to lastDatetime',
);
if (props.lastDatetime > props.lastMaxDatetime)
throw new ArgumentInvalidException(
'lastDatetime must be before or equal to lastMaxDatetime',
);
}
}

View File

@ -0,0 +1,51 @@
import {
ArgumentOutOfRangeException,
ValueObject,
} from '@mobicoop/ddd-library';
import { ActorTime } from './actor-time.value-object';
import { Step, StepProps } from './step.value-object';
/** Note:
* Value Objects with multiple properties can contain
* other Value Objects inside if needed.
* */
export interface JourneyItemProps extends StepProps {
actorTimes: ActorTime[];
}
export class JourneyItem extends ValueObject<JourneyItemProps> {
get duration(): number {
return this.props.duration;
}
get distance(): number | undefined {
return this.props.distance;
}
get lon(): number {
return this.props.lon;
}
get lat(): number {
return this.props.lat;
}
get actorTimes(): ActorTime[] {
return this.props.actorTimes;
}
protected validate(props: JourneyItemProps): void {
// validate step props
new Step({
lon: props.lon,
lat: props.lat,
distance: props.distance,
duration: props.duration,
});
if (props.actorTimes.length == 0)
throw new ArgumentOutOfRangeException(
'at least one actorTime is required',
);
}
}

View File

@ -3,19 +3,18 @@ import {
ArgumentOutOfRangeException,
ValueObject,
} from '@mobicoop/ddd-library';
import { ScheduleItem, ScheduleItemProps } from './schedule-item.value-object';
import { ActorTime } from './actor-time.value-object';
import { Actor } from './actor.value-object';
import { JourneyItem } from './journey-item.value-object';
/** Note:
* Value Objects with multiple properties can contain
* other Value Objects inside if needed.
* */
export interface JourneyProps extends ScheduleItemProps {
export interface JourneyProps {
firstDate: Date;
lastDate: Date;
actorTimes: ActorTime[];
day: number;
journeyItems: JourneyItem[];
}
export class Journey extends ValueObject<JourneyProps> {
@ -27,41 +26,26 @@ export class Journey extends ValueObject<JourneyProps> {
return this.props.lastDate;
}
get actorTimes(): ActorTime[] {
return this.props.actorTimes;
}
get day(): number {
return this.props.day;
}
get time(): string {
return this.props.time;
}
get margin(): number {
return this.props.margin;
get journeyItems(): JourneyItem[] {
return this.props.journeyItems;
}
protected validate(props: JourneyProps): void {
// validate scheduleItem props
new ScheduleItem({
day: props.day,
time: props.time,
margin: props.margin,
});
// validate actor times
props.actorTimes.forEach((actorTime: ActorTime) => {
new Actor({
role: actorTime.role,
target: actorTime.target,
});
});
if (props.day < 0 || props.day > 6)
throw new ArgumentOutOfRangeException('day must be between 0 and 6');
if (props.firstDate.getDay() != props.lastDate.getDay())
throw new ArgumentInvalidException(
'firstDate week day must be equal to lastDate week day',
);
if (props.firstDate > props.lastDate)
throw new ArgumentInvalidException('firstDate must be before lastDate');
if (props.actorTimes.length < 4)
throw new ArgumentOutOfRangeException(
'at least 4 actorTimes are required',
if (props.journeyItems.length < 2)
throw new ArgumentInvalidException(
'at least 2 journey items are required',
);
}
}

View File

@ -22,9 +22,6 @@ export class Point extends ValueObject<PointProps> {
return this.props.lat;
}
isSame = (point: this): boolean =>
point.lon == this.lon && point.lat == this.lat;
protected validate(props: PointProps): void {
if (props.lon > 180 || props.lon < -180)
throw new ArgumentOutOfRangeException('lon must be between -180 and 180');

View File

@ -1,3 +1,4 @@
import { ArgumentInvalidException } from '@mobicoop/ddd-library';
import { Role } from '@modules/ad/core/domain/ad.types';
import { Target } from '@modules/ad/core/domain/candidate.types';
import { ActorTime } from '@modules/ad/core/domain/value-objects/actor-time.value-object';
@ -7,43 +8,100 @@ describe('Actor time value object', () => {
const actorTimeVO = new ActorTime({
role: Role.DRIVER,
target: Target.START,
time: '07:00',
minTime: '06:45',
maxTime: '07:15',
firstDatetime: new Date('2023-09-01 07:00'),
firstMinDatetime: new Date('2023-09-01 06:45'),
firstMaxDatetime: new Date('2023-09-01 07:15'),
lastDatetime: new Date('2024-08-30 07:00'),
lastMinDatetime: new Date('2024-08-30 06:45'),
lastMaxDatetime: new Date('2024-08-30 07:15'),
});
expect(actorTimeVO.role).toBe(Role.DRIVER);
expect(actorTimeVO.target).toBe(Target.START);
expect(actorTimeVO.time).toBe('07:00');
expect(actorTimeVO.minTime).toBe('06:45');
expect(actorTimeVO.maxTime).toBe('07:15');
expect(actorTimeVO.firstDatetime.getHours()).toBe(7);
expect(actorTimeVO.firstMinDatetime.getMinutes()).toBe(45);
expect(actorTimeVO.firstMaxDatetime.getMinutes()).toBe(15);
expect(actorTimeVO.lastDatetime.getHours()).toBe(7);
expect(actorTimeVO.lastMinDatetime.getMinutes()).toBe(45);
expect(actorTimeVO.lastMaxDatetime.getMinutes()).toBe(15);
});
it('should throw an error if a time is invalid', () => {
expect(() => {
new ActorTime({
role: Role.DRIVER,
target: Target.START,
time: '27:00',
minTime: '06:45',
maxTime: '07:15',
});
}).toThrow();
expect(() => {
new ActorTime({
role: Role.DRIVER,
target: Target.START,
time: '07:00',
minTime: '06:95',
maxTime: '07:15',
});
}).toThrow();
expect(() => {
new ActorTime({
role: Role.DRIVER,
target: Target.START,
time: '07:00',
minTime: '06:45',
maxTime: '07',
});
}).toThrow();
it('should throw an error if dates are inconsistent', () => {
expect(
() =>
new ActorTime({
role: Role.DRIVER,
target: Target.START,
firstDatetime: new Date('2023-09-01 07:00'),
firstMinDatetime: new Date('2023-09-01 07:05'),
firstMaxDatetime: new Date('2023-09-01 07:15'),
lastDatetime: new Date('2024-08-30 07:00'),
lastMinDatetime: new Date('2024-08-30 06:45'),
lastMaxDatetime: new Date('2024-08-30 07:15'),
}),
).toThrow(ArgumentInvalidException);
expect(
() =>
new ActorTime({
role: Role.DRIVER,
target: Target.START,
firstDatetime: new Date('2023-09-01 07:00'),
firstMinDatetime: new Date('2023-09-01 06:45'),
firstMaxDatetime: new Date('2023-09-01 06:55'),
lastDatetime: new Date('2024-08-30 07:00'),
lastMinDatetime: new Date('2024-08-30 06:45'),
lastMaxDatetime: new Date('2024-08-30 07:15'),
}),
).toThrow(ArgumentInvalidException);
expect(
() =>
new ActorTime({
role: Role.DRIVER,
target: Target.START,
firstDatetime: new Date('2023-09-01 07:00'),
firstMinDatetime: new Date('2023-09-01 06:45'),
firstMaxDatetime: new Date('2023-09-01 07:15'),
lastDatetime: new Date('2024-08-30 07:00'),
lastMinDatetime: new Date('2024-08-30 07:05'),
lastMaxDatetime: new Date('2024-08-30 07:15'),
}),
).toThrow(ArgumentInvalidException);
expect(
() =>
new ActorTime({
role: Role.DRIVER,
target: Target.START,
firstDatetime: new Date('2023-09-01 07:00'),
firstMinDatetime: new Date('2023-09-01 06:45'),
firstMaxDatetime: new Date('2023-09-01 07:15'),
lastDatetime: new Date('2024-08-30 07:00'),
lastMinDatetime: new Date('2024-08-30 06:45'),
lastMaxDatetime: new Date('2024-08-30 06:35'),
}),
).toThrow(ArgumentInvalidException);
expect(
() =>
new ActorTime({
role: Role.DRIVER,
target: Target.START,
firstDatetime: new Date('2024-08-30 07:00'),
firstMinDatetime: new Date('2024-08-30 06:45'),
firstMaxDatetime: new Date('2024-08-30 07:15'),
lastDatetime: new Date('2023-09-01 07:00'),
lastMinDatetime: new Date('2023-09-01 06:45'),
lastMaxDatetime: new Date('2023-09-01 07:15'),
}),
).toThrow(ArgumentInvalidException);
expect(
() =>
new ActorTime({
role: Role.DRIVER,
target: Target.START,
firstDatetime: new Date('2023-09-01 07:00'),
firstMinDatetime: new Date('2023-09-01 06:45'),
firstMaxDatetime: new Date('2023-09-01 07:15'),
lastDatetime: new Date('2024-08-31 07:00'),
lastMinDatetime: new Date('2024-08-31 06:45'),
lastMaxDatetime: new Date('2024-08-31 06:35'),
}),
).toThrow(ArgumentInvalidException);
});
});

View File

@ -0,0 +1,84 @@
import {
CalendarTools,
CalendarToolsException,
} from '@modules/ad/core/domain/calendar-tools.service';
describe('Calendar tools service', () => {
describe('First date', () => {
it('should return the first date for a given week day within a date range', () => {
const firstDate: Date = CalendarTools.firstDate(
1,
'2023-08-31',
'2023-09-07',
);
expect(firstDate.getDay()).toBe(1);
expect(firstDate.getDate()).toBe(4);
expect(firstDate.getMonth()).toBe(8);
const secondDate: Date = CalendarTools.firstDate(
5,
'2023-08-31',
'2023-09-07',
);
expect(secondDate.getDay()).toBe(5);
expect(secondDate.getDate()).toBe(1);
expect(secondDate.getMonth()).toBe(8);
const thirdDate: Date = CalendarTools.firstDate(
4,
'2023-08-31',
'2023-09-07',
);
expect(thirdDate.getDay()).toBe(4);
expect(thirdDate.getDate()).toBe(31);
expect(thirdDate.getMonth()).toBe(7);
});
it('should throw an exception if a given week day is not within a date range', () => {
expect(() => {
CalendarTools.firstDate(1, '2023-09-05', '2023-09-07');
}).toThrow(CalendarToolsException);
});
it('should throw an exception if a given week day is invalid', () => {
expect(() => {
CalendarTools.firstDate(8, '2023-09-05', '2023-09-07');
}).toThrow(CalendarToolsException);
});
});
describe('Second date', () => {
it('should return the last date for a given week day within a date range', () => {
const firstDate: Date = CalendarTools.lastDate(
0,
'2023-09-30',
'2024-09-30',
);
expect(firstDate.getDay()).toBe(0);
expect(firstDate.getDate()).toBe(29);
expect(firstDate.getMonth()).toBe(8);
const secondDate: Date = CalendarTools.lastDate(
5,
'2023-09-30',
'2024-09-30',
);
expect(secondDate.getDay()).toBe(5);
expect(secondDate.getDate()).toBe(27);
expect(secondDate.getMonth()).toBe(8);
const thirdDate: Date = CalendarTools.lastDate(
1,
'2023-09-30',
'2024-09-30',
);
expect(thirdDate.getDay()).toBe(1);
expect(thirdDate.getDate()).toBe(30);
expect(thirdDate.getMonth()).toBe(8);
});
it('should throw an exception if a given week day is not within a date range', () => {
expect(() => {
CalendarTools.lastDate(2, '2024-09-27', '2024-09-30');
}).toThrow(CalendarToolsException);
});
it('should throw an exception if a given week day is invalid', () => {
expect(() => {
CalendarTools.lastDate(8, '2023-09-30', '2024-09-30');
}).toThrow(CalendarToolsException);
});
});
});

View File

@ -0,0 +1,45 @@
import { ArgumentOutOfRangeException } from '@mobicoop/ddd-library';
import { Role } from '@modules/ad/core/domain/ad.types';
import { Target } from '@modules/ad/core/domain/candidate.types';
import { ActorTime } from '@modules/ad/core/domain/value-objects/actor-time.value-object';
import { JourneyItem } from '@modules/ad/core/domain/value-objects/journey-item.value-object';
describe('Journey item value object', () => {
it('should create a journey item value object', () => {
const journeyItemVO: JourneyItem = new JourneyItem({
lat: 48.689445,
lon: 6.17651,
duration: 1545,
distance: 48754,
actorTimes: [
new ActorTime({
role: Role.DRIVER,
target: Target.NEUTRAL,
firstDatetime: new Date('2023-09-01 07:00'),
firstMinDatetime: new Date('2023-09-01 06:45'),
firstMaxDatetime: new Date('2023-09-01 07:15'),
lastDatetime: new Date('2024-08-30 07:00'),
lastMinDatetime: new Date('2024-08-30 06:45'),
lastMaxDatetime: new Date('2024-08-30 07:15'),
}),
],
});
expect(journeyItemVO.duration).toBe(1545);
expect(journeyItemVO.distance).toBe(48754);
expect(journeyItemVO.lon).toBe(6.17651);
expect(journeyItemVO.lat).toBe(48.689445);
expect(journeyItemVO.actorTimes[0].firstMaxDatetime.getMinutes()).toBe(15);
});
it('should throw an error if actorTimes is too short', () => {
expect(
() =>
new JourneyItem({
lat: 48.689445,
lon: 6.17651,
duration: 1545,
distance: 48754,
actorTimes: [],
}),
).toThrow(ArgumentOutOfRangeException);
});
});

View File

@ -90,14 +90,14 @@ const candidate: CandidateEntity = CandidateEntity.create({
driverDuration: 13548,
driverSchedule: [
{
day: 0,
day: 1,
time: '07:00',
margin: 900,
},
],
passengerSchedule: [
{
day: 0,
day: 1,
time: '07:10',
margin: 900,
},
@ -140,6 +140,7 @@ const candidate: CandidateEntity = CandidateEntity.create({
],
},
]);
candidate.createJourneys = jest.fn().mockImplementation(() => candidate);
describe('Journey completer', () => {
it('should complete candidates with their journey', async () => {

View File

@ -5,161 +5,453 @@ import {
import { Role } from '@modules/ad/core/domain/ad.types';
import { Target } from '@modules/ad/core/domain/candidate.types';
import { ActorTime } from '@modules/ad/core/domain/value-objects/actor-time.value-object';
import { JourneyItem } from '@modules/ad/core/domain/value-objects/journey-item.value-object';
import { Journey } from '@modules/ad/core/domain/value-objects/journey.value-object';
describe('Journey value object', () => {
it('should create a journey value object', () => {
const journeyVO = new Journey({
firstDate: new Date('2023-09-20'),
lastDate: new Date('2024-09-20'),
actorTimes: [
new ActorTime({
role: Role.DRIVER,
target: Target.START,
time: '07:00',
minTime: '06:45',
maxTime: '07:15',
firstDate: new Date('2023-09-01'),
lastDate: new Date('2024-08-30'),
day: 5,
journeyItems: [
new JourneyItem({
lat: 48.689445,
lon: 6.17651,
duration: 0,
distance: 0,
actorTimes: [
new ActorTime({
role: Role.DRIVER,
target: Target.START,
firstDatetime: new Date('2023-09-01 07:00'),
firstMinDatetime: new Date('2023-09-01 06:45'),
firstMaxDatetime: new Date('2023-09-01 07:15'),
lastDatetime: new Date('2024-08-30 07:00'),
lastMinDatetime: new Date('2024-08-30 06:45'),
lastMaxDatetime: new Date('2024-08-30 07:15'),
}),
],
}),
new ActorTime({
role: Role.PASSENGER,
target: Target.START,
time: '07:10',
minTime: '06:55',
maxTime: '07:25',
new JourneyItem({
lat: 48.369445,
lon: 6.67487,
duration: 2100,
distance: 56878,
actorTimes: [
new ActorTime({
role: Role.DRIVER,
target: Target.NEUTRAL,
firstDatetime: new Date('2023-09-01 07:35'),
firstMinDatetime: new Date('2023-09-01 07:20'),
firstMaxDatetime: new Date('2023-09-01 07:50'),
lastDatetime: new Date('2024-08-30 07:35'),
lastMinDatetime: new Date('2024-08-30 07:20'),
lastMaxDatetime: new Date('2024-08-30 07:50'),
}),
new ActorTime({
role: Role.PASSENGER,
target: Target.START,
firstDatetime: new Date('2023-09-01 07:32'),
firstMinDatetime: new Date('2023-09-01 07:17'),
firstMaxDatetime: new Date('2023-09-01 07:47'),
lastDatetime: new Date('2024-08-30 07:32'),
lastMinDatetime: new Date('2024-08-30 07:17'),
lastMaxDatetime: new Date('2024-08-30 07:47'),
}),
],
}),
new ActorTime({
role: Role.DRIVER,
target: Target.FINISH,
time: '08:30',
minTime: '08:15',
maxTime: '08:45',
new JourneyItem({
lat: 47.98487,
lon: 6.9427,
duration: 3840,
distance: 76491,
actorTimes: [
new ActorTime({
role: Role.DRIVER,
target: Target.NEUTRAL,
firstDatetime: new Date('2023-09-01 08:04'),
firstMinDatetime: new Date('2023-09-01 07:51'),
firstMaxDatetime: new Date('2023-09-01 08:19'),
lastDatetime: new Date('2024-08-30 08:04'),
lastMinDatetime: new Date('2024-08-30 07:51'),
lastMaxDatetime: new Date('2024-08-30 08:19'),
}),
new ActorTime({
role: Role.PASSENGER,
target: Target.FINISH,
firstDatetime: new Date('2023-09-01 08:01'),
firstMinDatetime: new Date('2023-09-01 07:46'),
firstMaxDatetime: new Date('2023-09-01 08:16'),
lastDatetime: new Date('2024-08-30 08:01'),
lastMinDatetime: new Date('2024-08-30 07:46'),
lastMaxDatetime: new Date('2024-08-30 08:16'),
}),
],
}),
new ActorTime({
role: Role.PASSENGER,
target: Target.FINISH,
time: '08:40',
minTime: '08:25',
maxTime: '08:55',
new JourneyItem({
lat: 47.365987,
lon: 7.02154,
duration: 4980,
distance: 96475,
actorTimes: [
new ActorTime({
role: Role.DRIVER,
target: Target.FINISH,
firstDatetime: new Date('2023-09-01 08:23'),
firstMinDatetime: new Date('2023-09-01 08:08'),
firstMaxDatetime: new Date('2023-09-01 08:38'),
lastDatetime: new Date('2024-08-30 08:23'),
lastMinDatetime: new Date('2024-08-30 08:08'),
lastMaxDatetime: new Date('2024-08-30 08:38'),
}),
],
}),
],
day: 0,
time: '07:00',
margin: 900,
});
expect(journeyVO.day).toBe(0);
expect(journeyVO.time).toBe('07:00');
expect(journeyVO.margin).toBe(900);
expect(journeyVO.actorTimes).toHaveLength(4);
expect(journeyVO.firstDate.getDate()).toBe(20);
expect(journeyVO.lastDate.getMonth()).toBe(8);
expect(journeyVO.day).toBe(5);
expect(journeyVO.journeyItems).toHaveLength(4);
expect(journeyVO.firstDate.getDate()).toBe(1);
expect(journeyVO.lastDate.getMonth()).toBe(7);
});
it('should throw an exception if day is invalid', () => {
expect(() => {
new Journey({
firstDate: new Date('2023-09-20'),
lastDate: new Date('2024-09-20'),
actorTimes: [
new ActorTime({
role: Role.DRIVER,
target: Target.START,
time: '07:00',
minTime: '06:45',
maxTime: '07:15',
}),
new ActorTime({
role: Role.PASSENGER,
target: Target.START,
time: '07:10',
minTime: '06:55',
maxTime: '07:25',
}),
new ActorTime({
role: Role.DRIVER,
target: Target.FINISH,
time: '08:30',
minTime: '08:15',
maxTime: '08:45',
}),
new ActorTime({
role: Role.PASSENGER,
target: Target.FINISH,
time: '08:40',
minTime: '08:25',
maxTime: '08:55',
}),
],
day: 7,
time: '07:00',
margin: 900,
});
}).toThrow(ArgumentOutOfRangeException);
it('should throw an error if day is wrong', () => {
expect(
() =>
new Journey({
firstDate: new Date('2023-09-01'),
lastDate: new Date('2024-08-30'),
day: 7,
journeyItems: [
new JourneyItem({
lat: 48.689445,
lon: 6.17651,
duration: 0,
distance: 0,
actorTimes: [
new ActorTime({
role: Role.DRIVER,
target: Target.START,
firstDatetime: new Date('2023-09-01 07:00'),
firstMinDatetime: new Date('2023-09-01 06:45'),
firstMaxDatetime: new Date('2023-09-01 07:15'),
lastDatetime: new Date('2024-08-30 07:00'),
lastMinDatetime: new Date('2024-08-30 06:45'),
lastMaxDatetime: new Date('2024-08-30 07:15'),
}),
],
}),
new JourneyItem({
lat: 48.369445,
lon: 6.67487,
duration: 2100,
distance: 56878,
actorTimes: [
new ActorTime({
role: Role.DRIVER,
target: Target.NEUTRAL,
firstDatetime: new Date('2023-09-01 07:35'),
firstMinDatetime: new Date('2023-09-01 07:20'),
firstMaxDatetime: new Date('2023-09-01 07:50'),
lastDatetime: new Date('2024-08-30 07:35'),
lastMinDatetime: new Date('2024-08-30 07:20'),
lastMaxDatetime: new Date('2024-08-30 07:50'),
}),
new ActorTime({
role: Role.PASSENGER,
target: Target.START,
firstDatetime: new Date('2023-09-01 07:32'),
firstMinDatetime: new Date('2023-09-01 07:17'),
firstMaxDatetime: new Date('2023-09-01 07:47'),
lastDatetime: new Date('2024-08-30 07:32'),
lastMinDatetime: new Date('2024-08-30 07:17'),
lastMaxDatetime: new Date('2024-08-30 07:47'),
}),
],
}),
new JourneyItem({
lat: 47.98487,
lon: 6.9427,
duration: 3840,
distance: 76491,
actorTimes: [
new ActorTime({
role: Role.DRIVER,
target: Target.NEUTRAL,
firstDatetime: new Date('2023-09-01 08:04'),
firstMinDatetime: new Date('2023-09-01 07:51'),
firstMaxDatetime: new Date('2023-09-01 08:19'),
lastDatetime: new Date('2024-08-30 08:04'),
lastMinDatetime: new Date('2024-08-30 07:51'),
lastMaxDatetime: new Date('2024-08-30 08:19'),
}),
new ActorTime({
role: Role.PASSENGER,
target: Target.FINISH,
firstDatetime: new Date('2023-09-01 08:01'),
firstMinDatetime: new Date('2023-09-01 07:46'),
firstMaxDatetime: new Date('2023-09-01 08:16'),
lastDatetime: new Date('2024-08-30 08:01'),
lastMinDatetime: new Date('2024-08-30 07:46'),
lastMaxDatetime: new Date('2024-08-30 08:16'),
}),
],
}),
new JourneyItem({
lat: 47.365987,
lon: 7.02154,
duration: 4980,
distance: 96475,
actorTimes: [
new ActorTime({
role: Role.DRIVER,
target: Target.FINISH,
firstDatetime: new Date('2023-09-01 08:23'),
firstMinDatetime: new Date('2023-09-01 08:08'),
firstMaxDatetime: new Date('2023-09-01 08:38'),
lastDatetime: new Date('2024-08-30 08:23'),
lastMinDatetime: new Date('2024-08-30 08:08'),
lastMaxDatetime: new Date('2024-08-30 08:38'),
}),
],
}),
],
}),
).toThrow(ArgumentOutOfRangeException);
});
it('should throw an exception if actor times is too short', () => {
expect(() => {
new Journey({
firstDate: new Date('2023-09-20'),
lastDate: new Date('2024-09-20'),
actorTimes: [
new ActorTime({
role: Role.DRIVER,
target: Target.START,
time: '07:00',
minTime: '06:45',
maxTime: '07:15',
}),
new ActorTime({
role: Role.DRIVER,
target: Target.FINISH,
time: '08:30',
minTime: '08:15',
maxTime: '08:45',
}),
],
day: 0,
time: '07:00',
margin: 900,
});
}).toThrow(ArgumentOutOfRangeException);
it('should throw an error if dates are inconsistent', () => {
expect(
() =>
new Journey({
firstDate: new Date('2023-09-01'),
lastDate: new Date('2024-08-31'),
day: 5,
journeyItems: [
new JourneyItem({
lat: 48.689445,
lon: 6.17651,
duration: 0,
distance: 0,
actorTimes: [
new ActorTime({
role: Role.DRIVER,
target: Target.START,
firstDatetime: new Date('2023-09-01 07:00'),
firstMinDatetime: new Date('2023-09-01 06:45'),
firstMaxDatetime: new Date('2023-09-01 07:15'),
lastDatetime: new Date('2024-08-30 07:00'),
lastMinDatetime: new Date('2024-08-30 06:45'),
lastMaxDatetime: new Date('2024-08-30 07:15'),
}),
],
}),
new JourneyItem({
lat: 48.369445,
lon: 6.67487,
duration: 2100,
distance: 56878,
actorTimes: [
new ActorTime({
role: Role.DRIVER,
target: Target.NEUTRAL,
firstDatetime: new Date('2023-09-01 07:35'),
firstMinDatetime: new Date('2023-09-01 07:20'),
firstMaxDatetime: new Date('2023-09-01 07:50'),
lastDatetime: new Date('2024-08-30 07:35'),
lastMinDatetime: new Date('2024-08-30 07:20'),
lastMaxDatetime: new Date('2024-08-30 07:50'),
}),
new ActorTime({
role: Role.PASSENGER,
target: Target.START,
firstDatetime: new Date('2023-09-01 07:32'),
firstMinDatetime: new Date('2023-09-01 07:17'),
firstMaxDatetime: new Date('2023-09-01 07:47'),
lastDatetime: new Date('2024-08-30 07:32'),
lastMinDatetime: new Date('2024-08-30 07:17'),
lastMaxDatetime: new Date('2024-08-30 07:47'),
}),
],
}),
new JourneyItem({
lat: 47.98487,
lon: 6.9427,
duration: 3840,
distance: 76491,
actorTimes: [
new ActorTime({
role: Role.DRIVER,
target: Target.NEUTRAL,
firstDatetime: new Date('2023-09-01 08:04'),
firstMinDatetime: new Date('2023-09-01 07:51'),
firstMaxDatetime: new Date('2023-09-01 08:19'),
lastDatetime: new Date('2024-08-30 08:04'),
lastMinDatetime: new Date('2024-08-30 07:51'),
lastMaxDatetime: new Date('2024-08-30 08:19'),
}),
new ActorTime({
role: Role.PASSENGER,
target: Target.FINISH,
firstDatetime: new Date('2023-09-01 08:01'),
firstMinDatetime: new Date('2023-09-01 07:46'),
firstMaxDatetime: new Date('2023-09-01 08:16'),
lastDatetime: new Date('2024-08-30 08:01'),
lastMinDatetime: new Date('2024-08-30 07:46'),
lastMaxDatetime: new Date('2024-08-30 08:16'),
}),
],
}),
new JourneyItem({
lat: 47.365987,
lon: 7.02154,
duration: 4980,
distance: 96475,
actorTimes: [
new ActorTime({
role: Role.DRIVER,
target: Target.FINISH,
firstDatetime: new Date('2023-09-01 08:23'),
firstMinDatetime: new Date('2023-09-01 08:08'),
firstMaxDatetime: new Date('2023-09-01 08:38'),
lastDatetime: new Date('2024-08-30 08:23'),
lastMinDatetime: new Date('2024-08-30 08:08'),
lastMaxDatetime: new Date('2024-08-30 08:38'),
}),
],
}),
],
}),
).toThrow(ArgumentInvalidException);
expect(
() =>
new Journey({
firstDate: new Date('2024-08-30'),
lastDate: new Date('2023-09-01'),
day: 5,
journeyItems: [
new JourneyItem({
lat: 48.689445,
lon: 6.17651,
duration: 0,
distance: 0,
actorTimes: [
new ActorTime({
role: Role.DRIVER,
target: Target.START,
firstDatetime: new Date('2023-09-01 07:00'),
firstMinDatetime: new Date('2023-09-01 06:45'),
firstMaxDatetime: new Date('2023-09-01 07:15'),
lastDatetime: new Date('2024-08-30 07:00'),
lastMinDatetime: new Date('2024-08-30 06:45'),
lastMaxDatetime: new Date('2024-08-30 07:15'),
}),
],
}),
new JourneyItem({
lat: 48.369445,
lon: 6.67487,
duration: 2100,
distance: 56878,
actorTimes: [
new ActorTime({
role: Role.DRIVER,
target: Target.NEUTRAL,
firstDatetime: new Date('2023-09-01 07:35'),
firstMinDatetime: new Date('2023-09-01 07:20'),
firstMaxDatetime: new Date('2023-09-01 07:50'),
lastDatetime: new Date('2024-08-30 07:35'),
lastMinDatetime: new Date('2024-08-30 07:20'),
lastMaxDatetime: new Date('2024-08-30 07:50'),
}),
new ActorTime({
role: Role.PASSENGER,
target: Target.START,
firstDatetime: new Date('2023-09-01 07:32'),
firstMinDatetime: new Date('2023-09-01 07:17'),
firstMaxDatetime: new Date('2023-09-01 07:47'),
lastDatetime: new Date('2024-08-30 07:32'),
lastMinDatetime: new Date('2024-08-30 07:17'),
lastMaxDatetime: new Date('2024-08-30 07:47'),
}),
],
}),
new JourneyItem({
lat: 47.98487,
lon: 6.9427,
duration: 3840,
distance: 76491,
actorTimes: [
new ActorTime({
role: Role.DRIVER,
target: Target.NEUTRAL,
firstDatetime: new Date('2023-09-01 08:04'),
firstMinDatetime: new Date('2023-09-01 07:51'),
firstMaxDatetime: new Date('2023-09-01 08:19'),
lastDatetime: new Date('2024-08-30 08:04'),
lastMinDatetime: new Date('2024-08-30 07:51'),
lastMaxDatetime: new Date('2024-08-30 08:19'),
}),
new ActorTime({
role: Role.PASSENGER,
target: Target.FINISH,
firstDatetime: new Date('2023-09-01 08:01'),
firstMinDatetime: new Date('2023-09-01 07:46'),
firstMaxDatetime: new Date('2023-09-01 08:16'),
lastDatetime: new Date('2024-08-30 08:01'),
lastMinDatetime: new Date('2024-08-30 07:46'),
lastMaxDatetime: new Date('2024-08-30 08:16'),
}),
],
}),
new JourneyItem({
lat: 47.365987,
lon: 7.02154,
duration: 4980,
distance: 96475,
actorTimes: [
new ActorTime({
role: Role.DRIVER,
target: Target.FINISH,
firstDatetime: new Date('2023-09-01 08:23'),
firstMinDatetime: new Date('2023-09-01 08:08'),
firstMaxDatetime: new Date('2023-09-01 08:38'),
lastDatetime: new Date('2024-08-30 08:23'),
lastMinDatetime: new Date('2024-08-30 08:08'),
lastMaxDatetime: new Date('2024-08-30 08:38'),
}),
],
}),
],
}),
).toThrow(ArgumentInvalidException);
});
it('should throw an exception if dates are invalid', () => {
expect(() => {
new Journey({
firstDate: new Date('2023-09-20'),
lastDate: new Date('2023-09-19'),
actorTimes: [
new ActorTime({
role: Role.DRIVER,
target: Target.START,
time: '07:00',
minTime: '06:45',
maxTime: '07:15',
}),
new ActorTime({
role: Role.PASSENGER,
target: Target.START,
time: '07:10',
minTime: '06:55',
maxTime: '07:25',
}),
new ActorTime({
role: Role.DRIVER,
target: Target.FINISH,
time: '08:30',
minTime: '08:15',
maxTime: '08:45',
}),
new ActorTime({
role: Role.PASSENGER,
target: Target.FINISH,
time: '08:40',
minTime: '08:25',
maxTime: '08:55',
}),
],
day: 0,
time: '07:00',
margin: 900,
});
}).toThrow(ArgumentInvalidException);
it('should throw an error if journeyItems is too short', () => {
expect(
() =>
new Journey({
firstDate: new Date('2023-09-01'),
lastDate: new Date('2024-08-30'),
day: 5,
journeyItems: [
new JourneyItem({
lat: 48.689445,
lon: 6.17651,
duration: 0,
distance: 0,
actorTimes: [
new ActorTime({
role: Role.DRIVER,
target: Target.START,
firstDatetime: new Date('2023-09-01 07:00'),
firstMinDatetime: new Date('2023-09-01 06:45'),
firstMaxDatetime: new Date('2023-09-01 07:15'),
lastDatetime: new Date('2024-08-30 07:00'),
lastMinDatetime: new Date('2024-08-30 06:45'),
lastMaxDatetime: new Date('2024-08-30 07:15'),
}),
],
}),
],
}),
).toThrow(ArgumentInvalidException);
});
});

View File

@ -23,8 +23,8 @@ describe('Point value object', () => {
lat: 48.689446,
lon: 6.17651,
});
expect(pointVO.isSame(identicalPointVO)).toBeTruthy();
expect(pointVO.isSame(differentPointVO)).toBeFalsy();
expect(pointVO.equals(identicalPointVO)).toBeTruthy();
expect(pointVO.equals(differentPointVO)).toBeFalsy();
});
it('should throw an exception if longitude is invalid', () => {
expect(() => {