refactor adapters

This commit is contained in:
sbriat 2023-07-26 16:30:50 +02:00
parent c530bc55f5
commit b55e122bef
22 changed files with 695 additions and 382 deletions

View File

@ -97,7 +97,7 @@
"main.ts" "main.ts"
], ],
"rootDir": "src", "rootDir": "src",
"testRegex": ".converter.*\\.spec\\.ts$", "testRegex": ".*\\.spec\\.ts$",
"transform": { "transform": {
"^.+\\.(t|j)s$": "ts-jest" "^.+\\.(t|j)s$": "ts-jest"
}, },

View File

@ -2,4 +2,5 @@ export const AD_MESSAGE_PUBLISHER = Symbol('AD_MESSAGE_PUBLISHER');
export const PARAMS_PROVIDER = Symbol('PARAMS_PROVIDER'); export const PARAMS_PROVIDER = Symbol('PARAMS_PROVIDER');
export const TIMEZONE_FINDER = Symbol('TIMEZONE_FINDER'); export const TIMEZONE_FINDER = Symbol('TIMEZONE_FINDER');
export const TIME_CONVERTER = Symbol('TIME_CONVERTER'); export const TIME_CONVERTER = Symbol('TIME_CONVERTER');
export const DATETIME_TRANSFORMER = Symbol('DATETIME_TRANSFORMER');
export const AD_REPOSITORY = Symbol('AD_REPOSITORY'); export const AD_REPOSITORY = Symbol('AD_REPOSITORY');

View File

