diff --git a/package.json b/package.json index e9d6b59..b6b4a8d 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ "main.ts" ], "rootDir": "src", - "testRegex": ".converter.*\\.spec\\.ts$", + "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" }, diff --git a/src/modules/ad/ad.di-tokens.ts b/src/modules/ad/ad.di-tokens.ts index fac28b5..6ad4746 100644 --- a/src/modules/ad/ad.di-tokens.ts +++ b/src/modules/ad/ad.di-tokens.ts @@ -2,4 +2,5 @@ export const AD_MESSAGE_PUBLISHER = Symbol('AD_MESSAGE_PUBLISHER'); export const PARAMS_PROVIDER = Symbol('PARAMS_PROVIDER'); export const TIMEZONE_FINDER = Symbol('TIMEZONE_FINDER'); export const TIME_CONVERTER = Symbol('TIME_CONVERTER'); +export const DATETIME_TRANSFORMER = Symbol('DATETIME_TRANSFORMER'); export const AD_REPOSITORY = Symbol('AD_REPOSITORY'); diff --git a/src/modules/ad/ad.module.ts b/src/modules/ad/ad.module.ts index b72c3ec..6ae54a6 100644 --- a/src/modules/ad/ad.module.ts +++ b/src/modules/ad/ad.module.ts @@ -4,6 +4,7 @@ import { CqrsModule } from '@nestjs/cqrs'; import { AD_MESSAGE_PUBLISHER, AD_REPOSITORY, + DATETIME_TRANSFORMER, PARAMS_PROVIDER, TIMEZONE_FINDER, 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 { PrismaService } from './infrastructure/prisma.service'; import { MessageBrokerPublisher } from '@mobicoop/message-broker-module'; +import { DateTimeTransformer } from './infrastructure/datetime-transformer'; const grpcControllers = [CreateAdGrpcController, FindAdByIdGrpcController]; @@ -60,6 +62,10 @@ const adapters: Provider[] = [ provide: TIME_CONVERTER, useClass: TimeConverter, }, + { + provide: DATETIME_TRANSFORMER, + useClass: DateTimeTransformer, + }, ]; @Module({ diff --git a/src/modules/ad/core/application/commands/create-ad/create-ad.service.ts b/src/modules/ad/core/application/commands/create-ad/create-ad.service.ts index 26c02dd..6d15807 100644 --- a/src/modules/ad/core/application/commands/create-ad/create-ad.service.ts +++ b/src/modules/ad/core/application/commands/create-ad/create-ad.service.ts @@ -3,9 +3,8 @@ import { CreateAdCommand } from './create-ad.command'; import { Inject } from '@nestjs/common'; import { AD_REPOSITORY, + DATETIME_TRANSFORMER, PARAMS_PROVIDER, - TIMEZONE_FINDER, - TIME_CONVERTER, } from '@modules/ad/ad.di-tokens'; import { AdEntity } from '@modules/ad/core/domain/ad.entity'; 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 { AggregateID, ConflictException } from '@mobicoop/ddd-library'; import { ScheduleItem } from '../../types/schedule-item'; -import { TimeConverterPort } from '../../ports/time-converter.port'; -import { TimezoneFinderPort } from '../../ports/timezone-finder.port'; -import { Frequency } from '@modules/ad/core/domain/ad.types'; +import { DateTimeTransformerPort } from '../../ports/datetime-transformer.port'; @CommandHandler(CreateAdCommand) export class CreateAdService implements ICommandHandler { @@ -28,52 +25,65 @@ export class CreateAdService implements ICommandHandler { private readonly repository: AdRepositoryPort, @Inject(PARAMS_PROVIDER) private readonly defaultParamsProvider: DefaultParamsProviderPort, - @Inject(TIMEZONE_FINDER) - private readonly timezoneFinder: TimezoneFinderPort, - @Inject(TIME_CONVERTER) - private readonly timeConverter: TimeConverterPort, + @Inject(DATETIME_TRANSFORMER) + private readonly datetimeTransformer: DateTimeTransformerPort, ) { this._defaultParams = defaultParamsProvider.getParams(); } async execute(command: CreateAdCommand): Promise { - const timezone = this.timezoneFinder.timezones( - command.waypoints[0].lon, - command.waypoints[0].lat, - this._defaultParams.DEFAULT_TIMEZONE, - )[0]; const ad = AdEntity.create( { userId: command.userId, driver: command.driver, passenger: command.passenger, frequency: command.frequency, - fromDate: this.getFromDate( - command.fromDate, + fromDate: this.datetimeTransformer.fromDate( + { + date: command.fromDate, + time: command.schedule[0].time, + coordinates: { + lon: command.waypoints[0].lon, + lat: command.waypoints[0].lat, + }, + }, command.frequency, - command.schedule[0].time, - timezone, ), - toDate: this.getToDate( - command.fromDate, + toDate: this.datetimeTransformer.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.schedule[0].time, - timezone, ), schedule: command.schedule.map((scheduleItem: ScheduleItem) => ({ - day: this.getDay( + day: this.datetimeTransformer.day( scheduleItem.day, - command.fromDate, + { + date: command.fromDate, + time: scheduleItem.time, + coordinates: { + lon: command.waypoints[0].lon, + lat: command.waypoints[0].lat, + }, + }, command.frequency, - scheduleItem.time, - timezone, ), - time: this.getTime( - command.fromDate, + time: this.datetimeTransformer.time( + { + date: command.fromDate, + time: scheduleItem.time, + coordinates: { + lon: command.waypoints[0].lon, + lat: command.waypoints[0].lat, + }, + }, command.frequency, - scheduleItem.time, - timezone, ), margin: scheduleItem.margin, })), @@ -116,71 +126,4 @@ export class CreateAdService implements ICommandHandler { 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; - }; } diff --git a/src/modules/ad/core/application/ports/datetime-transformer.port.ts b/src/modules/ad/core/application/ports/datetime-transformer.port.ts new file mode 100644 index 0000000..4b651c0 --- /dev/null +++ b/src/modules/ad/core/application/ports/datetime-transformer.port.ts @@ -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', +} diff --git a/src/modules/ad/core/application/ports/time-converter.port.ts b/src/modules/ad/core/application/ports/time-converter.port.ts index b1eef3b..e981717 100644 --- a/src/modules/ad/core/application/ports/time-converter.port.ts +++ b/src/modules/ad/core/application/ports/time-converter.port.ts @@ -6,5 +6,5 @@ export interface TimeConverterPort { timezone: string, dst?: boolean, ): Date; - utcDatetimeToLocalTime(isoString: string, timezone: string): string; + utcUnixEpochDayFromTime(time: string, timezone: string): number; } diff --git a/src/modules/ad/core/application/ports/timezone-finder.port.ts b/src/modules/ad/core/application/ports/timezone-finder.port.ts index 2991a6a..72ba115 100644 --- a/src/modules/ad/core/application/ports/timezone-finder.port.ts +++ b/src/modules/ad/core/application/ports/timezone-finder.port.ts @@ -1,4 +1,3 @@ export interface TimezoneFinderPort { timezones(lon: number, lat: number, defaultTimezone?: string): string[]; - offset(timezone: string): number; } diff --git a/src/modules/ad/core/domain/events/ad-created.domain-events.ts b/src/modules/ad/core/domain/events/ad-created.domain-events.ts index 7c7bcd8..8f9a225 100644 --- a/src/modules/ad/core/domain/events/ad-created.domain-events.ts +++ b/src/modules/ad/core/domain/events/ad-created.domain-events.ts @@ -7,7 +7,7 @@ export class AdCreatedDomainEvent extends DomainEvent { readonly frequency: string; readonly fromDate: string; readonly toDate: string; - readonly schedule: ScheduleDay[]; + readonly schedule: ScheduleItem[]; readonly seatsProposed: number; readonly seatsRequested: number; readonly strict: boolean; @@ -29,7 +29,7 @@ export class AdCreatedDomainEvent extends DomainEvent { } } -export class ScheduleDay { +export class ScheduleItem { day: number; time: string; margin: number; diff --git a/src/modules/ad/core/domain/value-objects/schedule-item.value-object.ts b/src/modules/ad/core/domain/value-objects/schedule-item.value-object.ts index efb7a66..8303eeb 100644 --- a/src/modules/ad/core/domain/value-objects/schedule-item.value-object.ts +++ b/src/modules/ad/core/domain/value-objects/schedule-item.value-object.ts @@ -6,13 +6,13 @@ import { ValueObject } from '@mobicoop/ddd-library'; * */ export interface ScheduleItemProps { - day: number; + day?: number; time: string; margin?: number; } export class ScheduleItem extends ValueObject { - get day(): number { + get day(): number | undefined { return this.props.day; } diff --git a/src/modules/ad/infrastructure/datetime-transformer.ts b/src/modules/ad/infrastructure/datetime-transformer.ts new file mode 100644 index 0000000..f8aa497 --- /dev/null +++ b/src/modules/ad/infrastructure/datetime-transformer.ts @@ -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; + }; +} diff --git a/src/modules/ad/infrastructure/time-converter.ts b/src/modules/ad/infrastructure/time-converter.ts index db7e545..1a54b40 100644 --- a/src/modules/ad/infrastructure/time-converter.ts +++ b/src/modules/ad/infrastructure/time-converter.ts @@ -4,12 +4,12 @@ import { TimeConverterPort } from '../core/application/ports/time-converter.port @Injectable() 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 => { try { 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')) .format('HH:mm'); } catch (e) { @@ -25,21 +25,29 @@ export class TimeConverter implements TimeConverterPort { ): Date => { try { if (!time || !timezone) throw new Error(); - return new DateTime(`${date}T${time}`, TimeZone.zone(timezone, dst)) - .convert(TimeZone.zone('UTC')) - .toDate(); + return new Date( + new DateTime( + `${date}T${time}`, + TimeZone.zone(timezone, dst), + ).toIsoString(), + ); } catch (e) { return undefined; } }; - utcDatetimeToLocalTime = (isoString: string, timezone: string): string => { + utcUnixEpochDayFromTime = (time: string, timezone: string): number => { try { - return new DateTime(isoString) - .convert(TimeZone.zone(timezone)) - .toString() - .split('T')[1] - .substring(0, 5); + if (!time || !timezone) throw new Error(); + return new Date( + new DateTime( + `${this.UNIX_EPOCH}T${time}`, + TimeZone.zone(timezone, false), + ) + .convert(TimeZone.zone('UTC')) + .toIsoString() + .split('T')[0], + ).getDay(); } catch (e) { return undefined; } diff --git a/src/modules/ad/infrastructure/timezone-finder.ts b/src/modules/ad/infrastructure/timezone-finder.ts index a996b52..feb0b5a 100644 --- a/src/modules/ad/infrastructure/timezone-finder.ts +++ b/src/modules/ad/infrastructure/timezone-finder.ts @@ -1,7 +1,6 @@ import { Injectable } from '@nestjs/common'; import { find } from 'geo-tz'; import { TimezoneFinderPort } from '../core/application/ports/timezone-finder.port'; -import { zone } from 'timezonecomplete'; @Injectable() export class TimezoneFinder implements TimezoneFinderPort { @@ -14,7 +13,4 @@ export class TimezoneFinder implements TimezoneFinderPort { if (defaultTimezone && foundTimezones.length == 0) return [defaultTimezone]; return foundTimezones; }; - - offset = (timezone: string): number => - zone(timezone).offsetForUtc(1970, 1, 1, 0, 0, 0); } diff --git a/src/modules/ad/tests/integration/ad.repository.spec.ts b/src/modules/ad/tests/integration/ad.repository.spec.ts index 64d3f08..31c9334 100644 --- a/src/modules/ad/tests/integration/ad.repository.spec.ts +++ b/src/modules/ad/tests/integration/ad.repository.spec.ts @@ -1,10 +1,4 @@ -import { - AD_MESSAGE_PUBLISHER, - AD_REPOSITORY, - PARAMS_PROVIDER, - TIMEZONE_FINDER, - TIME_CONVERTER, -} from '@modules/ad/ad.di-tokens'; +import { AD_MESSAGE_PUBLISHER, AD_REPOSITORY } from '@modules/ad/ad.di-tokens'; import { AdMapper } from '@modules/ad/ad.mapper'; import { AdEntity } from '@modules/ad/core/domain/ad.entity'; import { @@ -13,10 +7,7 @@ import { Frequency, } from '@modules/ad/core/domain/ad.types'; 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 { TimeConverter } from '@modules/ad/infrastructure/time-converter'; -import { TimezoneFinder } from '@modules/ad/infrastructure/timezone-finder'; import { ConfigModule } from '@nestjs/config'; import { EventEmitterModule } from '@nestjs/event-emitter'; import { Test } from '@nestjs/testing'; @@ -133,22 +124,6 @@ describe('Ad Repository', () => { providers: [ PrismaService, 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, useValue: mockMessagePublisher, @@ -193,12 +168,11 @@ describe('Ad Repository', () => { frequency: Frequency.PUNCTUAL, fromDate: '2023-02-01', toDate: '2023-02-01', - schedule: { - wed: '12:05', - }, - marginDurations: { - wed: 900, - }, + schedule: [ + { + time: '12:05', + }, + ], seatsProposed: 3, seatsRequested: 1, strict: false, @@ -233,15 +207,7 @@ describe('Ad Repository', () => { const defaultAdProps: DefaultAdProps = { driver: false, passenger: true, - marginDurations: { - mon: 900, - tue: 900, - wed: 900, - thu: 900, - fri: 900, - sat: 900, - sun: 900, - }, + marginDuration: 900, seatsProposed: 3, seatsRequested: 1, strict: false, diff --git a/src/modules/ad/tests/unit/core/create-ad.service.spec.ts b/src/modules/ad/tests/unit/core/create-ad.service.spec.ts index 3e42dcd..0f99875 100644 --- a/src/modules/ad/tests/unit/core/create-ad.service.spec.ts +++ b/src/modules/ad/tests/unit/core/create-ad.service.spec.ts @@ -1,5 +1,9 @@ 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 { CreateAdRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/create-ad.request.dto'; 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 { CreateAdCommand } from '@modules/ad/core/application/commands/create-ad/create-ad.command'; import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors'; +import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.port'; const originWaypoint: WaypointDto = { position: 0, @@ -33,9 +38,11 @@ const punctualCreateAdRequest: CreateAdRequestDto = { userId: '4eb6a6af-ecfd-41c3-9118-473a507014d4', fromDate: '2023-12-21', toDate: '2023-12-21', - schedule: { - thu: '08:15', - }, + schedule: [ + { + time: '08:15', + }, + ], driver: true, passenger: true, seatsRequested: 1, @@ -58,13 +65,7 @@ const mockAdRepository = { 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, + DEPARTURE_TIME_MARGIN: 900, DRIVER: false, SEATS_PROPOSED: 3, 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', () => { let createAdService: CreateAdService; @@ -89,6 +97,10 @@ describe('create-ad.service', () => { provide: PARAMS_PROVIDER, useValue: mockDefaultParamsProvider, }, + { + provide: DATETIME_TRANSFORMER, + useValue: mockDateTimeTransformer, + }, CreateAdService, ], }).compile(); @@ -102,7 +114,7 @@ describe('create-ad.service', () => { describe('execution', () => { 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({ id: '047a6ecf-23d4-4d3e-877c-3225d560a8da', }); diff --git a/src/modules/ad/tests/unit/core/find-ad-by-id.query-handler.spec.ts b/src/modules/ad/tests/unit/core/find-ad-by-id.query-handler.spec.ts index 7fdf39a..70eb372 100644 --- a/src/modules/ad/tests/unit/core/find-ad-by-id.query-handler.spec.ts +++ b/src/modules/ad/tests/unit/core/find-ad-by-id.query-handler.spec.ts @@ -7,7 +7,6 @@ import { } 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 { 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 { 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 = { userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36', seatsProposed: 3, @@ -56,31 +46,24 @@ const baseCreateAdProps = { const punctualCreateAdProps = { fromDate: '2023-06-22', toDate: '2023-06-22', - schedule: { - wed: '08:30', - }, + schedule: [ + { + time: '08:30', + }, + ], frequency: Frequency.PUNCTUAL, }; const punctualPassengerCreateAdProps: CreateAdProps = { ...baseCreateAdProps, ...punctualCreateAdProps, - marginDurations: marginDurationsProps, driver: false, passenger: true, }; const defaultAdProps: DefaultAdProps = { + marginDuration: 900, driver: false, passenger: true, - marginDurations: { - mon: 900, - tue: 900, - wed: 900, - thu: 900, - fri: 900, - sat: 900, - sun: 900, - }, seatsProposed: 3, seatsRequested: 1, strict: false, diff --git a/src/modules/ad/tests/unit/core/margin-durations.value-object.spec.ts b/src/modules/ad/tests/unit/core/margin-durations.value-object.spec.ts deleted file mode 100644 index 41e30ef..0000000 --- a/src/modules/ad/tests/unit/core/margin-durations.value-object.spec.ts +++ /dev/null @@ -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); - }); -}); diff --git a/src/modules/ad/tests/unit/core/publish-message-when-ad-is-created.domain-event-handler.spec.ts b/src/modules/ad/tests/unit/core/publish-message-when-ad-is-created.domain-event-handler.spec.ts index 79f667e..144c1b0 100644 --- a/src/modules/ad/tests/unit/core/publish-message-when-ad-is-created.domain-event-handler.spec.ts +++ b/src/modules/ad/tests/unit/core/publish-message-when-ad-is-created.domain-event-handler.spec.ts @@ -39,20 +39,13 @@ describe('Publish message when ad is created domain event handler', () => { frequency: Frequency.PUNCTUAL, fromDate: '2023-06-28', toDate: '2023-06-28', - monTime: undefined, - tueTime: undefined, - wedTime: '07:15', - thuTime: undefined, - friTime: undefined, - satTime: undefined, - sunTime: undefined, - monMarginDuration: 900, - tueMarginDuration: 900, - wedMarginDuration: 900, - thuMarginDuration: 900, - friMarginDuration: 900, - satMarginDuration: 900, - sunMarginDuration: 900, + schedule: [ + { + day: 3, + time: '07:15', + margin: 900, + }, + ], seatsProposed: 3, seatsRequested: 1, strict: false, @@ -88,7 +81,7 @@ describe('Publish message when ad is created domain event handler', () => { expect(mockMessagePublisher.publish).toHaveBeenCalledTimes(1); expect(mockMessagePublisher.publish).toHaveBeenCalledWith( '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"}}', ); }); }); diff --git a/src/modules/ad/tests/unit/infrastructure/ad.repository.spec.ts b/src/modules/ad/tests/unit/infrastructure/ad.repository.spec.ts index d082f8e..43ed4ac 100644 --- a/src/modules/ad/tests/unit/infrastructure/ad.repository.spec.ts +++ b/src/modules/ad/tests/unit/infrastructure/ad.repository.spec.ts @@ -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 { 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 { PrismaService } from '@modules/ad/infrastructure/prisma.service'; import { EventEmitter2, EventEmitterModule } from '@nestjs/event-emitter'; 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 = { publish: jest.fn().mockImplementation(), }; @@ -62,22 +16,7 @@ describe('Ad repository', () => { beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ imports: [EventEmitterModule.forRoot()], - providers: [ - PrismaService, - AdMapper, - { - provide: PARAMS_PROVIDER, - useValue: mockDefaultParamsProvider, - }, - { - provide: TIMEZONE_FINDER, - useValue: mockTimezoneFinder, - }, - { - provide: TIME_CONVERTER, - useValue: mockTimeConverter, - }, - ], + providers: [PrismaService, AdMapper], }).compile(); prismaService = module.get(PrismaService); diff --git a/src/modules/ad/tests/unit/infrastructure/datetime-transformer.spec.ts b/src/modules/ad/tests/unit/infrastructure/datetime-transformer.spec.ts new file mode 100644 index 0000000..c2192f4 --- /dev/null +++ b/src/modules/ad/tests/unit/infrastructure/datetime-transformer.spec.ts @@ -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); + }); + + 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'); + }); + }); +}); diff --git a/src/modules/ad/tests/unit/infrastructure/time-converter.spec.ts b/src/modules/ad/tests/unit/infrastructure/time-converter.spec.ts index 348653c..eaf6361 100644 --- a/src/modules/ad/tests/unit/infrastructure/time-converter.spec.ts +++ b/src/modules/ad/tests/unit/infrastructure/time-converter.spec.ts @@ -7,7 +7,7 @@ describe('Time Converter', () => { }); 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 parisTime = '08:00'; const utcDatetime = timeConverter.localStringTimeToUtcStringTime( @@ -54,33 +54,151 @@ describe('Time Converter', () => { }); }); - // describe('utcDatetimeToLocalTime', () => { - // it('should convert an utc datetime isostring to a paris local time', () => { - // const timeConverter: TimeConverter = new TimeConverter(); - // const utcDatetimeIsostring = '2023-06-22T06:25:00.000Z'; - // const parisTime = timeConverter.utcDatetimeToLocalTime( - // utcDatetimeIsostring, - // 'Europe/Paris', - // ); - // expect(parisTime).toBe('08:25'); - // }); - // it('should return undefined if isostring input is invalid', () => { - // const timeConverter: TimeConverter = new TimeConverter(); - // const utcDatetimeIsostring = 'not_an_isostring'; - // const parisTime = timeConverter.utcDatetimeToLocalTime( - // utcDatetimeIsostring, - // 'Europe/Paris', - // ); - // expect(parisTime).toBeUndefined(); - // }); - // it('should return undefined if timezone input is invalid', () => { - // const timeConverter: TimeConverter = new TimeConverter(); - // const utcDatetimeIsostring = '2023-06-22T06:25:00.000Z'; - // const parisTime = timeConverter.utcDatetimeToLocalTime( - // utcDatetimeIsostring, - // 'Foo/Bar', - // ); - // expect(parisTime).toBeUndefined(); - // }); - // }); + describe('localStringDateTimeToUtcDate', () => { + it('should convert a summer paris date and time to a utc date', () => { + const timeConverter: TimeConverter = new TimeConverter(); + const parisDate = '2023-06-22'; + const parisTime = '12:00'; + const utcDate = timeConverter.localStringDateTimeToUtcDate( + parisDate, + parisTime, + 'Europe/Paris', + ); + expect(utcDate.toISOString()).toBe('2023-06-22T10:00:00.000Z'); + }); + it('should convert a winter paris date and time to a utc date', () => { + const timeConverter: TimeConverter = new TimeConverter(); + const parisDate = '2023-02-02'; + const parisTime = '12:00'; + const utcDate = timeConverter.localStringDateTimeToUtcDate( + parisDate, + parisTime, + 'Europe/Paris', + ); + expect(utcDate.toISOString()).toBe('2023-02-02T11:00:00.000Z'); + }); + it('should convert a summer paris date and time to a utc date without dst', () => { + const timeConverter: TimeConverter = new TimeConverter(); + const parisDate = '2023-06-22'; + 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(); + }); + }); }); diff --git a/src/modules/ad/tests/unit/interface/create-ad.grpc.controller.spec.ts b/src/modules/ad/tests/unit/interface/create-ad.grpc.controller.spec.ts index 3313aeb..4ebaa59 100644 --- a/src/modules/ad/tests/unit/interface/create-ad.grpc.controller.spec.ts +++ b/src/modules/ad/tests/unit/interface/create-ad.grpc.controller.spec.ts @@ -31,9 +31,11 @@ const punctualCreateAdRequest: CreateAdRequestDto = { userId: '4eb6a6af-ecfd-41c3-9118-473a507014d4', fromDate: '2023-12-21', toDate: '2023-12-21', - schedule: { - thu: '08:15', - }, + schedule: [ + { + time: '08:15', + }, + ], driver: false, passenger: true, seatsRequested: 1, diff --git a/src/modules/ad/tests/unit/interface/is-schedule.decorator.spec.ts b/src/modules/ad/tests/unit/interface/is-schedule.decorator.spec.ts deleted file mode 100644 index ca7154b..0000000 --- a/src/modules/ad/tests/unit/interface/is-schedule.decorator.spec.ts +++ /dev/null @@ -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); - }); -});