refactor adapters
This commit is contained in:
		
							parent
							
								
									c530bc55f5
								
							
						
					
					
						commit
						b55e122bef
					
				| 
						 | 
				
			
			@ -97,7 +97,7 @@
 | 
			
		|||
      "main.ts"
 | 
			
		||||
    ],
 | 
			
		||||
    "rootDir": "src",
 | 
			
		||||
    "testRegex": ".converter.*\\.spec\\.ts$",
 | 
			
		||||
    "testRegex": ".*\\.spec\\.ts$",
 | 
			
		||||
    "transform": {
 | 
			
		||||
      "^.+\\.(t|j)s$": "ts-jest"
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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');
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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({
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<AggregateID> {
 | 
			
		||||
    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;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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',
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -6,5 +6,5 @@ export interface TimeConverterPort {
 | 
			
		|||
    timezone: string,
 | 
			
		||||
    dst?: boolean,
 | 
			
		||||
  ): Date;
 | 
			
		||||
  utcDatetimeToLocalTime(isoString: string, timezone: string): string;
 | 
			
		||||
  utcUnixEpochDayFromTime(time: string, timezone: string): number;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,3 @@
 | 
			
		|||
export interface TimezoneFinderPort {
 | 
			
		||||
  timezones(lon: number, lat: number, defaultTimezone?: string): string[];
 | 
			
		||||
  offset(timezone: string): number;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<ScheduleItemProps> {
 | 
			
		||||
  get day(): number {
 | 
			
		||||
  get day(): number | undefined {
 | 
			
		||||
    return this.props.day;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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',
 | 
			
		||||
      });
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -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"}}',
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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>(PrismaService);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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');
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -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();
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
		Loading…
	
		Reference in New Issue