transform to output

This commit is contained in:
sbriat 2023-07-27 12:31:54 +02:00
parent 218595553d
commit df92245357
21 changed files with 533 additions and 91 deletions

View File

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

View File

@ -1,6 +1,6 @@
import { Mapper } from '@mobicoop/ddd-library'; import { Mapper } from '@mobicoop/ddd-library';
import { AdResponseDto } from './interface/dtos/ad.response.dto'; import { AdResponseDto } from './interface/dtos/ad.response.dto';
import { Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { AdEntity } from './core/domain/ad.entity'; import { AdEntity } from './core/domain/ad.entity';
import { import {
AdWriteModel, AdWriteModel,
@ -12,6 +12,8 @@ import { Frequency } from './core/domain/ad.types';
import { WaypointProps } from './core/domain/value-objects/waypoint.value-object'; import { WaypointProps } from './core/domain/value-objects/waypoint.value-object';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { ScheduleItemProps } from './core/domain/value-objects/schedule-item.value-object'; import { ScheduleItemProps } from './core/domain/value-objects/schedule-item.value-object';
import { OUTPUT_DATETIME_TRANSFORMER } from './ad.di-tokens';
import { DateTimeTransformerPort } from './core/application/ports/datetime-transformer.port';
/** /**
* Mapper constructs objects that are used in different layers: * Mapper constructs objects that are used in different layers:
@ -24,6 +26,11 @@ import { ScheduleItemProps } from './core/domain/value-objects/schedule-item.val
export class AdMapper export class AdMapper
implements Mapper<AdEntity, AdReadModel, AdWriteModel, AdResponseDto> implements Mapper<AdEntity, AdReadModel, AdWriteModel, AdResponseDto>
{ {
constructor(
@Inject(OUTPUT_DATETIME_TRANSFORMER)
private readonly outputDatetimeTransformer: DateTimeTransformerPort,
) {}
toPersistence = (entity: AdEntity): AdWriteModel => { toPersistence = (entity: AdEntity): AdWriteModel => {
const copy = entity.getProps(); const copy = entity.getProps();
const now = new Date(); const now = new Date();
@ -129,12 +136,42 @@ export class AdMapper
response.driver = props.driver; response.driver = props.driver;
response.passenger = props.passenger; response.passenger = props.passenger;
response.frequency = props.frequency; response.frequency = props.frequency;
response.fromDate = props.fromDate; response.fromDate = this.outputDatetimeTransformer.fromDate(
response.toDate = props.toDate; {
date: props.fromDate,
time: props.schedule[0].time,
coordinates: props.waypoints[0].address.coordinates,
},
props.frequency,
);
response.toDate = this.outputDatetimeTransformer.toDate(
props.toDate,
{
date: props.fromDate,
time: props.schedule[0].time,
coordinates: props.waypoints[0].address.coordinates,
},
props.frequency,
);
response.schedule = props.schedule.map( response.schedule = props.schedule.map(
(scheduleItem: ScheduleItemProps) => ({ (scheduleItem: ScheduleItemProps) => ({
day: scheduleItem.day, day: this.outputDatetimeTransformer.day(
scheduleItem.day,
{
date: props.fromDate,
time: scheduleItem.time, time: scheduleItem.time,
coordinates: props.waypoints[0].address.coordinates,
},
props.frequency,
),
time: this.outputDatetimeTransformer.time(
{
date: props.fromDate,
time: scheduleItem.time,
coordinates: props.waypoints[0].address.coordinates,
},
props.frequency,
),
margin: scheduleItem.margin, margin: scheduleItem.margin,
}), }),
); );

View File

@ -4,7 +4,8 @@ import { CqrsModule } from '@nestjs/cqrs';
import { import {
AD_MESSAGE_PUBLISHER, AD_MESSAGE_PUBLISHER,
AD_REPOSITORY, AD_REPOSITORY,
DATETIME_TRANSFORMER, INPUT_DATETIME_TRANSFORMER,
OUTPUT_DATETIME_TRANSFORMER,
PARAMS_PROVIDER, PARAMS_PROVIDER,
TIMEZONE_FINDER, TIMEZONE_FINDER,
TIME_CONVERTER, TIME_CONVERTER,
@ -20,7 +21,8 @@ import { FindAdByIdQueryHandler } from './core/application/queries/find-ad-by-id
import { PublishMessageWhenAdIsCreatedDomainEventHandler } from './core/application/event-handlers/publish-message-when-ad-is-created.domain-event-handler'; import { PublishMessageWhenAdIsCreatedDomainEventHandler } from './core/application/event-handlers/publish-message-when-ad-is-created.domain-event-handler';
import { PrismaService } from './infrastructure/prisma.service'; import { PrismaService } from './infrastructure/prisma.service';
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module'; import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
import { DateTimeTransformer } from './infrastructure/datetime-transformer'; import { InputDateTimeTransformer } from './infrastructure/input-datetime-transformer';
import { OutputDateTimeTransformer } from './infrastructure/output-datetime-transformer';
const grpcControllers = [CreateAdGrpcController, FindAdByIdGrpcController]; const grpcControllers = [CreateAdGrpcController, FindAdByIdGrpcController];
@ -63,8 +65,12 @@ const adapters: Provider[] = [
useClass: TimeConverter, useClass: TimeConverter,
}, },
{ {
provide: DATETIME_TRANSFORMER, provide: INPUT_DATETIME_TRANSFORMER,
useClass: DateTimeTransformer, useClass: InputDateTimeTransformer,
},
{
provide: OUTPUT_DATETIME_TRANSFORMER,
useClass: OutputDateTimeTransformer,
}, },
]; ];

View File

@ -3,7 +3,7 @@ import { CreateAdCommand } from './create-ad.command';
import { Inject } from '@nestjs/common'; import { Inject } from '@nestjs/common';
import { import {
AD_REPOSITORY, AD_REPOSITORY,
DATETIME_TRANSFORMER, INPUT_DATETIME_TRANSFORMER,
PARAMS_PROVIDER, PARAMS_PROVIDER,
} from '@modules/ad/ad.di-tokens'; } from '@modules/ad/ad.di-tokens';
import { AdEntity } from '@modules/ad/core/domain/ad.entity'; import { AdEntity } from '@modules/ad/core/domain/ad.entity';
@ -25,7 +25,7 @@ export class CreateAdService implements ICommandHandler {
private readonly repository: AdRepositoryPort, private readonly repository: AdRepositoryPort,
@Inject(PARAMS_PROVIDER) @Inject(PARAMS_PROVIDER)
private readonly defaultParamsProvider: DefaultParamsProviderPort, private readonly defaultParamsProvider: DefaultParamsProviderPort,
@Inject(DATETIME_TRANSFORMER) @Inject(INPUT_DATETIME_TRANSFORMER)
private readonly datetimeTransformer: DateTimeTransformerPort, private readonly datetimeTransformer: DateTimeTransformerPort,
) { ) {
this._defaultParams = defaultParamsProvider.getParams(); this._defaultParams = defaultParamsProvider.getParams();

View File

@ -1,10 +1,18 @@
export interface TimeConverterPort { export interface TimeConverterPort {
localStringTimeToUtcStringTime(time: string, timezone: string): string; localStringTimeToUtcStringTime(time: string, timezone: string): string;
utcStringTimeToLocalStringTime(time: string, timezone: string): string;
localStringDateTimeToUtcDate( localStringDateTimeToUtcDate(
date: string, date: string,
time: string, time: string,
timezone: string, timezone: string,
dst?: boolean, dst?: boolean,
): Date; ): Date;
utcStringDateTimeToLocalIsoString(
date: string,
time: string,
timezone: string,
dst?: boolean,
): string;
utcUnixEpochDayFromTime(time: string, timezone: string): number; utcUnixEpochDayFromTime(time: string, timezone: string): number;
localUnixEpochDayFromTime(time: string, timezone: string): number;
} }

View File

@ -12,6 +12,9 @@ export class FindAdByIdQueryHandler implements IQueryHandler {
private readonly repository: AdRepositoryPort, private readonly repository: AdRepositoryPort,
) {} ) {}
async execute(query: FindAdByIdQuery): Promise<AdEntity> { async execute(query: FindAdByIdQuery): Promise<AdEntity> {
return await this.repository.findOneById(query.id, { waypoints: true }); return await this.repository.findOneById(query.id, {
waypoints: true,
schedule: true,
});
} }
} }

View File

@ -14,7 +14,7 @@ import { TimezoneFinderPort } from '../core/application/ports/timezone-finder.po
import { DefaultParamsProviderPort } from '../core/application/ports/default-params-provider.port'; import { DefaultParamsProviderPort } from '../core/application/ports/default-params-provider.port';
@Injectable() @Injectable()
export class DateTimeTransformer implements DateTimeTransformerPort { export class InputDateTimeTransformer implements DateTimeTransformerPort {
private readonly _defaultTimezone: string; private readonly _defaultTimezone: string;
constructor( constructor(
@Inject(PARAMS_PROVIDER) @Inject(PARAMS_PROVIDER)

View File

@ -0,0 +1,116 @@
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 { TIMEZONE_FINDER, TIME_CONVERTER } from '../ad.di-tokens';
import { TimezoneFinderPort } from '../core/application/ports/timezone-finder.port';
@Injectable()
export class OutputDateTimeTransformer implements DateTimeTransformerPort {
constructor(
@Inject(TIMEZONE_FINDER)
private readonly timezoneFinder: TimezoneFinderPort,
@Inject(TIME_CONVERTER) private readonly timeConverter: TimeConverterPort,
) {}
/**
* Compute the fromDate : if an ad is punctual, the departure date
* is converted from UTC to the local date with the time and timezone
*/
fromDate = (geoFromDate: GeoDateTime, frequency: Frequency): string => {
if (frequency === Frequency.RECURRENT) return geoFromDate.date;
return this.timeConverter
.utcStringDateTimeToLocalIsoString(
geoFromDate.date,
geoFromDate.time,
this.timezoneFinder.timezones(
geoFromDate.coordinates.lon,
geoFromDate.coordinates.lat,
)[0],
)
.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 from utc to local time
*/
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,
)[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.utcStringTimeToLocalStringTime(
geoFromDate.time,
this.timezoneFinder.timezones(
geoFromDate.coordinates.lon,
geoFromDate.coordinates.lat,
)[0],
);
return this.timeConverter
.utcStringDateTimeToLocalIsoString(
geoFromDate.date,
geoFromDate.time,
this.timezoneFinder.timezones(
geoFromDate.coordinates.lon,
geoFromDate.coordinates.lat,
)[0],
)
.split('T')[1]
.split(':', 2)
.join(':');
};
/**
* Get the day for a schedule item for a recurrent ad
* The day may change when transforming from utc to local timezone
*/
private recurrentDay = (
day: number,
time: string,
timezone: string,
): number => {
const unixEpochDay = 4; // 1970-01-01 is a thursday !
const localBaseDay = this.timeConverter.localUnixEpochDayFromTime(
time,
timezone,
);
if (unixEpochDay == localBaseDay) return day;
if (unixEpochDay > localBaseDay) return day > 0 ? day - 1 : 6;
return day < 6 ? day + 1 : 0;
};
}

View File

@ -17,6 +17,17 @@ export class TimeConverter implements TimeConverterPort {
} }
}; };
utcStringTimeToLocalStringTime = (time: string, timezone: string): string => {
try {
if (!time || !timezone) throw new Error();
return new DateTime(`${this.UNIX_EPOCH}T${time}`, TimeZone.zone('UTC'))
.convert(TimeZone.zone(timezone))
.format('HH:mm');
} catch (e) {
return undefined;
}
};
localStringDateTimeToUtcDate = ( localStringDateTimeToUtcDate = (
date: string, date: string,
time: string, time: string,
@ -24,7 +35,7 @@ export class TimeConverter implements TimeConverterPort {
dst = true, dst = true,
): Date => { ): Date => {
try { try {
if (!time || !timezone) throw new Error(); if (!date || !time || !timezone) throw new Error();
return new Date( return new Date(
new DateTime( new DateTime(
`${date}T${time}`, `${date}T${time}`,
@ -36,6 +47,22 @@ export class TimeConverter implements TimeConverterPort {
} }
}; };
utcStringDateTimeToLocalIsoString = (
date: string,
time: string,
timezone: string,
dst?: boolean,
): string => {
try {
if (!date || !time || !timezone) throw new Error();
return new DateTime(`${date}T${time}`, TimeZone.zone('UTC'))
.convert(TimeZone.zone(timezone, dst))
.toIsoString();
} catch (e) {
return undefined;
}
};
utcUnixEpochDayFromTime = (time: string, timezone: string): number => { utcUnixEpochDayFromTime = (time: string, timezone: string): number => {
try { try {
if (!time || !timezone) throw new Error(); if (!time || !timezone) throw new Error();
@ -52,4 +79,18 @@ export class TimeConverter implements TimeConverterPort {
return undefined; return undefined;
} }
}; };
localUnixEpochDayFromTime = (time: string, timezone: string): number => {
try {
if (!time || !timezone) throw new Error();
return new Date(
new DateTime(`${this.UNIX_EPOCH}T${time}`, TimeZone.zone('UTC'))
.convert(TimeZone.zone(timezone))
.toIsoString()
.split('T')[0],
).getDay();
} catch (e) {
return undefined;
}
};
} }

View File

@ -37,8 +37,8 @@ message ScheduleItem {
message Waypoint { message Waypoint {
int32 position = 1; int32 position = 1;
float lon = 2; double lon = 2;
float lat = 3; double lat = 3;
string name = 4; string name = 4;
string houseNumber = 5; string houseNumber = 5;
string street = 6; string street = 6;

View File

@ -1,17 +1,9 @@
import { Transform } from 'class-transformer';
import { IsLatitude, IsLongitude } from 'class-validator'; import { IsLatitude, IsLongitude } from 'class-validator';
import { toPrecision } from './transformers/to-precision';
export class CoordinatesDto { export class CoordinatesDto {
@Transform(({ value }) => toPrecision(value, 6), {
toClassOnly: true,
})
@IsLongitude() @IsLongitude()
lon: number; lon: number;
@Transform(({ value }) => toPrecision(value, 6), {
toClassOnly: true,
})
@IsLatitude() @IsLatitude()
lat: number; lat: number;
} }

View File

@ -1,7 +0,0 @@
import { Frequency } from '@modules/ad/core/domain/ad.types';
export const intToFrequency = (frequencyAsInt: number): Frequency => {
if (frequencyAsInt == 1) return Frequency.PUNCTUAL;
if (frequencyAsInt == 2) return Frequency.RECURRENT;
throw new Error('Unknown frequency value');
};

View File

@ -1,4 +0,0 @@
export const toPrecision = (input: number, precision: number): number => {
const multiplier = 10 ** precision;
return Math.round((input + Number.EPSILON) * multiplier) / multiplier;
};

View File

@ -23,7 +23,7 @@ export class FindAdByIdGrpcController {
private readonly queryBus: QueryBus, private readonly queryBus: QueryBus,
) {} ) {}
@GrpcMethod('AdsService', 'FindOneById') @GrpcMethod('AdService', 'FindOneById')
async findOnebyId(data: FindAdByIdRequestDto): Promise<AdResponseDto> { async findOnebyId(data: FindAdByIdRequestDto): Promise<AdResponseDto> {
try { try {
const ad: AdEntity = await this.queryBus.execute( const ad: AdEntity = await this.queryBus.execute(

View File

@ -1,4 +1,6 @@
import { OUTPUT_DATETIME_TRANSFORMER } from '@modules/ad/ad.di-tokens';
import { AdMapper } from '@modules/ad/ad.mapper'; import { AdMapper } from '@modules/ad/ad.mapper';
import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.port';
import { AdEntity } from '@modules/ad/core/domain/ad.entity'; import { AdEntity } from '@modules/ad/core/domain/ad.entity';
import { Frequency } from '@modules/ad/core/domain/ad.types'; import { Frequency } from '@modules/ad/core/domain/ad.types';
import { import {
@ -111,12 +113,25 @@ const adReadModel: AdReadModel = {
updatedAt: now, updatedAt: now,
}; };
const mockOutputDatetimeTransformer: DateTimeTransformerPort = {
fromDate: jest.fn(),
toDate: jest.fn(),
day: jest.fn(),
time: jest.fn(),
};
describe('Ad Mapper', () => { describe('Ad Mapper', () => {
let adMapper: AdMapper; let adMapper: AdMapper;
beforeAll(async () => { beforeAll(async () => {
const module = await Test.createTestingModule({ const module = await Test.createTestingModule({
providers: [AdMapper], providers: [
AdMapper,
{
provide: OUTPUT_DATETIME_TRANSFORMER,
useValue: mockOutputDatetimeTransformer,
},
],
}).compile(); }).compile();
adMapper = module.get<AdMapper>(AdMapper); adMapper = module.get<AdMapper>(AdMapper);
}); });

View File

@ -1,7 +1,7 @@
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { import {
AD_REPOSITORY, AD_REPOSITORY,
DATETIME_TRANSFORMER, INPUT_DATETIME_TRANSFORMER,
PARAMS_PROVIDER, PARAMS_PROVIDER,
} from '@modules/ad/ad.di-tokens'; } from '@modules/ad/ad.di-tokens';
import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto'; import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto';
@ -76,7 +76,7 @@ const mockDefaultParamsProvider: DefaultParamsProviderPort = {
}, },
}; };
const mockDateTimeTransformer: DateTimeTransformerPort = { const mockInputDateTimeTransformer: DateTimeTransformerPort = {
fromDate: jest.fn(), fromDate: jest.fn(),
toDate: jest.fn(), toDate: jest.fn(),
day: jest.fn(), day: jest.fn(),
@ -98,8 +98,8 @@ describe('create-ad.service', () => {
useValue: mockDefaultParamsProvider, useValue: mockDefaultParamsProvider,
}, },
{ {
provide: DATETIME_TRANSFORMER, provide: INPUT_DATETIME_TRANSFORMER,
useValue: mockDateTimeTransformer, useValue: mockInputDateTimeTransformer,
}, },
CreateAdService, CreateAdService,
], ],

View File

@ -1,4 +1,6 @@
import { OUTPUT_DATETIME_TRANSFORMER } from '@modules/ad/ad.di-tokens';
import { AdMapper } from '@modules/ad/ad.mapper'; import { AdMapper } from '@modules/ad/ad.mapper';
import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.port';
import { AdRepository } from '@modules/ad/infrastructure/ad.repository'; import { AdRepository } from '@modules/ad/infrastructure/ad.repository';
import { PrismaService } from '@modules/ad/infrastructure/prisma.service'; import { PrismaService } from '@modules/ad/infrastructure/prisma.service';
import { EventEmitter2, EventEmitterModule } from '@nestjs/event-emitter'; import { EventEmitter2, EventEmitterModule } from '@nestjs/event-emitter';
@ -8,6 +10,13 @@ const mockMessagePublisher = {
publish: jest.fn().mockImplementation(), publish: jest.fn().mockImplementation(),
}; };
const mockOutputDatetimeTransformer: DateTimeTransformerPort = {
fromDate: jest.fn(),
toDate: jest.fn(),
day: jest.fn(),
time: jest.fn(),
};
describe('Ad repository', () => { describe('Ad repository', () => {
let prismaService: PrismaService; let prismaService: PrismaService;
let adMapper: AdMapper; let adMapper: AdMapper;
@ -16,7 +25,14 @@ describe('Ad repository', () => {
beforeAll(async () => { beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
imports: [EventEmitterModule.forRoot()], imports: [EventEmitterModule.forRoot()],
providers: [PrismaService, AdMapper], providers: [
PrismaService,
AdMapper,
{
provide: OUTPUT_DATETIME_TRANSFORMER,
useValue: mockOutputDatetimeTransformer,
},
],
}).compile(); }).compile();
prismaService = module.get<PrismaService>(PrismaService); prismaService = module.get<PrismaService>(PrismaService);

View File

@ -7,7 +7,7 @@ import { Frequency } from '@modules/ad/core/application/ports/datetime-transform
import { DefaultParamsProviderPort } from '@modules/ad/core/application/ports/default-params-provider.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 { TimeConverterPort } from '@modules/ad/core/application/ports/time-converter.port';
import { TimezoneFinderPort } from '@modules/ad/core/application/ports/timezone-finder.port'; import { TimezoneFinderPort } from '@modules/ad/core/application/ports/timezone-finder.port';
import { DateTimeTransformer } from '@modules/ad/infrastructure/datetime-transformer'; import { InputDateTimeTransformer } from '@modules/ad/infrastructure/input-datetime-transformer';
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
const mockDefaultParamsProvider: DefaultParamsProviderPort = { const mockDefaultParamsProvider: DefaultParamsProviderPort = {
@ -32,12 +32,14 @@ const mockTimeConverter: TimeConverterPort = {
localStringTimeToUtcStringTime: jest localStringTimeToUtcStringTime: jest
.fn() .fn()
.mockImplementationOnce(() => '00:15'), .mockImplementationOnce(() => '00:15'),
utcStringTimeToLocalStringTime: jest.fn(),
localStringDateTimeToUtcDate: jest localStringDateTimeToUtcDate: jest
.fn() .fn()
.mockImplementationOnce(() => new Date('2023-07-30T06:15:00.000Z')) .mockImplementationOnce(() => new Date('2023-07-30T06:15:00.000Z'))
.mockImplementationOnce(() => new Date('2023-07-20T08: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'))
.mockImplementationOnce(() => new Date('2023-07-19T23:15:00.000Z')), .mockImplementationOnce(() => new Date('2023-07-19T23:15:00.000Z')),
utcStringDateTimeToLocalIsoString: jest.fn(),
utcUnixEpochDayFromTime: jest utcUnixEpochDayFromTime: jest
.fn() .fn()
.mockImplementationOnce(() => 4) .mockImplementationOnce(() => 4)
@ -45,10 +47,11 @@ const mockTimeConverter: TimeConverterPort = {
.mockImplementationOnce(() => 3) .mockImplementationOnce(() => 3)
.mockImplementationOnce(() => 5) .mockImplementationOnce(() => 5)
.mockImplementationOnce(() => 5), .mockImplementationOnce(() => 5),
localUnixEpochDayFromTime: jest.fn(),
}; };
describe('Datetime Transformer', () => { describe('Input Datetime Transformer', () => {
let datetimeTransformer: DateTimeTransformer; let inputDatetimeTransformer: InputDateTimeTransformer;
beforeAll(async () => { beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
@ -65,20 +68,22 @@ describe('Datetime Transformer', () => {
provide: TIME_CONVERTER, provide: TIME_CONVERTER,
useValue: mockTimeConverter, useValue: mockTimeConverter,
}, },
DateTimeTransformer, InputDateTimeTransformer,
], ],
}).compile(); }).compile();
datetimeTransformer = module.get<DateTimeTransformer>(DateTimeTransformer); inputDatetimeTransformer = module.get<InputDateTimeTransformer>(
InputDateTimeTransformer,
);
}); });
it('should be defined', () => { it('should be defined', () => {
expect(datetimeTransformer).toBeDefined(); expect(inputDatetimeTransformer).toBeDefined();
}); });
describe('fromDate', () => { describe('fromDate', () => {
it('should return fromDate as is if frequency is recurrent', () => { it('should return fromDate as is if frequency is recurrent', () => {
const transformedFromDate: string = datetimeTransformer.fromDate( const transformedFromDate: string = inputDatetimeTransformer.fromDate(
{ {
date: '2023-07-30', date: '2023-07-30',
time: '07:15', time: '07:15',
@ -92,7 +97,7 @@ describe('Datetime Transformer', () => {
expect(transformedFromDate).toBe('2023-07-30'); expect(transformedFromDate).toBe('2023-07-30');
}); });
it('should return transformed fromDate if frequency is punctual and coordinates are those of Nancy', () => { it('should return transformed fromDate if frequency is punctual and coordinates are those of Nancy', () => {
const transformedFromDate: string = datetimeTransformer.fromDate( const transformedFromDate: string = inputDatetimeTransformer.fromDate(
{ {
date: '2023-07-30', date: '2023-07-30',
time: '07:15', time: '07:15',
@ -109,7 +114,7 @@ describe('Datetime Transformer', () => {
describe('toDate', () => { describe('toDate', () => {
it('should return toDate as is if frequency is recurrent', () => { it('should return toDate as is if frequency is recurrent', () => {
const transformedToDate: string = datetimeTransformer.toDate( const transformedToDate: string = inputDatetimeTransformer.toDate(
'2024-07-29', '2024-07-29',
{ {
date: '2023-07-20', date: '2023-07-20',
@ -124,7 +129,7 @@ describe('Datetime Transformer', () => {
expect(transformedToDate).toBe('2024-07-29'); expect(transformedToDate).toBe('2024-07-29');
}); });
it('should return transformed fromDate if frequency is punctual', () => { it('should return transformed fromDate if frequency is punctual', () => {
const transformedToDate: string = datetimeTransformer.toDate( const transformedToDate: string = inputDatetimeTransformer.toDate(
'2024-07-30', '2024-07-30',
{ {
date: '2023-07-20', date: '2023-07-20',
@ -142,7 +147,7 @@ describe('Datetime Transformer', () => {
describe('day', () => { describe('day', () => {
it('should not change day if frequency is recurrent and converted UTC time is on the same day', () => { it('should not change day if frequency is recurrent and converted UTC time is on the same day', () => {
const day: number = datetimeTransformer.day( const day: number = inputDatetimeTransformer.day(
1, 1,
{ {
date: '2023-07-24', date: '2023-07-24',
@ -157,7 +162,7 @@ describe('Datetime Transformer', () => {
expect(day).toBe(1); expect(day).toBe(1);
}); });
it('should change day if frequency is recurrent and converted UTC time is on the previous day', () => { it('should change day if frequency is recurrent and converted UTC time is on the previous day', () => {
const day: number = datetimeTransformer.day( const day: number = inputDatetimeTransformer.day(
1, 1,
{ {
date: '2023-07-24', date: '2023-07-24',
@ -172,7 +177,7 @@ describe('Datetime Transformer', () => {
expect(day).toBe(0); 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', () => { 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( const day: number = inputDatetimeTransformer.day(
0, 0,
{ {
date: '2023-07-23', date: '2023-07-23',
@ -187,7 +192,7 @@ describe('Datetime Transformer', () => {
expect(day).toBe(6); expect(day).toBe(6);
}); });
it('should change day if frequency is recurrent and converted UTC time is on the next day', () => { it('should change day if frequency is recurrent and converted UTC time is on the next day', () => {
const day: number = datetimeTransformer.day( const day: number = inputDatetimeTransformer.day(
1, 1,
{ {
date: '2023-07-24', date: '2023-07-24',
@ -202,7 +207,7 @@ describe('Datetime Transformer', () => {
expect(day).toBe(2); 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)', () => { 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( const day: number = inputDatetimeTransformer.day(
6, 6,
{ {
date: '2023-07-29', date: '2023-07-29',
@ -217,7 +222,7 @@ describe('Datetime Transformer', () => {
expect(day).toBe(0); expect(day).toBe(0);
}); });
it('should return utc fromDate day if frequency is punctual', () => { it('should return utc fromDate day if frequency is punctual', () => {
const day: number = datetimeTransformer.day( const day: number = inputDatetimeTransformer.day(
1, 1,
{ {
date: '2023-07-20', date: '2023-07-20',
@ -235,7 +240,7 @@ describe('Datetime Transformer', () => {
describe('time', () => { describe('time', () => {
it('should transform given time to utc time if frequency is recurrent', () => { it('should transform given time to utc time if frequency is recurrent', () => {
const time: string = datetimeTransformer.time( const time: string = inputDatetimeTransformer.time(
{ {
date: '2023-07-24', date: '2023-07-24',
time: '01:15', time: '01:15',
@ -249,7 +254,7 @@ describe('Datetime Transformer', () => {
expect(time).toBe('00:15'); expect(time).toBe('00:15');
}); });
it('should return given time to utc time if frequency is punctual', () => { it('should return given time to utc time if frequency is punctual', () => {
const time: string = datetimeTransformer.time( const time: string = inputDatetimeTransformer.time(
{ {
date: '2023-07-24', date: '2023-07-24',
time: '01:15', time: '01:15',

View File

@ -54,6 +54,54 @@ describe('Time Converter', () => {
}); });
}); });
describe('utcStringTimeToLocalStringTime', () => {
it('should convert a utc time to a paris time', () => {
const timeConverter: TimeConverter = new TimeConverter();
const utcTime = '07:00';
const parisTime = timeConverter.utcStringTimeToLocalStringTime(
utcTime,
'Europe/Paris',
);
expect(parisTime).toBe('08:00');
});
it('should return undefined if time is invalid', () => {
const timeConverter: TimeConverter = new TimeConverter();
const utcTime = '27:00';
const parisTime = timeConverter.utcStringTimeToLocalStringTime(
utcTime,
'Europe/Paris',
);
expect(parisTime).toBeUndefined();
});
it('should return undefined if time is undefined', () => {
const timeConverter: TimeConverter = new TimeConverter();
const utcTime = undefined;
const parisTime = timeConverter.utcStringTimeToLocalStringTime(
utcTime,
'Europe/Paris',
);
expect(parisTime).toBeUndefined();
});
it('should return undefined if timezone is invalid', () => {
const timeConverter: TimeConverter = new TimeConverter();
const utcTime = '07:00';
const parisTime = timeConverter.utcStringTimeToLocalStringTime(
utcTime,
'Foo/Bar',
);
expect(parisTime).toBeUndefined();
});
it('should return undefined if timezone is undefined', () => {
const timeConverter: TimeConverter = new TimeConverter();
const utcTime = '07:00';
const parisTime = timeConverter.utcStringTimeToLocalStringTime(
utcTime,
undefined,
);
expect(parisTime).toBeUndefined();
});
});
describe('localStringDateTimeToUtcDate', () => { describe('localStringDateTimeToUtcDate', () => {
it('should convert a summer paris date and time to a utc date', () => { it('should convert a summer paris date and time to a utc date', () => {
const timeConverter: TimeConverter = new TimeConverter(); const timeConverter: TimeConverter = new TimeConverter();
@ -111,6 +159,28 @@ describe('Time Converter', () => {
); );
expect(utcDate.toISOString()).toBe('2023-02-03T01:00:00.000Z'); expect(utcDate.toISOString()).toBe('2023-02-03T01:00:00.000Z');
}); });
it('should return undefined if date is invalid', () => {
const timeConverter: TimeConverter = new TimeConverter();
const parisDate = '2023-06-32';
const parisTime = '08:00';
const utcDate = timeConverter.localStringDateTimeToUtcDate(
parisDate,
parisTime,
'Europe/Paris',
);
expect(utcDate).toBeUndefined();
});
it('should return undefined if date is undefined', () => {
const timeConverter: TimeConverter = new TimeConverter();
const parisDate = undefined;
const parisTime = '08:00';
const utcDate = timeConverter.localStringDateTimeToUtcDate(
parisDate,
parisTime,
'Europe/Paris',
);
expect(utcDate).toBeUndefined();
});
it('should return undefined if time is invalid', () => { it('should return undefined if time is invalid', () => {
const timeConverter: TimeConverter = new TimeConverter(); const timeConverter: TimeConverter = new TimeConverter();
const parisDate = '2023-06-22'; const parisDate = '2023-06-22';
@ -157,7 +227,132 @@ describe('Time Converter', () => {
}); });
}); });
describe('utcBaseDayFromTime', () => { describe('utcStringDateTimeToLocalIsoString', () => {
it('should convert a utc string date and time to a summer paris date isostring', () => {
const timeConverter: TimeConverter = new TimeConverter();
const utcDate = '2023-06-22';
const utcTime = '10:00';
const localIsoString = timeConverter.utcStringDateTimeToLocalIsoString(
utcDate,
utcTime,
'Europe/Paris',
);
expect(localIsoString).toBe('2023-06-22T12:00:00.000+02:00');
});
it('should convert a utc string date and time to a winter paris date isostring', () => {
const timeConverter: TimeConverter = new TimeConverter();
const utcDate = '2023-02-02';
const utcTime = '10:00';
const localIsoString = timeConverter.utcStringDateTimeToLocalIsoString(
utcDate,
utcTime,
'Europe/Paris',
);
expect(localIsoString).toBe('2023-02-02T11:00:00.000+01:00');
});
it('should convert a utc string date and time to a summer paris date isostring without dst', () => {
const timeConverter: TimeConverter = new TimeConverter();
const utcDate = '2023-06-22';
const utcTime = '10:00';
const localIsoString = timeConverter.utcStringDateTimeToLocalIsoString(
utcDate,
utcTime,
'Europe/Paris',
false,
);
expect(localIsoString).toBe('2023-06-22T11:00:00.000+01:00');
});
it('should convert a utc date to a tonga date isostring', () => {
const timeConverter: TimeConverter = new TimeConverter();
const utcDate = '2023-02-01';
const utcTime = '23:00';
const localIsoString = timeConverter.utcStringDateTimeToLocalIsoString(
utcDate,
utcTime,
'Pacific/Tongatapu',
);
expect(localIsoString).toBe('2023-02-02T12:00:00.000+13:00');
});
it('should convert a utc date to a papeete date isostring', () => {
const timeConverter: TimeConverter = new TimeConverter();
const utcDate = '2023-02-03';
const utcTime = '01:00';
const localIsoString = timeConverter.utcStringDateTimeToLocalIsoString(
utcDate,
utcTime,
'Pacific/Tahiti',
);
expect(localIsoString).toBe('2023-02-02T15:00:00.000-10:00');
});
it('should return undefined if date is invalid', () => {
const timeConverter: TimeConverter = new TimeConverter();
const utcDate = '2023-06-32';
const utcTime = '07:00';
const parisTime = timeConverter.utcStringDateTimeToLocalIsoString(
utcDate,
utcTime,
'Europe/Paris',
);
expect(parisTime).toBeUndefined();
});
it('should return undefined if date is undefined', () => {
const timeConverter: TimeConverter = new TimeConverter();
const utcDate = undefined;
const utcTime = '07:00';
const parisTime = timeConverter.utcStringDateTimeToLocalIsoString(
utcDate,
utcTime,
'Europe/Paris',
);
expect(parisTime).toBeUndefined();
});
it('should return undefined if time is invalid', () => {
const timeConverter: TimeConverter = new TimeConverter();
const utcDate = '2023-06-22';
const utcTime = '27:00';
const parisTime = timeConverter.utcStringDateTimeToLocalIsoString(
utcDate,
utcTime,
'Europe/Paris',
);
expect(parisTime).toBeUndefined();
});
it('should return undefined if time is undefined', () => {
const timeConverter: TimeConverter = new TimeConverter();
const utcDate = '2023-06-22';
const utcTime = undefined;
const parisTime = timeConverter.utcStringDateTimeToLocalIsoString(
utcDate,
utcTime,
'Europe/Paris',
);
expect(parisTime).toBeUndefined();
});
it('should return undefined if timezone is invalid', () => {
const timeConverter: TimeConverter = new TimeConverter();
const utcDate = '2023-06-22';
const utcTime = '07:00';
const parisTime = timeConverter.utcStringDateTimeToLocalIsoString(
utcDate,
utcTime,
'Foo/Bar',
);
expect(parisTime).toBeUndefined();
});
it('should return undefined if timezone is undefined', () => {
const timeConverter: TimeConverter = new TimeConverter();
const utcDate = '2023-06-22';
const utcTime = '07:00';
const parisTime = timeConverter.utcStringDateTimeToLocalIsoString(
utcDate,
utcTime,
undefined,
);
expect(parisTime).toBeUndefined();
});
});
describe('utcUnixEpochDayFromTime', () => {
it('should get the utc day of paris at 12:00', () => { it('should get the utc day of paris at 12:00', () => {
const timeConverter: TimeConverter = new TimeConverter(); const timeConverter: TimeConverter = new TimeConverter();
expect( expect(
@ -201,4 +396,49 @@ describe('Time Converter', () => {
).toBeUndefined(); ).toBeUndefined();
}); });
}); });
describe('localUnixEpochDayFromTime', () => {
it('should get the day of paris at 12:00 utc', () => {
const timeConverter: TimeConverter = new TimeConverter();
expect(
timeConverter.localUnixEpochDayFromTime('12:00', 'Europe/Paris'),
).toBe(4);
});
it('should get the day of paris at 23:00 utc', () => {
const timeConverter: TimeConverter = new TimeConverter();
expect(
timeConverter.localUnixEpochDayFromTime('23:00', 'Europe/Paris'),
).toBe(5);
});
it('should get the day of papeete at 05:00 utc', () => {
const timeConverter: TimeConverter = new TimeConverter();
expect(
timeConverter.localUnixEpochDayFromTime('05:00', 'Pacific/Tahiti'),
).toBe(3);
});
it('should return undefined if time is invalid', () => {
const timeConverter: TimeConverter = new TimeConverter();
expect(
timeConverter.localUnixEpochDayFromTime('28:00', 'Europe/Paris'),
).toBeUndefined();
});
it('should return undefined if time is undefined', () => {
const timeConverter: TimeConverter = new TimeConverter();
expect(
timeConverter.localUnixEpochDayFromTime(undefined, 'Europe/Paris'),
).toBeUndefined();
});
it('should return undefined if timezone is invalid', () => {
const timeConverter: TimeConverter = new TimeConverter();
expect(
timeConverter.localUnixEpochDayFromTime('12:00', 'Foo/Bar'),
).toBeUndefined();
});
it('should return undefined if timezone is undefined', () => {
const timeConverter: TimeConverter = new TimeConverter();
expect(
timeConverter.localUnixEpochDayFromTime('12:00', undefined),
).toBeUndefined();
});
});
}); });

View File

@ -1,15 +0,0 @@
import { Frequency } from '@modules/ad/core/domain/ad.types';
import { intToFrequency } from '@modules/ad/interface/grpc-controllers/dtos/transformers/int-to-frequency';
describe('frequency mapping', () => {
it('should return punctual if frequency is 1', () => {
expect(intToFrequency(1)).toBe(Frequency.PUNCTUAL);
});
it('should return recurrent if frequency is 2', () => {
expect(intToFrequency(2)).toBe(Frequency.RECURRENT);
});
it('should throw an error if frequency is unknown', () => {
expect(() => intToFrequency(0)).toThrow();
expect(() => intToFrequency(3)).toThrow();
});
});

View File

@ -1,14 +0,0 @@
import { toPrecision } from '@modules/ad/interface/grpc-controllers/dtos/transformers/to-precision';
describe('precision handler', () => {
it('should return a 6 digits float number for a 10 digits float input number and 6 as precision', () => {
const precised = toPrecision(1.1234567891, 6);
const stringPrecised = precised.toString().split('.')[1];
expect(stringPrecised.length).toBe(6);
});
it('should return a 2 digits float number for a 2 digits float input number and 4 as precision', () => {
const precised = toPrecision(1.12, 4);
const stringPrecised = precised.toString().split('.')[1];
expect(stringPrecised.length).toBe(2);
});
});