@ -4,6 +4,7 @@ import { CqrsModule } from '@nestjs/cqrs';
import { import {
AD_MESSAGE_PUBLISHER, AD_MESSAGE_PUBLISHER,
AD_REPOSITORY, AD_REPOSITORY,
DATETIME_TRANSFORMER,
PARAMS_PROVIDER, PARAMS_PROVIDER,
TIMEZONE_FINDER, TIMEZONE_FINDER,
TIME_CONVERTER, TIME_CONVERTER,
@ -19,6 +20,7 @@ import { FindAdByIdQueryHandler } from './core/application/queries/find-ad-by-id
import { PublishMessageWhenAdIsCreatedDomainEventHandler } from './core/application/event-handlers/publish-message-when-ad-is-created.domain-event-handler'; import { PublishMessageWhenAdIsCreatedDomainEventHandler } from './core/application/event-handlers/publish-message-when-ad-is-created.domain-event-handler';
import { PrismaService } from './infrastructure/prisma.service'; import { PrismaService } from './infrastructure/prisma.service';
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module'; import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
import { DateTimeTransformer } from './infrastructure/datetime-transformer';
const grpcControllers = [CreateAdGrpcController, FindAdByIdGrpcController]; const grpcControllers = [CreateAdGrpcController, FindAdByIdGrpcController];
@ -60,6 +62,10 @@ const adapters: Provider[] = [
provide: TIME_CONVERTER, provide: TIME_CONVERTER,
useClass: TimeConverter, useClass: TimeConverter,
}, },
{
provide: DATETIME_TRANSFORMER,
useClass: DateTimeTransformer,
},
]; ];
@Module({ @Module({

View File

@ -3,9 +3,8 @@ import { CreateAdCommand } from './create-ad.command';
import { Inject } from '@nestjs/common'; import { Inject } from '@nestjs/common';
import { import {
AD_REPOSITORY, AD_REPOSITORY,
DATETIME_TRANSFORMER,
PARAMS_PROVIDER, PARAMS_PROVIDER,
TIMEZONE_FINDER,
TIME_CONVERTER,
} from '@modules/ad/ad.di-tokens'; } from '@modules/ad/ad.di-tokens';
import { AdEntity } from '@modules/ad/core/domain/ad.entity'; import { AdEntity } from '@modules/ad/core/domain/ad.entity';
import { Waypoint } from '../../types/waypoint'; import { Waypoint } from '../../types/waypoint';
@ -15,9 +14,7 @@ import { DefaultParamsProviderPort } from '../../ports/default-params-provider.p
import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors'; import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
import { AggregateID, ConflictException } from '@mobicoop/ddd-library'; import { AggregateID, ConflictException } from '@mobicoop/ddd-library';
import { ScheduleItem } from '../../types/schedule-item'; import { ScheduleItem } from '../../types/schedule-item';
import { TimeConverterPort } from '../../ports/time-converter.port'; import { DateTimeTransformerPort } from '../../ports/datetime-transformer.port';
import { TimezoneFinderPort } from '../../ports/timezone-finder.port';
import { Frequency } from '@modules/ad/core/domain/ad.types';
@CommandHandler(CreateAdCommand) @CommandHandler(CreateAdCommand)
export class CreateAdService implements ICommandHandler { export class CreateAdService implements ICommandHandler {
@ -28,52 +25,65 @@ export class CreateAdService implements ICommandHandler {
private readonly repository: AdRepositoryPort, private readonly repository: AdRepositoryPort,
@Inject(PARAMS_PROVIDER) @Inject(PARAMS_PROVIDER)
private readonly defaultParamsProvider: DefaultParamsProviderPort, private readonly defaultParamsProvider: DefaultParamsProviderPort,
@Inject(TIMEZONE_FINDER) @Inject(DATETIME_TRANSFORMER)
private readonly timezoneFinder: TimezoneFinderPort, private readonly datetimeTransformer: DateTimeTransformerPort,
@Inject(TIME_CONVERTER)
private readonly timeConverter: TimeConverterPort,
) { ) {
this._defaultParams = defaultParamsProvider.getParams(); this._defaultParams = defaultParamsProvider.getParams();
} }
async execute(command: CreateAdCommand): Promise<AggregateID> { async execute(command: CreateAdCommand): Promise<AggregateID> {
const timezone = this.timezoneFinder.timezones(
command.waypoints[0].lon,
command.waypoints[0].lat,
this._defaultParams.DEFAULT_TIMEZONE,
)[0];
const ad = AdEntity.create( const ad = AdEntity.create(
{ {
userId: command.userId, userId: command.userId,
driver: command.driver, driver: command.driver,
passenger: command.passenger, passenger: command.passenger,
frequency: command.frequency, frequency: command.frequency,
fromDate: this.getFromDate( fromDate: this.datetimeTransformer.fromDate(
command.fromDate, {
date: command.fromDate,
time: command.schedule[0].time,
coordinates: {
lon: command.waypoints[0].lon,
lat: command.waypoints[0].lat,
},
},
command.frequency, command.frequency,
command.schedule[0].time,
timezone,
), ),
toDate: this.getToDate( toDate: this.datetimeTransformer.toDate(
command.fromDate,
command.toDate, command.toDate,
{
date: command.fromDate,
time: command.schedule[0].time,
coordinates: {
lon: command.waypoints[0].lon,
lat: command.waypoints[0].lat,
},
},
command.frequency, command.frequency,
command.schedule[0].time,
timezone,
), ),
schedule: command.schedule.map((scheduleItem: ScheduleItem) => ({ schedule: command.schedule.map((scheduleItem: ScheduleItem) => ({
day: this.getDay( day: this.datetimeTransformer.day(
scheduleItem.day, scheduleItem.day,
command.fromDate, {
date: command.fromDate,
time: scheduleItem.time,
coordinates: {
lon: command.waypoints[0].lon,
lat: command.waypoints[0].lat,
},
},
command.frequency, command.frequency,
scheduleItem.time,
timezone,
), ),
time: this.getTime( time: this.datetimeTransformer.time(
command.fromDate, {
date: command.fromDate,
time: scheduleItem.time,
coordinates: {
lon: command.waypoints[0].lon,
lat: command.waypoints[0].lat,
},
},
command.frequency, command.frequency,
scheduleItem.time,
timezone,
), ),
margin: scheduleItem.margin, margin: scheduleItem.margin,
})), })),
@ -116,71 +126,4 @@ export class CreateAdService implements ICommandHandler {
throw error; throw error;
} }
} }
private getFromDate = (
fromDate: string,
frequency: Frequency,
time: string,
timezone: string,
): string => {
if (frequency === Frequency.RECURRENT) return fromDate;
return this.timeConverter
.localStringDateTimeToUtcDate(fromDate, time, timezone)
.toISOString();
};
private getToDate = (
fromDate: string,
toDate: string,
frequency: Frequency,
time: string,
timezone: string,
): string => {
if (frequency === Frequency.RECURRENT) return toDate;
return this.getFromDate(fromDate, frequency, time, timezone);
};
private getDay = (
day: number,
fromDate: string,
frequency: Frequency,
time: string,
timezone: string,
): number => {
if (frequency === Frequency.RECURRENT)
return this.getRecurrentDay(day, time, timezone);
return new Date(
this.getFromDate(fromDate, frequency, time, timezone),
).getDay();
};
private getTime = (
fromDate: string,
frequency: Frequency,
time: string,
timezone: string,
): string => {
if (frequency === Frequency.RECURRENT)
return this.timeConverter.localStringTimeToUtcStringTime(time, timezone);
return new Date(
this.getFromDate(fromDate, frequency, time, timezone),
).toTimeString();
};
private getRecurrentDay = (
day: number,
time: string,
timezone: string,
): number => {
// continuer ici
const baseDate = new Date('1970-01-01T00:00:00Z');
const hour = parseInt(time.split(':')[0]);
const utcHour = parseInt(
this.timeConverter
.localStringTimeToUtcStringTime(time, timezone)
.split(':')[0],
);
if (utcHour >= 11 && hour < 13) return day > 0 ? day - 1 : 6;
return day;
};
} }

View File

@ -0,0 +1,26 @@
export interface DateTimeTransformerPort {
fromDate(geoFromDate: GeoDateTime, frequency: Frequency): string;
toDate(
toDate: string,
geoFromDate: GeoDateTime,
frequency: Frequency,
): string;
day(day: number, geoFromDate: GeoDateTime, frequency: Frequency): number;
time(geoFromDate: GeoDateTime, frequency: Frequency): string;
}
export type GeoDateTime = {
date: string;
time: string;
coordinates: Coordinates;
};
export type Coordinates = {
lon: number;
lat: number;
};
export enum Frequency {
PUNCTUAL = 'PUNCTUAL',
RECURRENT = 'RECURRENT',
}

View File

@ -6,5 +6,5 @@ export interface TimeConverterPort {
timezone: string, timezone: string,
dst?: boolean, dst?: boolean,
): Date; ): Date;
utcDatetimeToLocalTime(isoString: string, timezone: string): string; utcUnixEpochDayFromTime(time: string, timezone: string): number;
} }

View File

@ -1,4 +1,3 @@
export interface TimezoneFinderPort { export interface TimezoneFinderPort {
timezones(lon: number, lat: number, defaultTimezone?: string): string[]; timezones(lon: number, lat: number, defaultTimezone?: string): string[];
offset(timezone: string): number;
} }

View File

@ -7,7 +7,7 @@ export class AdCreatedDomainEvent extends DomainEvent {
readonly frequency: string; readonly frequency: string;
readonly fromDate: string; readonly fromDate: string;
readonly toDate: string; readonly toDate: string;
readonly schedule: ScheduleDay[]; readonly schedule: ScheduleItem[];
readonly seatsProposed: number; readonly seatsProposed: number;
readonly seatsRequested: number; readonly seatsRequested: number;
readonly strict: boolean; readonly strict: boolean;
@ -29,7 +29,7 @@ export class AdCreatedDomainEvent extends DomainEvent {
} }
} }
export class ScheduleDay { export class ScheduleItem {
day: number; day: number;
time: string; time: string;
margin: number; margin: number;

View File

@ -6,13 +6,13 @@ import { ValueObject } from '@mobicoop/ddd-library';
* */ * */
export interface ScheduleItemProps { export interface ScheduleItemProps {
day: number; day?: number;
time: string; time: string;
margin?: number; margin?: number;
} }
export class ScheduleItem extends ValueObject<ScheduleItemProps> { export class ScheduleItem extends ValueObject<ScheduleItemProps> {
get day(): number { get day(): number | undefined {
return this.props.day; return this.props.day;
} }

View File

@ -0,0 +1,132 @@
import { Inject, Injectable } from '@nestjs/common';
import {
DateTimeTransformerPort,
Frequency,
GeoDateTime,
} from '../core/application/ports/datetime-transformer.port';
import { TimeConverterPort } from '../core/application/ports/time-converter.port';
import {
PARAMS_PROVIDER,
TIMEZONE_FINDER,
TIME_CONVERTER,
} from '../ad.di-tokens';
import { TimezoneFinderPort } from '../core/application/ports/timezone-finder.port';
import { DefaultParamsProviderPort } from '../core/application/ports/default-params-provider.port';
@Injectable()
export class DateTimeTransformer implements DateTimeTransformerPort {
private readonly _defaultTimezone: string;
constructor(
@Inject(PARAMS_PROVIDER)
private readonly defaultParamsProvider: DefaultParamsProviderPort,
@Inject(TIMEZONE_FINDER)
private readonly timezoneFinder: TimezoneFinderPort,
@Inject(TIME_CONVERTER) private readonly timeConverter: TimeConverterPort,
) {
this._defaultTimezone = defaultParamsProvider.getParams().DEFAULT_TIMEZONE;
}
/**
* Compute the fromDate : if an ad is punctual, the departure date
* is converted to UTC with the time and timezone
*/
fromDate = (geoFromDate: GeoDateTime, frequency: Frequency): string => {
if (frequency === Frequency.RECURRENT) return geoFromDate.date;
return this.timeConverter
.localStringDateTimeToUtcDate(
geoFromDate.date,
geoFromDate.time,
this.timezoneFinder.timezones(
geoFromDate.coordinates.lon,
geoFromDate.coordinates.lat,
this._defaultTimezone,
)[0],
)
.toISOString()
.split('T')[0];
};
/**
* Get the toDate depending on frequency, time and timezone :
* if the ad is punctual, the toDate is equal to the fromDate
*/
toDate = (
toDate: string,
geoFromDate: GeoDateTime,
frequency: Frequency,
): string => {
if (frequency === Frequency.RECURRENT) return toDate;
return this.fromDate(geoFromDate, frequency);
};
/**
* Get the day for a schedule item :
* - if the ad is punctual, the day is infered from fromDate
* - if the ad is recurrent, the day is computed by converting the time to utc
*/
day = (
day: number,
geoFromDate: GeoDateTime,
frequency: Frequency,
): number => {
if (frequency === Frequency.RECURRENT)
return this.recurrentDay(
day,
geoFromDate.time,
this.timezoneFinder.timezones(
geoFromDate.coordinates.lon,
geoFromDate.coordinates.lat,
this._defaultTimezone,
)[0],
);
return new Date(this.fromDate(geoFromDate, frequency)).getDay();
};
/**
* Get the utc time
*/
time = (geoFromDate: GeoDateTime, frequency: Frequency): string => {
if (frequency === Frequency.RECURRENT)
return this.timeConverter.localStringTimeToUtcStringTime(
geoFromDate.time,
this.timezoneFinder.timezones(
geoFromDate.coordinates.lon,
geoFromDate.coordinates.lat,
this._defaultTimezone,
)[0],
);
return this.timeConverter
.localStringDateTimeToUtcDate(
geoFromDate.date,
geoFromDate.time,
this.timezoneFinder.timezones(
geoFromDate.coordinates.lon,
geoFromDate.coordinates.lat,
this._defaultTimezone,
)[0],
)
.toISOString()
.split('T')[1]
.split(':', 2)
.join(':');
};
/**
* Get the day for a schedule item for a recurrent ad
* The day may change when transforming from local timezone to utc
*/
private recurrentDay = (
day: number,
time: string,
timezone: string,
): number => {
const unixEpochDay = 4; // 1970-01-01 is a thursday !
const utcBaseDay = this.timeConverter.utcUnixEpochDayFromTime(
time,
timezone,
);
if (unixEpochDay == utcBaseDay) return day;
if (unixEpochDay > utcBaseDay) return day > 0 ? day - 1 : 6;
return day < 6 ? day + 1 : 0;
};
}

View File

@ -4,12 +4,12 @@ import { TimeConverterPort } from '../core/application/ports/time-converter.port
@Injectable() @Injectable()
export class TimeConverter implements TimeConverterPort { export class TimeConverter implements TimeConverterPort {
private readonly BASE_DATE = '1970-01-01'; private readonly UNIX_EPOCH = '1970-01-01';
localStringTimeToUtcStringTime = (time: string, timezone: string): string => { localStringTimeToUtcStringTime = (time: string, timezone: string): string => {
try { try {
if (!time || !timezone) throw new Error(); if (!time || !timezone) throw new Error();
return new DateTime(`${this.BASE_DATE}T${time}`, TimeZone.zone(timezone)) return new DateTime(`${this.UNIX_EPOCH}T${time}`, TimeZone.zone(timezone))
.convert(TimeZone.zone('UTC')) .convert(TimeZone.zone('UTC'))
.format('HH:mm'); .format('HH:mm');
} catch (e) { } catch (e) {
@ -25,21 +25,29 @@ export class TimeConverter implements TimeConverterPort {
): Date => { ): Date => {
try { try {
if (!time || !timezone) throw new Error(); if (!time || !timezone) throw new Error();
return new DateTime(`${date}T${time}`, TimeZone.zone(timezone, dst)) return new Date(
.convert(TimeZone.zone('UTC')) new DateTime(
.toDate(); `${date}T${time}`,
TimeZone.zone(timezone, dst),
).toIsoString(),
);
} catch (e) { } catch (e) {
return undefined; return undefined;
} }
}; };
utcDatetimeToLocalTime = (isoString: string, timezone: string): string => { utcUnixEpochDayFromTime = (time: string, timezone: string): number => {
try { try {
return new DateTime(isoString) if (!time || !timezone) throw new Error();
.convert(TimeZone.zone(timezone)) return new Date(
.toString() new DateTime(
.split('T')[1] `${this.UNIX_EPOCH}T${time}`,
.substring(0, 5); TimeZone.zone(timezone, false),
)
.convert(TimeZone.zone('UTC'))
.toIsoString()
.split('T')[0],
).getDay();
} catch (e) { } catch (e) {
return undefined; return undefined;
} }

View File

@ -1,7 +1,6 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { find } from 'geo-tz'; import { find } from 'geo-tz';
import { TimezoneFinderPort } from '../core/application/ports/timezone-finder.port'; import { TimezoneFinderPort } from '../core/application/ports/timezone-finder.port';
import { zone } from 'timezonecomplete';
@Injectable() @Injectable()
export class TimezoneFinder implements TimezoneFinderPort { export class TimezoneFinder implements TimezoneFinderPort {
@ -14,7 +13,4 @@ export class TimezoneFinder implements TimezoneFinderPort {
if (defaultTimezone && foundTimezones.length == 0) return [defaultTimezone]; if (defaultTimezone && foundTimezones.length == 0) return [defaultTimezone];
return foundTimezones; return foundTimezones;
}; };
offset = (timezone: string): number =>
zone(timezone).offsetForUtc(1970, 1, 1, 0, 0, 0);
} }

View File

@ -1,10 +1,4 @@
import { import { AD_MESSAGE_PUBLISHER, AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
AD_MESSAGE_PUBLISHER,
AD_REPOSITORY,
PARAMS_PROVIDER,
TIMEZONE_FINDER,
TIME_CONVERTER,
} from '@modules/ad/ad.di-tokens';
import { AdMapper } from '@modules/ad/ad.mapper'; import { AdMapper } from '@modules/ad/ad.mapper';
import { AdEntity } from '@modules/ad/core/domain/ad.entity'; import { AdEntity } from '@modules/ad/core/domain/ad.entity';
import { import {
@ -13,10 +7,7 @@ import {
Frequency, Frequency,
} from '@modules/ad/core/domain/ad.types'; } from '@modules/ad/core/domain/ad.types';
import { AdRepository } from '@modules/ad/infrastructure/ad.repository'; import { AdRepository } from '@modules/ad/infrastructure/ad.repository';
import { DefaultParamsProvider } from '@modules/ad/infrastructure/default-params-provider';
import { PrismaService } from '@modules/ad/infrastructure/prisma.service'; import { PrismaService } from '@modules/ad/infrastructure/prisma.service';
import { TimeConverter } from '@modules/ad/infrastructure/time-converter';
import { TimezoneFinder } from '@modules/ad/infrastructure/timezone-finder';
import { ConfigModule } from '@nestjs/config'; import { ConfigModule } from '@nestjs/config';
import { EventEmitterModule } from '@nestjs/event-emitter'; import { EventEmitterModule } from '@nestjs/event-emitter';
import { Test } from '@nestjs/testing'; import { Test } from '@nestjs/testing';
@ -133,22 +124,6 @@ describe('Ad Repository', () => {
providers: [ providers: [
PrismaService, PrismaService,
AdMapper, AdMapper,
{
provide: AD_REPOSITORY,
useClass: AdRepository,
},
{
provide: PARAMS_PROVIDER,
useClass: DefaultParamsProvider,
},
{
provide: TIMEZONE_FINDER,
useClass: TimezoneFinder,
},
{
provide: TIME_CONVERTER,
useClass: TimeConverter,
},
{ {
provide: AD_MESSAGE_PUBLISHER, provide: AD_MESSAGE_PUBLISHER,
useValue: mockMessagePublisher, useValue: mockMessagePublisher,
@ -193,12 +168,11 @@ describe('Ad Repository', () => {
frequency: Frequency.PUNCTUAL, frequency: Frequency.PUNCTUAL,
fromDate: '2023-02-01', fromDate: '2023-02-01',
toDate: '2023-02-01', toDate: '2023-02-01',
schedule: { schedule: [
wed: '12:05', {
}, time: '12:05',
marginDurations: { },
wed: 900, ],
},
seatsProposed: 3, seatsProposed: 3,
seatsRequested: 1, seatsRequested: 1,
strict: false, strict: false,
@ -233,15 +207,7 @@ describe('Ad Repository', () => {
const defaultAdProps: DefaultAdProps = { const defaultAdProps: DefaultAdProps = {
driver: false, driver: false,
passenger: true, passenger: true,
marginDurations: { marginDuration: 900,
mon: 900,
tue: 900,
wed: 900,
thu: 900,
fri: 900,
sat: 900,
sun: 900,
},
seatsProposed: 3, seatsProposed: 3,
seatsRequested: 1, seatsRequested: 1,
strict: false, strict: false,

View File

@ -1,5 +1,9 @@
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { AD_REPOSITORY, PARAMS_PROVIDER } from '@modules/ad/ad.di-tokens'; import {
AD_REPOSITORY,
DATETIME_TRANSFORMER,
PARAMS_PROVIDER,
} from '@modules/ad/ad.di-tokens';
import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto'; import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto';
import { CreateAdRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/create-ad.request.dto'; import { CreateAdRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/create-ad.request.dto';
import { AggregateID } from '@mobicoop/ddd-library'; import { AggregateID } from '@mobicoop/ddd-library';
@ -10,6 +14,7 @@ import { DefaultParamsProviderPort } from '@modules/ad/core/application/ports/de
import { CreateAdService } from '@modules/ad/core/application/commands/create-ad/create-ad.service'; import { CreateAdService } from '@modules/ad/core/application/commands/create-ad/create-ad.service';
import { CreateAdCommand } from '@modules/ad/core/application/commands/create-ad/create-ad.command'; import { CreateAdCommand } from '@modules/ad/core/application/commands/create-ad/create-ad.command';
import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors'; import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.port';
const originWaypoint: WaypointDto = { const originWaypoint: WaypointDto = {
position: 0, position: 0,
@ -33,9 +38,11 @@ const punctualCreateAdRequest: CreateAdRequestDto = {
userId: '4eb6a6af-ecfd-41c3-9118-473a507014d4', userId: '4eb6a6af-ecfd-41c3-9118-473a507014d4',
fromDate: '2023-12-21', fromDate: '2023-12-21',
toDate: '2023-12-21', toDate: '2023-12-21',
schedule: { schedule: [
thu: '08:15', {
}, time: '08:15',
},
],
driver: true, driver: true,
passenger: true, passenger: true,
seatsRequested: 1, seatsRequested: 1,
@ -58,13 +65,7 @@ const mockAdRepository = {
const mockDefaultParamsProvider: DefaultParamsProviderPort = { const mockDefaultParamsProvider: DefaultParamsProviderPort = {
getParams: () => { getParams: () => {
return { return {
MON_MARGIN: 900, DEPARTURE_TIME_MARGIN: 900,
TUE_MARGIN: 900,
WED_MARGIN: 900,
THU_MARGIN: 900,
FRI_MARGIN: 900,
SAT_MARGIN: 900,
SUN_MARGIN: 900,
DRIVER: false, DRIVER: false,
SEATS_PROPOSED: 3, SEATS_PROPOSED: 3,
PASSENGER: true, PASSENGER: true,
@ -75,6 +76,13 @@ const mockDefaultParamsProvider: DefaultParamsProviderPort = {
}, },
}; };
const mockDateTimeTransformer: DateTimeTransformerPort = {
fromDate: jest.fn(),
toDate: jest.fn(),
day: jest.fn(),
time: jest.fn(),
};
describe('create-ad.service', () => { describe('create-ad.service', () => {
let createAdService: CreateAdService; let createAdService: CreateAdService;
@ -89,6 +97,10 @@ describe('create-ad.service', () => {
provide: PARAMS_PROVIDER, provide: PARAMS_PROVIDER,
useValue: mockDefaultParamsProvider, useValue: mockDefaultParamsProvider,
}, },
{
provide: DATETIME_TRANSFORMER,
useValue: mockDateTimeTransformer,
},
CreateAdService, CreateAdService,
], ],
}).compile(); }).compile();
@ -102,7 +114,7 @@ describe('create-ad.service', () => {
describe('execution', () => { describe('execution', () => {
const createAdCommand = new CreateAdCommand(punctualCreateAdRequest); const createAdCommand = new CreateAdCommand(punctualCreateAdRequest);
it('should create a new ad', async () => { it('should create a new punctual ad', async () => {
AdEntity.create = jest.fn().mockReturnValue({ AdEntity.create = jest.fn().mockReturnValue({
id: '047a6ecf-23d4-4d3e-877c-3225d560a8da', id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
}); });

View File

@ -7,7 +7,6 @@ import {
} from '@modules/ad/core/domain/ad.types'; } from '@modules/ad/core/domain/ad.types';
import { FindAdByIdQuery } from '@modules/ad/core/application/queries/find-ad-by-id/find-ad-by-id.query'; import { FindAdByIdQuery } from '@modules/ad/core/application/queries/find-ad-by-id/find-ad-by-id.query';
import { FindAdByIdQueryHandler } from '@modules/ad/core/application/queries/find-ad-by-id/find-ad-by-id.query-handler'; import { FindAdByIdQueryHandler } from '@modules/ad/core/application/queries/find-ad-by-id/find-ad-by-id.query-handler';
import { MarginDurationsProps } from '@modules/ad/core/domain/value-objects/margin-durations.value-object';
import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object'; import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
@ -37,15 +36,6 @@ const destinationWaypointProps: WaypointProps = {
}, },
}, },
}; };
const marginDurationsProps: MarginDurationsProps = {
mon: 600,
tue: 600,
wed: 600,
thu: 600,
fri: 600,
sat: 600,
sun: 600,
};
const baseCreateAdProps = { const baseCreateAdProps = {
userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36', userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36',
seatsProposed: 3, seatsProposed: 3,
@ -56,31 +46,24 @@ const baseCreateAdProps = {
const punctualCreateAdProps = { const punctualCreateAdProps = {
fromDate: '2023-06-22', fromDate: '2023-06-22',
toDate: '2023-06-22', toDate: '2023-06-22',
schedule: { schedule: [
wed: '08:30', {
}, time: '08:30',
},
],
frequency: Frequency.PUNCTUAL, frequency: Frequency.PUNCTUAL,
}; };
const punctualPassengerCreateAdProps: CreateAdProps = { const punctualPassengerCreateAdProps: CreateAdProps = {
...baseCreateAdProps, ...baseCreateAdProps,
...punctualCreateAdProps, ...punctualCreateAdProps,
marginDurations: marginDurationsProps,
driver: false, driver: false,
passenger: true, passenger: true,
}; };
const defaultAdProps: DefaultAdProps = { const defaultAdProps: DefaultAdProps = {
marginDuration: 900,
driver: false, driver: false,
passenger: true, passenger: true,
marginDurations: {
mon: 900,
tue: 900,
wed: 900,
thu: 900,
fri: 900,
sat: 900,
sun: 900,
},
seatsProposed: 3, seatsProposed: 3,
seatsRequested: 1, seatsRequested: 1,
strict: false, strict: false,

View File

@ -1,47 +0,0 @@
import { MarginDurations } from '@modules/ad/core/domain/value-objects/margin-durations.value-object';
describe('Margin durations value object', () => {
it('should create a margin durations value object', () => {
const marginDurationsVO = new MarginDurations({
mon: 600,
tue: 610,
wed: 620,
thu: 630,
fri: 640,
sat: 650,
sun: 660,
});
expect(marginDurationsVO.mon).toBe(600);
expect(marginDurationsVO.tue).toBe(610);
expect(marginDurationsVO.wed).toBe(620);
expect(marginDurationsVO.thu).toBe(630);
expect(marginDurationsVO.fri).toBe(640);
expect(marginDurationsVO.sat).toBe(650);
expect(marginDurationsVO.sun).toBe(660);
});
it('should update margin durations value object values', () => {
const marginDurationsVO = new MarginDurations({
mon: 600,
tue: 610,
wed: 620,
thu: 630,
fri: 640,
sat: 650,
sun: 660,
});
marginDurationsVO.mon = 700;
marginDurationsVO.tue = 710;
marginDurationsVO.wed = 720;
marginDurationsVO.thu = 730;
marginDurationsVO.fri = 740;
marginDurationsVO.sat = 750;
marginDurationsVO.sun = 760;
expect(marginDurationsVO.mon).toBe(700);
expect(marginDurationsVO.tue).toBe(710);
expect(marginDurationsVO.wed).toBe(720);
expect(marginDurationsVO.thu).toBe(730);
expect(marginDurationsVO.fri).toBe(740);
expect(marginDurationsVO.sat).toBe(750);
expect(marginDurationsVO.sun).toBe(760);
});
});

View File

@ -39,20 +39,13 @@ describe('Publish message when ad is created domain event handler', () => {
frequency: Frequency.PUNCTUAL, frequency: Frequency.PUNCTUAL,
fromDate: '2023-06-28', fromDate: '2023-06-28',
toDate: '2023-06-28', toDate: '2023-06-28',
monTime: undefined, schedule: [
tueTime: undefined, {
wedTime: '07:15', day: 3,
thuTime: undefined, time: '07:15',
friTime: undefined, margin: 900,
satTime: undefined, },
sunTime: undefined, ],
monMarginDuration: 900,
tueMarginDuration: 900,
wedMarginDuration: 900,
thuMarginDuration: 900,
friMarginDuration: 900,
satMarginDuration: 900,
sunMarginDuration: 900,
seatsProposed: 3, seatsProposed: 3,
seatsRequested: 1, seatsRequested: 1,
strict: false, strict: false,
@ -88,7 +81,7 @@ describe('Publish message when ad is created domain event handler', () => {
expect(mockMessagePublisher.publish).toHaveBeenCalledTimes(1); expect(mockMessagePublisher.publish).toHaveBeenCalledTimes(1);
expect(mockMessagePublisher.publish).toHaveBeenCalledWith( expect(mockMessagePublisher.publish).toHaveBeenCalledWith(
'ad.created', 'ad.created',
'{"id":"some-domain-event-id","aggregateId":"some-aggregate-id","userId":"some-user-id","driver":false,"passenger":true,"frequency":"PUNCTUAL","fromDate":"2023-06-28","toDate":"2023-06-28","wedTime":"07:15","monMarginDuration":900,"tueMarginDuration":900,"wedMarginDuration":900,"thuMarginDuration":900,"friMarginDuration":900,"satMarginDuration":900,"sunMarginDuration":900,"seatsProposed":3,"seatsRequested":1,"strict":false,"waypoints":[{"position":0,"houseNumber":"5","street":"Avenue Foch","locality":"Nancy","postalCode":"54000","country":"France","lat":48.689445,"lon":6.1765102},{"position":1,"locality":"Paris","postalCode":"75000","country":"France","lat":48.8566,"lon":2.3522}],"metadata":{"timestamp":1687928400000,"correlationId":"some-correlation-id"}}', '{"id":"some-domain-event-id","aggregateId":"some-aggregate-id","userId":"some-user-id","driver":false,"passenger":true,"frequency":"PUNCTUAL","fromDate":"2023-06-28","toDate":"2023-06-28","schedule":[{"day":3,"time":"07:15","margin":900}],"seatsProposed":3,"seatsRequested":1,"strict":false,"waypoints":[{"position":0,"houseNumber":"5","street":"Avenue Foch","locality":"Nancy","postalCode":"54000","country":"France","lat":48.689445,"lon":6.1765102},{"position":1,"locality":"Paris","postalCode":"75000","country":"France","lat":48.8566,"lon":2.3522}],"metadata":{"timestamp":1687928400000,"correlationId":"some-correlation-id"}}',
); );
}); });
}); });

View File

@ -1,55 +1,9 @@
import {
PARAMS_PROVIDER,
TIMEZONE_FINDER,
TIME_CONVERTER,
} from '@modules/ad/ad.di-tokens';
import { AdMapper } from '@modules/ad/ad.mapper'; import { AdMapper } from '@modules/ad/ad.mapper';
import { DefaultParamsProviderPort } from '@modules/ad/core/application/ports/default-params-provider.port';
import { TimeConverterPort } from '@modules/ad/core/application/ports/time-converter.port';
import { TimezoneFinderPort } from '@modules/ad/core/application/ports/timezone-finder.port';
import { AdRepository } from '@modules/ad/infrastructure/ad.repository'; import { AdRepository } from '@modules/ad/infrastructure/ad.repository';
import { PrismaService } from '@modules/ad/infrastructure/prisma.service'; import { PrismaService } from '@modules/ad/infrastructure/prisma.service';
import { EventEmitter2, EventEmitterModule } from '@nestjs/event-emitter'; import { EventEmitter2, EventEmitterModule } from '@nestjs/event-emitter';
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
const mockDefaultParamsProvider: DefaultParamsProviderPort = {
getParams: () => {
return {
MON_MARGIN: 900,
TUE_MARGIN: 900,
WED_MARGIN: 900,
THU_MARGIN: 900,
FRI_MARGIN: 900,
SAT_MARGIN: 900,
SUN_MARGIN: 900,
DRIVER: false,
SEATS_PROPOSED: 3,
PASSENGER: true,
SEATS_REQUESTED: 1,
STRICT: false,
DEFAULT_TIMEZONE: 'Europe/Paris',
};
},
};
const mockTimezoneFinder: TimezoneFinderPort = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
timezones: jest.fn().mockImplementation((lon: number, lat: number) => {
if (lon < 60) return 'Europe/Paris';
return 'America/New_York';
}),
};
const mockTimeConverter: TimeConverterPort = {
localDateTimeToUtc: jest
.fn()
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.mockImplementation((datetime: Date, timezone: string, dst?: boolean) => {
return datetime;
}),
utcDatetimeToLocalTime: jest.fn(),
};
const mockMessagePublisher = { const mockMessagePublisher = {
publish: jest.fn().mockImplementation(), publish: jest.fn().mockImplementation(),
}; };
@ -62,22 +16,7 @@ describe('Ad repository', () => {
beforeAll(async () => { beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
imports: [EventEmitterModule.forRoot()], imports: [EventEmitterModule.forRoot()],
providers: [ providers: [PrismaService, AdMapper],
PrismaService,
AdMapper,
{
provide: PARAMS_PROVIDER,
useValue: mockDefaultParamsProvider,
},
{
provide: TIMEZONE_FINDER,
useValue: mockTimezoneFinder,
},
{
provide: TIME_CONVERTER,
useValue: mockTimeConverter,
},
],
}).compile(); }).compile();
prismaService = module.get<PrismaService>(PrismaService); prismaService = module.get<PrismaService>(PrismaService);

View File

@ -0,0 +1,266 @@
import {
PARAMS_PROVIDER,
TIMEZONE_FINDER,
TIME_CONVERTER,
} from '@modules/ad/ad.di-tokens';
import { Frequency } from '@modules/ad/core/application/ports/datetime-transformer.port';
import { DefaultParamsProviderPort } from '@modules/ad/core/application/ports/default-params-provider.port';
import { TimeConverterPort } from '@modules/ad/core/application/ports/time-converter.port';
import { TimezoneFinderPort } from '@modules/ad/core/application/ports/timezone-finder.port';
import { DateTimeTransformer } from '@modules/ad/infrastructure/datetime-transformer';
import { Test, TestingModule } from '@nestjs/testing';
const mockDefaultParamsProvider: DefaultParamsProviderPort = {
getParams: () => {
return {
DEPARTURE_TIME_MARGIN: 900,
DRIVER: false,
SEATS_PROPOSED: 3,
PASSENGER: true,
SEATS_REQUESTED: 1,
STRICT: false,
DEFAULT_TIMEZONE: 'Europe/Paris',
};
},
};
const mockTimezoneFinder: TimezoneFinderPort = {
timezones: jest.fn().mockImplementation(() => ['Europe/Paris']),
};
const mockTimeConverter: TimeConverterPort = {
localStringTimeToUtcStringTime: jest
.fn()
.mockImplementationOnce(() => '00:15'),
localStringDateTimeToUtcDate: jest
.fn()
.mockImplementationOnce(() => new Date('2023-07-30T06:15:00.000Z'))
.mockImplementationOnce(() => new Date('2023-07-20T08:15:00.000Z'))
.mockImplementationOnce(() => new Date('2023-07-19T23:15:00.000Z'))
.mockImplementationOnce(() => new Date('2023-07-19T23:15:00.000Z')),
utcUnixEpochDayFromTime: jest
.fn()
.mockImplementationOnce(() => 4)
.mockImplementationOnce(() => 3)
.mockImplementationOnce(() => 3)
.mockImplementationOnce(() => 5)
.mockImplementationOnce(() => 5),
};
describe('Datetime Transformer', () => {
let datetimeTransformer: DateTimeTransformer;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: PARAMS_PROVIDER,
useValue: mockDefaultParamsProvider,
},
{
provide: TIMEZONE_FINDER,
useValue: mockTimezoneFinder,
},
{
provide: TIME_CONVERTER,
useValue: mockTimeConverter,
},
DateTimeTransformer,
],
}).compile();
datetimeTransformer = module.get<DateTimeTransformer>(DateTimeTransformer);
});
it('should be defined', () => {
expect(datetimeTransformer).toBeDefined();
});
describe('fromDate', () => {
it('should return fromDate as is if frequency is recurrent', () => {
const transformedFromDate: string = datetimeTransformer.fromDate(
{
date: '2023-07-30',
time: '07:15',
coordinates: {
lon: 6.175,
lat: 48.685,
},
},
Frequency.RECURRENT,
);
expect(transformedFromDate).toBe('2023-07-30');
});
it('should return transformed fromDate if frequency is punctual and coordinates are those of Nancy', () => {
const transformedFromDate: string = datetimeTransformer.fromDate(
{
date: '2023-07-30',
time: '07:15',
coordinates: {
lon: 6.175,
lat: 48.685,
},
},
Frequency.PUNCTUAL,
);
expect(transformedFromDate).toBe('2023-07-30');
});
});
describe('toDate', () => {
it('should return toDate as is if frequency is recurrent', () => {
const transformedToDate: string = datetimeTransformer.toDate(
'2024-07-29',
{
date: '2023-07-20',
time: '10:15',
coordinates: {
lon: 6.175,
lat: 48.685,
},
},
Frequency.RECURRENT,
);
expect(transformedToDate).toBe('2024-07-29');
});
it('should return transformed fromDate if frequency is punctual', () => {
const transformedToDate: string = datetimeTransformer.toDate(
'2024-07-30',
{
date: '2023-07-20',
time: '10:15',
coordinates: {
lon: 6.175,
lat: 48.685,
},
},
Frequency.PUNCTUAL,
);
expect(transformedToDate).toBe('2023-07-20');
});
});
describe('day', () => {
it('should not change day if frequency is recurrent and converted UTC time is on the same day', () => {
const day: number = datetimeTransformer.day(
1,
{
date: '2023-07-24',
time: '01:15',
coordinates: {
lon: 6.175,
lat: 48.685,
},
},
Frequency.RECURRENT,
);
expect(day).toBe(1);
});
it('should change day if frequency is recurrent and converted UTC time is on the previous day', () => {
const day: number = datetimeTransformer.day(
1,
{
date: '2023-07-24',
time: '00:15',
coordinates: {
lon: 6.175,
lat: 48.685,
},
},
Frequency.RECURRENT,
);
expect(day).toBe(0);
});
it('should change day if frequency is recurrent and converted UTC time is on the previous day and given day is sunday', () => {
const day: number = datetimeTransformer.day(
0,
{
date: '2023-07-23',
time: '00:15',
coordinates: {
lon: 6.175,
lat: 48.685,
},
},
Frequency.RECURRENT,
);
expect(day).toBe(6);
});
it('should change day if frequency is recurrent and converted UTC time is on the next day', () => {
const day: number = datetimeTransformer.day(
1,
{
date: '2023-07-24',
time: '23:15',
coordinates: {
lon: 30.82,
lat: 49.37,
},
},
Frequency.RECURRENT,
);
expect(day).toBe(2);
});
it('should change day if frequency is recurrent and converted UTC time is on the next day and given day is saturday(6)', () => {
const day: number = datetimeTransformer.day(
6,
{
date: '2023-07-29',
time: '23:15',
coordinates: {
lon: 30.82,
lat: 49.37,
},
},
Frequency.RECURRENT,
);
expect(day).toBe(0);
});
it('should return utc fromDate day if frequency is punctual', () => {
const day: number = datetimeTransformer.day(
1,
{
date: '2023-07-20',
time: '00:15',
coordinates: {
lon: 6.175,
lat: 48.685,
},
},
Frequency.PUNCTUAL,
);
expect(day).toBe(3);
});
});
describe('time', () => {
it('should transform given time to utc time if frequency is recurrent', () => {
const time: string = datetimeTransformer.time(
{
date: '2023-07-24',
time: '01:15',
coordinates: {
lon: 6.175,
lat: 48.685,
},
},
Frequency.RECURRENT,
);
expect(time).toBe('00:15');
});
it('should return given time to utc time if frequency is punctual', () => {
const time: string = datetimeTransformer.time(
{
date: '2023-07-24',
time: '01:15',
coordinates: {
lon: 6.175,
lat: 48.685,
},
},
Frequency.PUNCTUAL,
);
expect(time).toBe('23:15');
});
});
});

View File

@ -7,7 +7,7 @@ describe('Time Converter', () => {
}); });
describe('localStringTimeToUtcStringTime', () => { describe('localStringTimeToUtcStringTime', () => {
it('should convert a paris time to utc time with dst', () => { it('should convert a paris time to utc time', () => {
const timeConverter: TimeConverter = new TimeConverter(); const timeConverter: TimeConverter = new TimeConverter();
const parisTime = '08:00'; const parisTime = '08:00';
const utcDatetime = timeConverter.localStringTimeToUtcStringTime( const utcDatetime = timeConverter.localStringTimeToUtcStringTime(
@ -54,33 +54,151 @@ describe('Time Converter', () => {
}); });
}); });
// describe('utcDatetimeToLocalTime', () => { describe('localStringDateTimeToUtcDate', () => {
// it('should convert an utc datetime isostring to a paris local time', () => { it('should convert a summer paris date and time to a utc date', () => {
// const timeConverter: TimeConverter = new TimeConverter(); const timeConverter: TimeConverter = new TimeConverter();
// const utcDatetimeIsostring = '2023-06-22T06:25:00.000Z'; const parisDate = '2023-06-22';
// const parisTime = timeConverter.utcDatetimeToLocalTime( const parisTime = '12:00';
// utcDatetimeIsostring, const utcDate = timeConverter.localStringDateTimeToUtcDate(
// 'Europe/Paris', parisDate,
// ); parisTime,
// expect(parisTime).toBe('08:25'); 'Europe/Paris',
// }); );
// it('should return undefined if isostring input is invalid', () => { expect(utcDate.toISOString()).toBe('2023-06-22T10:00:00.000Z');
// const timeConverter: TimeConverter = new TimeConverter(); });
// const utcDatetimeIsostring = 'not_an_isostring'; it('should convert a winter paris date and time to a utc date', () => {
// const parisTime = timeConverter.utcDatetimeToLocalTime( const timeConverter: TimeConverter = new TimeConverter();
// utcDatetimeIsostring, const parisDate = '2023-02-02';
// 'Europe/Paris', const parisTime = '12:00';
// ); const utcDate = timeConverter.localStringDateTimeToUtcDate(
// expect(parisTime).toBeUndefined(); parisDate,
// }); parisTime,
// it('should return undefined if timezone input is invalid', () => { 'Europe/Paris',
// const timeConverter: TimeConverter = new TimeConverter(); );
// const utcDatetimeIsostring = '2023-06-22T06:25:00.000Z'; expect(utcDate.toISOString()).toBe('2023-02-02T11:00:00.000Z');
// const parisTime = timeConverter.utcDatetimeToLocalTime( });
// utcDatetimeIsostring, it('should convert a summer paris date and time to a utc date without dst', () => {
// 'Foo/Bar', const timeConverter: TimeConverter = new TimeConverter();
// ); const parisDate = '2023-06-22';
// expect(parisTime).toBeUndefined(); const parisTime = '12:00';
// }); const utcDate = timeConverter.localStringDateTimeToUtcDate(
// }); parisDate,
parisTime,
'Europe/Paris',
false,
);
expect(utcDate.toISOString()).toBe('2023-06-22T11:00:00.000Z');
});
it('should convert a tonga date and time to a utc date', () => {
const timeConverter: TimeConverter = new TimeConverter();
const tongaDate = '2023-02-02';
const tongaTime = '12:00';
const utcDate = timeConverter.localStringDateTimeToUtcDate(
tongaDate,
tongaTime,
'Pacific/Tongatapu',
);
expect(utcDate.toISOString()).toBe('2023-02-01T23:00:00.000Z');
});
it('should convert a papeete date and time to a utc date', () => {
const timeConverter: TimeConverter = new TimeConverter();
const papeeteDate = '2023-02-02';
const papeeteTime = '15:00';
const utcDate = timeConverter.localStringDateTimeToUtcDate(
papeeteDate,
papeeteTime,
'Pacific/Tahiti',
);
expect(utcDate.toISOString()).toBe('2023-02-03T01:00:00.000Z');
});
it('should return undefined if time is invalid', () => {
const timeConverter: TimeConverter = new TimeConverter();
const parisDate = '2023-06-22';
const parisTime = '28:00';
const utcDate = timeConverter.localStringDateTimeToUtcDate(
parisDate,
parisTime,
'Europe/Paris',
);
expect(utcDate).toBeUndefined();
});
it('should return undefined if time is undefined', () => {
const timeConverter: TimeConverter = new TimeConverter();
const parisDate = '2023-06-22';
const parisTime = undefined;
const utcDate = timeConverter.localStringDateTimeToUtcDate(
parisDate,
parisTime,
'Europe/Paris',
);
expect(utcDate).toBeUndefined();
});
it('should return undefined if timezone is invalid', () => {
const timeConverter: TimeConverter = new TimeConverter();
const parisDate = '2023-06-22';
const parisTime = '12:00';
const utcDate = timeConverter.localStringDateTimeToUtcDate(
parisDate,
parisTime,
'Foo/Bar',
);
expect(utcDate).toBeUndefined();
});
it('should return undefined if timezone is undefined', () => {
const timeConverter: TimeConverter = new TimeConverter();
const parisDate = '2023-06-22';
const parisTime = '12:00';
const utcDate = timeConverter.localStringDateTimeToUtcDate(
parisDate,
parisTime,
undefined,
);
expect(utcDate).toBeUndefined();
});
});
describe('utcBaseDayFromTime', () => {
it('should get the utc day of paris at 12:00', () => {
const timeConverter: TimeConverter = new TimeConverter();
expect(
timeConverter.utcUnixEpochDayFromTime('12:00', 'Europe/Paris'),
).toBe(4);
});
it('should get the utc day of paris at 00:00', () => {
const timeConverter: TimeConverter = new TimeConverter();
expect(
timeConverter.utcUnixEpochDayFromTime('00:00', 'Europe/Paris'),
).toBe(3);
});
it('should get the utc day of papeete at 16:00', () => {
const timeConverter: TimeConverter = new TimeConverter();
expect(
timeConverter.utcUnixEpochDayFromTime('16:00', 'Pacific/Tahiti'),
).toBe(5);
});
it('should return undefined if time is invalid', () => {
const timeConverter: TimeConverter = new TimeConverter();
expect(
timeConverter.utcUnixEpochDayFromTime('28:00', 'Europe/Paris'),
).toBeUndefined();
});
it('should return undefined if time is undefined', () => {
const timeConverter: TimeConverter = new TimeConverter();
expect(
timeConverter.utcUnixEpochDayFromTime(undefined, 'Europe/Paris'),
).toBeUndefined();
});
it('should return undefined if timezone is invalid', () => {
const timeConverter: TimeConverter = new TimeConverter();
expect(
timeConverter.utcUnixEpochDayFromTime('12:00', 'Foo/Bar'),
).toBeUndefined();
});
it('should return undefined if timezone is undefined', () => {
const timeConverter: TimeConverter = new TimeConverter();
expect(
timeConverter.utcUnixEpochDayFromTime('12:00', undefined),
).toBeUndefined();
});
});
}); });

View File

@ -31,9 +31,11 @@ const punctualCreateAdRequest: CreateAdRequestDto = {
userId: '4eb6a6af-ecfd-41c3-9118-473a507014d4', userId: '4eb6a6af-ecfd-41c3-9118-473a507014d4',
fromDate: '2023-12-21', fromDate: '2023-12-21',
toDate: '2023-12-21', toDate: '2023-12-21',
schedule: { schedule: [
thu: '08:15', {
}, time: '08:15',
},
],
driver: false, driver: false,
passenger: true, passenger: true,
seatsRequested: 1, seatsRequested: 1,

View File

@ -1,30 +0,0 @@
import { ScheduleItemDto } from '@modules/ad/interface/grpc-controllers/dtos/schedule.dto';
import { IsSchedule } from '@modules/ad/interface/grpc-controllers/dtos/validators/decorators/is-schedule.decorator';
import { Validator } from 'class-validator';
describe('schedule decorator', () => {
class MyClass {
@IsSchedule()
schedule: ScheduleItemDto;
}
it('should return a property decorator has a function', () => {
const isSchedule = IsSchedule();
expect(typeof isSchedule).toBe('function');
});
it('should validate a valid schedule', async () => {
const myClassInstance = new MyClass();
myClassInstance.schedule = {
mon: '07:15',
};
const validator = new Validator();
const validation = await validator.validate(myClassInstance);
expect(validation.length).toBe(0);
});
it('should not validate a invalid schedule', async () => {
const myClassInstance = new MyClass();
myClassInstance.schedule = {};
const validator = new Validator();
const validation = await validator.validate(myClassInstance);
expect(validation.length).toBe(1);
});
});