Merge branch 'createAD' into 'main'

feat : ad creation to the service

See merge request v3/service/ad!3
This commit is contained in:
Sylvain Briat
2023-05-31 08:11:07 +00:00
47 changed files with 2559 additions and 137 deletions

View File

@@ -8,6 +8,8 @@ import { AdProfile } from './mappers/ad.profile';
import { AdsRepository } from './adapters/secondaries/ads.repository';
import { Messager } from './adapters/secondaries/messager';
import { FindAdByUuidUseCase } from './domain/usecases/find-ad-by-uuid.usecase';
import { CreateAdUseCase } from './domain/usecases/create-ad.usecase';
import { DefaultParamsProvider } from './adapters/secondaries/default-params.provider';
@Module({
imports: [
@@ -29,6 +31,16 @@ import { FindAdByUuidUseCase } from './domain/usecases/find-ad-by-uuid.usecase';
}),
],
controllers: [AdController],
providers: [AdProfile, AdsRepository, Messager, FindAdByUuidUseCase],
providers: [
AdProfile,
AdsRepository,
Messager,
FindAdByUuidUseCase,
CreateAdUseCase,
{
provide: 'ParamsProvider',
useClass: DefaultParamsProvider,
},
],
})
export class AdModule {}

View File

@@ -1,13 +1,16 @@
import { Mapper } from '@automapper/core';
import { InjectMapper } from '@automapper/nestjs';
import { Controller, UsePipes } from '@nestjs/common';
import { QueryBus } from '@nestjs/cqrs';
import { CommandBus, QueryBus } from '@nestjs/cqrs';
import { GrpcMethod, RpcException } from '@nestjs/microservices';
import { RpcValidationPipe } from '../../../../utils/pipes/rpc.validation-pipe';
import { FindAdByUuidRequest } from '../../domain/dtos/find-ad-by-uuid.request';
import { AdPresenter } from './ad.presenter';
import { FindAdByUuidQuery } from '../../queries/find-ad-by-uuid.query';
import { Ad } from '../../domain/entities/ad';
import { CreateAdRequest } from '../../domain/dtos/create-ad.request';
import { CreateAdCommand } from '../../commands/create-ad.command';
import { DatabaseException } from '../../../database/exceptions/database.exception';
@UsePipes(
new RpcValidationPipe({
@@ -18,6 +21,7 @@ import { Ad } from '../../domain/entities/ad';
@Controller()
export class AdController {
constructor(
private readonly _commandBus: CommandBus,
private readonly queryBus: QueryBus,
@InjectMapper() private readonly _mapper: Mapper,
) {}
@@ -25,7 +29,6 @@ export class AdController {
@GrpcMethod('AdsService', 'FindOneByUuid')
async findOnebyUuid(data: FindAdByUuidRequest): Promise<AdPresenter> {
try {
console.log('ici');
const ad = await this.queryBus.execute(new FindAdByUuidQuery(data));
return this._mapper.map(ad, Ad, AdPresenter);
} catch (e) {
@@ -35,4 +38,22 @@ export class AdController {
});
}
}
@GrpcMethod('AdsService', 'Create')
async createAd(data: CreateAdRequest): Promise<AdPresenter> {
try {
const ad = await this._commandBus.execute(new CreateAdCommand(data));
return this._mapper.map(ad, Ad, AdPresenter);
} catch (e) {
if (e instanceof DatabaseException) {
if (e.message.includes('Already exists')) {
throw new RpcException({
code: 6,
message: 'Ad already exists',
});
}
}
throw new RpcException({});
}
}
}

View File

@@ -5,7 +5,7 @@ package ad;
service AdsService {
rpc FindOneByUuid(AdByUuid) returns (Ad);
rpc FindAll(AdFilter) returns (Ads);
rpc Create(Ad) returns (Ad);
rpc Create(Ad) returns (AdByUuid);
rpc Update(Ad) returns (Ad);
rpc Delete(AdByUuid) returns (Empty);
}
@@ -19,25 +19,26 @@ message Ad {
string userUuid = 2;
bool driver = 3;
bool passenger = 4;
int32 frequency = 5;
string fromDate = 6;
string toDate = 7;
Schedule schedule = 8;
MarginDurations marginDurations = 9;
int32 seatsPassenger = 10;
int32 seatsDriver = 11;
bool strict = 12;
Addresses addresses = 13;
Frequency frequency = 5;
optional string departure = 6;
string fromDate = 7;
string toDate = 8;
Schedule schedule = 9;
MarginDurations marginDurations = 10;
int32 seatsPassenger = 11;
int32 seatsDriver = 12;
bool strict = 13;
repeated Address addresses = 14;
}
message Schedule {
string mon = 1;
string tue = 2;
string wed = 3;
string thu = 4;
string fri = 5;
string sat = 6;
string sun = 7;
optional string mon = 1;
optional string tue = 2;
optional string wed = 3;
optional string thu = 4;
optional string fri = 5;
optional string sat = 6;
optional string sun = 7;
}
message MarginDurations {
@@ -50,32 +51,28 @@ message MarginDurations {
int32 sun = 7;
}
message Addresses {
repeated Address address = 1;
}
message Address {
float lon = 1;
float lat = 2;
string houseNumber = 3;
string street = 4;
string locality = 5;
string postalCode = 6;
string country = 7;
AddressType type = 8;
}
string uuid = 1;
int32 position = 2;
float lon = 3;
float lat = 4;
optional string name = 5;
optional string houseNumber = 6;
optional string street = 7;
optional string locality = 8;
optional string postalCode = 9;
string country = 10;
}
enum AddressType {
HOUSE_NUMBER = 1;
STREET_ADDRESS = 2;
LOCALITY = 3;
VENUE = 4;
OTHER = 5;
enum Frequency {
PUNCTUAL = 1;
RECURRENT = 2;
}
message AdFilter {
optional int32 page = 1;
optional int32 perPage = 2;
int32 page = 1;
int32 perPage = 2;
}
message Ads {

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
import { AdRepository } from '../../../database/domain/ad-repository';
import { Ad } from '../../domain/entities/ad';
//TODO : properly implement mutate operation to prisma
@Injectable()
export class AdsRepository extends AdRepository<Ad> {
protected _model = 'ad';

View File

@@ -0,0 +1,25 @@
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { DefaultParams } from '../../domain/types/default-params.type';
import { IProvideParams } from '../../domain/interfaces/param-provider.interface';
@Injectable()
export class DefaultParamsProvider implements IProvideParams {
constructor(private readonly configService: ConfigService) {}
getParams = (): DefaultParams => {
return {
MON_MARGIN: parseInt(this.configService.get('DEPARTURE_MARGIN')),
TUE_MARGIN: parseInt(this.configService.get('DEPARTURE_MARGIN')),
WED_MARGIN: parseInt(this.configService.get('DEPARTURE_MARGIN')),
THU_MARGIN: parseInt(this.configService.get('DEPARTURE_MARGIN')),
FRI_MARGIN: parseInt(this.configService.get('DEPARTURE_MARGIN')),
SAT_MARGIN: parseInt(this.configService.get('DEPARTURE_MARGIN')),
SUN_MARGIN: parseInt(this.configService.get('DEPARTURE_MARGIN')),
DRIVER: this.configService.get('ROLE') == 'driver' ? true : false,
SEATS_PROVIDED: parseInt(this.configService.get('SEATS_PROVIDED')),
PASSENGER: this.configService.get('ROLE') == 'passenger' ? true : false,
SEATS_REQUESTED: parseInt(this.configService.get('SEATS_REQUESTED')),
STRICT: false,
};
};
}

View File

@@ -0,0 +1,9 @@
import { CreateAdRequest } from '../domain/dtos/create-ad.request';
export class CreateAdCommand {
readonly createAdRequest: CreateAdRequest;
constructor(request: CreateAdRequest) {
this.createAdRequest = request;
}
}

View File

@@ -0,0 +1,130 @@
import { AutoMap } from '@automapper/classes';
import {
IsOptional,
IsString,
IsBoolean,
IsDate,
IsInt,
IsEnum,
ValidateNested,
IsUUID,
} from 'class-validator';
import { Frequency } from '../types/frequency.enum';
import { Address } from '../entities/address';
export class AdCreation {
@IsUUID(4)
@AutoMap()
uuid: string;
@IsUUID(4)
@AutoMap()
userUuid: string;
@IsBoolean()
@AutoMap()
driver: boolean;
@IsBoolean()
@AutoMap()
passenger: boolean;
@IsEnum(Frequency)
@AutoMap()
frequency: Frequency;
@IsDate()
@AutoMap()
fromDate: Date;
@IsDate()
@AutoMap()
toDate: Date;
@IsOptional()
@IsDate()
@AutoMap()
monTime?: string;
@IsOptional()
@IsString()
@AutoMap()
tueTime?: string;
@IsOptional()
@IsString()
@AutoMap()
wedTime?: string;
@IsOptional()
@IsString()
@AutoMap()
thuTime?: string;
@IsOptional()
@IsString()
@AutoMap()
friTime?: string;
@IsOptional()
@IsString()
@AutoMap()
satTime?: string;
@IsOptional()
@IsString()
@AutoMap()
sunTime?: string;
@IsInt()
@AutoMap()
monMargin: number;
@IsInt()
@AutoMap()
tueMargin: number;
@IsInt()
@AutoMap()
wedMargin: number;
@IsInt()
@AutoMap()
thuMargin: number;
@IsInt()
@AutoMap()
friMargin: number;
@IsInt()
@AutoMap()
satMargin: number;
@IsInt()
@AutoMap()
sunMargin: number;
@IsInt()
@AutoMap()
seatsDriver: number;
@IsInt()
@AutoMap()
seatsPassenger: number;
@IsBoolean()
@AutoMap()
strict: boolean;
@IsDate()
@AutoMap()
createdAt: Date;
@IsDate()
@AutoMap()
updatedAt?: Date;
@ValidateNested({ each: true })
@AutoMap()
addresses: { create: Address[] };
}

View File

@@ -0,0 +1,106 @@
import { AutoMap } from '@automapper/classes';
import {
IsOptional,
IsBoolean,
IsDate,
IsInt,
IsEnum,
ValidateNested,
ArrayMinSize,
IsUUID,
} from 'class-validator';
import { Frequency } from '../types/frequency.enum';
import { Transform, Type } from 'class-transformer';
import { mappingKeyToFrequency } from './validators/frequency.mapping';
import { MarginDTO } from './create.margin.dto';
import { ScheduleDTO } from './create.schedule.dto';
import { AddressRequestDTO } from './create.address.request';
import { IsPunctualOrRecurrent } from './validators/decorators/is-punctual-or-recurrent.validator';
import { HasProperDriverSeats } from './validators/decorators/has-driver-seats.validator';
import { HasProperPassengerSeats } from './validators/decorators/has-passenger-seats.validator';
import { HasProperPositionIndexes } from './validators/decorators/address-position.validator';
export class CreateAdRequest {
@IsOptional()
@IsUUID(4)
@AutoMap()
uuid?: string;
@IsUUID(4)
@AutoMap()
userUuid: string;
@IsOptional()
@IsBoolean()
@AutoMap()
driver?: boolean;
@IsOptional()
@IsBoolean()
@AutoMap()
passenger?: boolean;
@Transform(({ value }) => mappingKeyToFrequency(value), {
toClassOnly: true,
})
@IsEnum(Frequency)
@AutoMap()
frequency: Frequency;
@IsOptional()
@IsPunctualOrRecurrent()
@Type(() => Date)
@IsDate()
@AutoMap()
departure?: Date;
@IsOptional()
@IsPunctualOrRecurrent()
@Type(() => Date)
@IsDate()
@AutoMap()
fromDate?: Date;
@IsOptional()
@IsPunctualOrRecurrent()
@Type(() => Date)
@IsDate()
@AutoMap()
toDate?: Date;
@Type(() => ScheduleDTO)
@IsPunctualOrRecurrent()
@ValidateNested({ each: true })
@AutoMap()
schedule: ScheduleDTO = {};
@IsOptional()
@Type(() => MarginDTO)
@ValidateNested({ each: true })
@AutoMap()
marginDurations?: MarginDTO;
@IsOptional()
@HasProperDriverSeats()
@IsInt()
@AutoMap()
seatsDriver?: number;
@IsOptional()
@HasProperPassengerSeats()
@IsInt()
@AutoMap()
seatsPassenger?: number;
@IsOptional()
@IsBoolean()
@AutoMap()
strict?: boolean;
@ArrayMinSize(2)
@Type(() => AddressRequestDTO)
@HasProperPositionIndexes()
@ValidateNested({ each: true })
@AutoMap()
addresses: AddressRequestDTO[];
}

View File

@@ -0,0 +1,62 @@
import { AutoMap } from '@automapper/classes';
import {
IsInt,
IsLatitude,
IsLongitude,
IsOptional,
IsString,
IsUUID,
} from 'class-validator';
export class AddressRequestDTO {
@IsOptional()
@IsUUID(4)
@AutoMap()
uuid?: string;
@IsOptional()
@IsUUID(4)
@AutoMap()
adUuid?: string;
@IsOptional()
@IsInt()
@AutoMap()
position?: number;
@IsLongitude()
@AutoMap()
lon: number;
@IsLatitude()
@AutoMap()
lat: number;
@IsOptional()
@AutoMap()
name?: string;
@IsOptional()
@IsString()
@AutoMap()
houseNumber?: string;
@IsOptional()
@IsString()
@AutoMap()
street?: string;
@IsOptional()
@IsString()
@AutoMap()
locality?: string;
@IsOptional()
@IsString()
@AutoMap()
postalCode?: string;
@IsString()
@AutoMap()
country: string;
}

View File

@@ -0,0 +1,39 @@
import { AutoMap } from '@automapper/classes';
import { IsInt, IsOptional } from 'class-validator';
export class MarginDTO {
@IsOptional()
@IsInt()
@AutoMap()
mon?: number;
@IsOptional()
@IsInt()
@AutoMap()
tue?: number;
@IsOptional()
@IsInt()
@AutoMap()
wed?: number;
@IsOptional()
@IsInt()
@AutoMap()
thu?: number;
@IsOptional()
@IsInt()
@AutoMap()
fri?: number;
@IsOptional()
@IsInt()
@AutoMap()
sat?: number;
@IsOptional()
@IsInt()
@AutoMap()
sun?: number;
}

View File

@@ -0,0 +1,39 @@
import { AutoMap } from '@automapper/classes';
import { IsOptional, IsMilitaryTime } from 'class-validator';
export class ScheduleDTO {
@IsOptional()
@IsMilitaryTime()
@AutoMap()
mon?: string;
@IsOptional()
@IsMilitaryTime()
@AutoMap()
tue?: string;
@IsOptional()
@IsMilitaryTime()
@AutoMap()
wed?: string;
@IsOptional()
@IsMilitaryTime()
@AutoMap()
thu?: string;
@IsOptional()
@IsMilitaryTime()
@AutoMap()
fri?: string;
@IsOptional()
@IsMilitaryTime()
@AutoMap()
sat?: string;
@IsOptional()
@IsMilitaryTime()
@AutoMap()
sun?: string;
}

View File

@@ -0,0 +1,13 @@
import { AddressRequestDTO } from '../create.address.request';
export function hasProperPositionIndexes(value: AddressRequestDTO[]) {
if (value.every((address) => address.position === undefined)) return true;
else if (value.every((address) => typeof address.position === 'number')) {
value.sort((a, b) => a.position - b.position);
for (let i = 1; i < value.length; i++) {
if (value[i - 1].position >= value[i].position) return false;
}
return true;
}
return false;
}

View File

@@ -0,0 +1,25 @@
import { ValidateBy, ValidationOptions, buildMessage } from 'class-validator';
import { AddressRequestDTO } from '../../create.address.request';
import { hasProperPositionIndexes } from '../address-position';
export function HasProperPositionIndexes(
validationOptions?: ValidationOptions,
): PropertyDecorator {
return ValidateBy(
{
name: '',
constraints: [],
validator: {
validate: (value: AddressRequestDTO[]): boolean =>
hasProperPositionIndexes(value),
defaultMessage: buildMessage(
() =>
`indexes position incorrect, please provide a complete list of indexes or ordened list of adresses from start to end of journey`,
validationOptions,
),
},
},
validationOptions,
);
}

View File

@@ -0,0 +1,27 @@
import {
ValidateBy,
ValidationArguments,
ValidationOptions,
buildMessage,
} from 'class-validator';
import { hasProperDriverSeats } from '../has-driver-seats';
export function HasProperDriverSeats(
validationOptions?: ValidationOptions,
): PropertyDecorator {
return ValidateBy(
{
name: '',
constraints: [],
validator: {
validate: (value: any, args: ValidationArguments): boolean =>
hasProperDriverSeats(args),
defaultMessage: buildMessage(
() => `driver and driver seats are not correct`,
validationOptions,
),
},
},
validationOptions,
);
}

View File

@@ -0,0 +1,27 @@
import {
ValidateBy,
ValidationArguments,
ValidationOptions,
buildMessage,
} from 'class-validator';
import { hasProperPassengerSeats } from '../has-passenger-seats';
export function HasProperPassengerSeats(
validationOptions?: ValidationOptions,
): PropertyDecorator {
return ValidateBy(
{
name: '',
constraints: [],
validator: {
validate: (value, args: ValidationArguments): boolean =>
hasProperPassengerSeats(args),
defaultMessage: buildMessage(
() => `passenger and passenger seats are not correct`,
validationOptions,
),
},
},
validationOptions,
);
}

View File

@@ -0,0 +1,28 @@
import {
ValidateBy,
ValidationArguments,
ValidationOptions,
buildMessage,
} from 'class-validator';
import { isPunctualOrRecurrent } from '../is-punctual-or-recurrent';
export function IsPunctualOrRecurrent(
validationOptions?: ValidationOptions,
): PropertyDecorator {
return ValidateBy(
{
name: '',
constraints: [],
validator: {
validate: (value, args: ValidationArguments): boolean =>
isPunctualOrRecurrent(args),
defaultMessage: buildMessage(
() =>
`the departure, from date, to date and schedule must be properly set on reccurent or punctual ad `,
validationOptions,
),
},
},
validationOptions,
);
}

View File

@@ -0,0 +1,6 @@
import { Frequency } from '../../types/frequency.enum';
export const mappingKeyToFrequency = (index: number): Frequency => {
if (index == 1) return Frequency.PUNCTUAL;
if (index == 2) return Frequency.RECURRENT;
return undefined;
};

View File

@@ -0,0 +1,19 @@
import { ValidationArguments } from 'class-validator';
export function hasProperDriverSeats(args: ValidationArguments) {
if (
args.object['driver'] === true &&
typeof args.object['seatsDriver'] === 'number'
)
return args.object['seatsDriver'] > 0;
if (
(args.object['driver'] === false ||
args.object['driver'] === null ||
args.object['driver'] === undefined) &&
(args.object['seatsDriver'] === 0 ||
args.object['seatsDriver'] === null ||
args.object['seatsDriver'] === undefined)
)
return true;
return false;
}

View File

@@ -0,0 +1,19 @@
import { ValidationArguments } from 'class-validator';
export function hasProperPassengerSeats(args: ValidationArguments) {
if (
args.object['passenger'] === true &&
typeof args.object['seatsPassenger'] === 'number'
)
return args.object['seatsPassenger'] > 0;
else if (
(args.object['passenger'] === false ||
args.object['passenger'] === null ||
args.object['passenger'] === undefined) &&
(args.object['seatsPassenger'] === 0 ||
args.object['seatsPassenger'] === null ||
args.object['seatsPassenger'] === undefined)
)
return true;
else return false;
}

View File

@@ -0,0 +1,27 @@
import { ValidationArguments } from 'class-validator';
import { Frequency } from '../../types/frequency.enum';
function isPunctual(args: ValidationArguments): boolean {
if (
args.object['frequency'] === Frequency.PUNCTUAL &&
args.object['departure'] instanceof Date &&
!Object.keys(args.object['schedule']).length
)
return true;
return false;
}
function isRecurrent(args: ValidationArguments): boolean {
if (
args.object['frequency'] === Frequency.RECURRENT &&
args.object['fromDate'] instanceof Date &&
args.object['toDate'] instanceof Date &&
Object.keys(args.object['schedule']).length
)
return true;
return false;
}
export const isPunctualOrRecurrent = (args: ValidationArguments): boolean => {
return isPunctual(args) || isRecurrent(args);
};

View File

@@ -1,6 +1,131 @@
import { AutoMap } from '@automapper/classes';
import {
IsOptional,
IsString,
IsBoolean,
IsDate,
IsInt,
IsEnum,
ValidateNested,
ArrayMinSize,
IsUUID,
} from 'class-validator';
import { Address } from '../entities/address';
import { Frequency } from '../types/frequency.enum';
export class Ad {
@IsUUID(4)
@AutoMap()
uuid: string;
@IsUUID(4)
@AutoMap()
userUuid: string;
@IsBoolean()
@AutoMap()
driver: boolean;
@IsBoolean()
@AutoMap()
passenger: boolean;
@IsEnum(Frequency)
@AutoMap()
frequency: Frequency;
@IsDate()
@AutoMap()
fromDate: Date;
@IsDate()
@AutoMap()
toDate: Date;
@IsOptional()
@IsDate()
@AutoMap()
monTime?: string;
@IsOptional()
@IsString()
@AutoMap()
tueTime?: string;
@IsOptional()
@IsString()
@AutoMap()
wedTime?: string;
@IsOptional()
@IsString()
@AutoMap()
thuTime?: string;
@IsOptional()
@IsString()
@AutoMap()
friTime?: string;
@IsOptional()
@IsString()
@AutoMap()
satTime?: string;
@IsOptional()
@IsString()
@AutoMap()
sunTime?: string;
@IsInt()
@AutoMap()
monMargin: number;
@IsInt()
@AutoMap()
tueMargin: number;
@IsInt()
@AutoMap()
wedMargin: number;
@IsInt()
@AutoMap()
thuMargin: number;
@IsInt()
@AutoMap()
friMargin: number;
@IsInt()
@AutoMap()
satMargin: number;
@IsInt()
@AutoMap()
sunMargin: number;
@IsInt()
@AutoMap()
seatsDriver: number;
@IsInt()
@AutoMap()
seatsPassenger: number;
@IsBoolean()
@AutoMap()
strict: boolean;
@IsDate()
@AutoMap()
createdAt: Date;
@IsDate()
@AutoMap()
updatedAt?: Date;
@ArrayMinSize(2)
@ValidateNested({ each: true })
@AutoMap(() => [Address])
addresses: Address[];
}

View File

@@ -0,0 +1,40 @@
import { AutoMap } from '@automapper/classes';
import { IsInt, IsUUID } from 'class-validator';
export class Address {
@IsUUID(4)
@AutoMap()
uuid: string;
@IsUUID(4)
@AutoMap()
adUuid: string;
@IsInt()
@AutoMap()
position: number;
@AutoMap()
lon: number;
@AutoMap()
lat: number;
@AutoMap()
name?: string;
@AutoMap()
houseNumber?: string;
@AutoMap()
street?: string;
@AutoMap()
locality: string;
@AutoMap()
postalCode: string;
@AutoMap()
country: string;
}

View File

@@ -0,0 +1,95 @@
import { CreateAdRequest } from '../dtos/create-ad.request';
import { Frequency } from '../types/frequency.enum';
export class RecurrentNormaliser {
fromDateResolver(createAdRequest: CreateAdRequest): Date {
if (createAdRequest.frequency === Frequency.PUNCTUAL)
return createAdRequest.departure;
return createAdRequest.fromDate;
}
toDateResolver(createAdRequest: CreateAdRequest): Date {
if (createAdRequest.frequency === Frequency.PUNCTUAL)
return createAdRequest.departure;
return createAdRequest.toDate;
}
scheduleSunResolver(createAdRequest: CreateAdRequest): string {
if (
Object.keys(createAdRequest.schedule).length === 0 &&
createAdRequest.frequency == Frequency.PUNCTUAL &&
createAdRequest.departure.getDay() === 0
)
return `${('0' + createAdRequest.departure.getHours()).slice(-2)}:${(
'0' + createAdRequest.departure.getMinutes()
).slice(-2)}`;
return createAdRequest.schedule.sun;
}
scheduleMonResolver(createAdRequest: CreateAdRequest): string {
if (
Object.keys(createAdRequest.schedule).length === 0 &&
createAdRequest.frequency == Frequency.PUNCTUAL &&
createAdRequest.departure.getDay() === 1
) {
return `${('0' + createAdRequest.departure.getHours()).slice(-2)}:${(
'0' + createAdRequest.departure.getMinutes()
).slice(-2)}`;
}
return createAdRequest.schedule.mon;
}
scheduleTueResolver(createAdRequest: CreateAdRequest): string {
if (
Object.keys(createAdRequest.schedule).length === 0 &&
createAdRequest.frequency == Frequency.PUNCTUAL &&
createAdRequest.departure.getDay() === 2
)
return `${('0' + createAdRequest.departure.getHours()).slice(-2)}:${(
'0' + createAdRequest.departure.getMinutes()
).slice(-2)}`;
return createAdRequest.schedule.tue;
}
scheduleWedResolver(createAdRequest: CreateAdRequest): string {
if (
Object.keys(createAdRequest.schedule).length === 0 &&
createAdRequest.frequency == Frequency.PUNCTUAL &&
createAdRequest.departure.getDay() === 3
)
return `${('0' + createAdRequest.departure.getHours()).slice(-2)}:${(
'0' + createAdRequest.departure.getMinutes()
).slice(-2)}`;
return createAdRequest.schedule.wed;
}
scheduleThuResolver(createAdRequest: CreateAdRequest): string {
if (
Object.keys(createAdRequest.schedule).length === 0 &&
createAdRequest.frequency == Frequency.PUNCTUAL &&
createAdRequest.departure.getDay() === 4
)
return `${('0' + createAdRequest.departure.getHours()).slice(-2)}:${(
'0' + createAdRequest.departure.getMinutes()
).slice(-2)}`;
return createAdRequest.schedule.thu;
}
scheduleFriResolver(createAdRequest: CreateAdRequest): string {
if (
Object.keys(createAdRequest.schedule).length === 0 &&
createAdRequest.frequency == Frequency.PUNCTUAL &&
createAdRequest.departure.getDay() === 5
)
return `${('0' + createAdRequest.departure.getHours()).slice(-2)}:${(
'0' + createAdRequest.departure.getMinutes()
).slice(-2)}`;
return createAdRequest.schedule.fri;
}
scheduleSatResolver(createAdRequest: CreateAdRequest): string {
if (
Object.keys(createAdRequest.schedule).length === 0 &&
createAdRequest.frequency == Frequency.PUNCTUAL &&
createAdRequest.departure.getDay() === 6
)
return `${('0' + createAdRequest.departure.getHours()).slice(-2)}:${(
'0' + createAdRequest.departure.getMinutes()
).slice(-2)}`;
return createAdRequest.schedule.sat;
}
}

View File

@@ -0,0 +1,4 @@
import { DefaultParams } from '../types/default-params.type';
export interface IProvideParams {
getParams(): DefaultParams;
}

View File

@@ -0,0 +1,14 @@
export type DefaultParams = {
MON_MARGIN: number;
TUE_MARGIN: number;
WED_MARGIN: number;
THU_MARGIN: number;
FRI_MARGIN: number;
SAT_MARGIN: number;
SUN_MARGIN: number;
DRIVER: boolean;
SEATS_PROVIDED: number;
PASSENGER: boolean;
SEATS_REQUESTED: number;
STRICT: boolean;
};

View File

@@ -0,0 +1,4 @@
export enum Frequency {
PUNCTUAL = 'PUNCTUAL',
RECURRENT = 'RECURRENT',
}

View File

@@ -0,0 +1,107 @@
import { Mapper } from '@automapper/core';
import { InjectMapper } from '@automapper/nestjs';
import { Inject } from '@nestjs/common';
import { CommandHandler } from '@nestjs/cqrs';
import { Messager } from '../../adapters/secondaries/messager';
import { AdsRepository } from '../../adapters/secondaries/ads.repository';
import { CreateAdCommand } from '../../commands/create-ad.command';
import { CreateAdRequest } from '../dtos/create-ad.request';
import { IProvideParams } from '../interfaces/param-provider.interface';
import { DefaultParams } from '../types/default-params.type';
import { AdCreation } from '../dtos/ad.creation';
import { Ad } from '../entities/ad';
@CommandHandler(CreateAdCommand)
export class CreateAdUseCase {
private readonly defaultParams: DefaultParams;
private ad: AdCreation;
constructor(
private readonly _repository: AdsRepository,
private readonly _messager: Messager,
@InjectMapper() private readonly _mapper: Mapper,
@Inject('ParamsProvider')
private readonly defaultParamsProvider: IProvideParams,
) {
this.defaultParams = defaultParamsProvider.getParams();
}
async execute(command: CreateAdCommand): Promise<Ad> {
this.ad = this._mapper.map(
command.createAdRequest,
CreateAdRequest,
AdCreation,
);
this.setDefaultSchedule();
this.setDefaultAddressesPosition();
this.setDefaultDriverAndPassengerParameters();
this.setDefaultDistanceMargin();
try {
const adCreated: Ad = await this._repository.create(this.ad);
this._messager.publish('ad.create', JSON.stringify(adCreated));
this._messager.publish(
'logging.ad.create.info',
JSON.stringify(adCreated),
);
return adCreated;
} catch (error) {
let key = 'logging.ad.create.crit';
if (error.message.includes('Already exists')) {
key = 'logging.ad.create.warning';
}
this._messager.publish(
key,
JSON.stringify({
command,
error,
}),
);
throw error;
}
}
setDefaultSchedule(): void {
if (this.ad.monMargin === undefined)
this.ad.monMargin = this.defaultParams.MON_MARGIN;
if (this.ad.tueMargin === undefined)
this.ad.tueMargin = this.defaultParams.TUE_MARGIN;
if (this.ad.wedMargin === undefined)
this.ad.wedMargin = this.defaultParams.WED_MARGIN;
if (this.ad.thuMargin === undefined)
this.ad.thuMargin = this.defaultParams.THU_MARGIN;
if (this.ad.friMargin === undefined)
this.ad.friMargin = this.defaultParams.FRI_MARGIN;
if (this.ad.satMargin === undefined)
this.ad.satMargin = this.defaultParams.SAT_MARGIN;
if (this.ad.sunMargin === undefined)
this.ad.sunMargin = this.defaultParams.SUN_MARGIN;
}
setDefaultDistanceMargin(): void {
if (this.ad.strict === undefined)
this.ad.strict = this.defaultParams.STRICT;
}
setDefaultDriverAndPassengerParameters(): void {
if (!this.ad.driver && !this.ad.passenger) {
this.ad.driver = this.defaultParams.DRIVER;
this.ad.seatsDriver = this.defaultParams.SEATS_PROVIDED;
this.ad.passenger = this.defaultParams.PASSENGER;
this.ad.seatsPassenger = this.defaultParams.SEATS_REQUESTED;
} else {
if (!this.ad.driver) {
this.ad.driver = false;
this.ad.seatsDriver = 0;
}
if (!this.ad.passenger) {
this.ad.passenger = false;
this.ad.seatsPassenger = 0;
}
}
}
setDefaultAddressesPosition(): void {
if (this.ad.addresses.create[0].position === undefined) {
for (let i = 0; i < this.ad.addresses.create.length; i++) {
this.ad.addresses.create[i].position = i;
}
}
}
}

View File

@@ -1,18 +1,139 @@
import { createMap, Mapper } from '@automapper/core';
import { createMap, forMember, mapFrom, Mapper } from '@automapper/core';
import { AutomapperProfile, InjectMapper } from '@automapper/nestjs';
import { Injectable } from '@nestjs/common';
import { Ad } from '../domain/entities/ad';
import { AdPresenter } from '../adapters/primaries/ad.presenter';
import { CreateAdRequest } from '../domain/dtos/create-ad.request';
import { AdCreation } from '../domain/dtos/ad.creation';
import { RecurrentNormaliser } from '../domain/entities/recurrent-normaliser';
@Injectable()
export class AdProfile extends AutomapperProfile {
recurrentNormaliser = new RecurrentNormaliser();
constructor(@InjectMapper() mapper: Mapper) {
super(mapper);
}
override get profile() {
return (mapper) => {
createMap(mapper, Ad, AdPresenter);
createMap(
mapper,
CreateAdRequest,
AdCreation,
forMember(
(destination) => destination.monMargin,
mapFrom((source) => source.marginDurations.mon),
),
forMember(
(destination) => destination.tueMargin,
mapFrom((source) => source.marginDurations.tue),
),
forMember(
(destination) => destination.wedMargin,
mapFrom((source) => source.marginDurations.wed),
),
forMember(
(destination) => destination.thuMargin,
mapFrom((source) => source.marginDurations.thu),
),
forMember(
(destination) => destination.friMargin,
mapFrom((source) => source.marginDurations.fri),
),
forMember(
(destination) => destination.satMargin,
mapFrom((source) => source.marginDurations.sat),
),
forMember(
(destination) => destination.sunMargin,
mapFrom((source) => source.marginDurations.sun),
),
forMember(
(destination) => destination.monTime,
mapFrom((source) => source.schedule.mon),
),
forMember(
(destination) => destination.tueTime,
mapFrom((source) => source.schedule.tue),
),
forMember(
(destination) => destination.wedTime,
mapFrom((source) => source.schedule.wed),
),
forMember(
(destination) => destination.thuTime,
mapFrom((source) => source.schedule.thu),
),
forMember(
(destination) => destination.friTime,
mapFrom((source) => source.schedule.fri),
),
forMember(
(destination) => destination.satTime,
mapFrom((source) => source.schedule.sat),
),
forMember(
(destination) => destination.sunTime,
mapFrom((source) => source.schedule.sun),
),
forMember(
(destination) => destination.addresses.create,
mapFrom((source) => source.addresses),
),
forMember(
(destination) => destination.fromDate,
mapFrom((source) =>
this.recurrentNormaliser.fromDateResolver(source),
),
),
forMember(
(destination) => destination.toDate,
mapFrom((source) => this.recurrentNormaliser.toDateResolver(source)),
),
forMember(
(destination) => destination.monTime,
mapFrom((source) =>
this.recurrentNormaliser.scheduleMonResolver(source),
),
),
forMember(
(destination) => destination.tueTime,
mapFrom((source) =>
this.recurrentNormaliser.scheduleTueResolver(source),
),
),
forMember(
(destination) => destination.wedTime,
mapFrom((source) =>
this.recurrentNormaliser.scheduleWedResolver(source),
),
),
forMember(
(destination) => destination.thuTime,
mapFrom((source) =>
this.recurrentNormaliser.scheduleThuResolver(source),
),
),
forMember(
(destination) => destination.friTime,
mapFrom((source) =>
this.recurrentNormaliser.scheduleFriResolver(source),
),
),
forMember(
(destination) => destination.satTime,
mapFrom((source) =>
this.recurrentNormaliser.scheduleSatResolver(source),
),
),
forMember(
(destination) => destination.sunTime,
mapFrom((source) =>
this.recurrentNormaliser.scheduleSunResolver(source),
),
),
);
};
}
}

View File

@@ -0,0 +1,18 @@
import { Mapper, createMap } from '@automapper/core';
import { AutomapperProfile, InjectMapper } from '@automapper/nestjs';
import { Injectable } from '@nestjs/common';
import { AddressRequestDTO } from '../domain/dtos/create.address.request';
import { Address } from '../domain/entities/address';
@Injectable()
export class AdProfile extends AutomapperProfile {
constructor(@InjectMapper() mapper: Mapper) {
super(mapper);
}
override get profile() {
return (mapper) => {
createMap(mapper, AddressRequestDTO, Address);
};
}
}

View File

@@ -0,0 +1,468 @@
import { Test } from '@nestjs/testing';
import { PrismaService } from '../../../database/adapters/secondaries/prisma-service';
import { AdsRepository } from '../../adapters/secondaries/ads.repository';
import { DatabaseModule } from '../../../database/database.module';
import { Frequency } from '../../domain/types/frequency.enum';
import { AdCreation } from '../../domain/dtos/ad.creation';
import { Address } from '../../domain/entities/address';
describe('Ad Repository', () => {
let prismaService: PrismaService;
let adsRepository: AdsRepository;
const executeInsertCommand = async (table: string, object: any) => {
const command = `INSERT INTO ${table} ("${Object.keys(object).join(
'","',
)}") VALUES (${Object.values(object).join(',')})`;
await prismaService.$executeRawUnsafe(command);
};
const getSeed = (index: number, uuid: string): string => {
return `'${uuid.slice(0, -2)}${index.toString(16).padStart(2, '0')}'`;
};
const baseUuid = {
uuid: 'be459a29-7a41-4c0b-b371-abe90bfb6f00',
};
const baseAdress0Uuid = {
uuid: 'bad5e786-3b15-4e51-a8fc-926fa9327ff1',
};
const baseAdress1Uuid = {
uuid: '4d200eb6-7389-487f-a1ca-dbc0e40381c9',
};
const baseUserUuid = {
userUuid: "'113e0000-0000-4000-a000-000000000000'",
};
const driverAd = {
driver: 'true',
passenger: 'false',
seatsDriver: 3,
seatsPassenger: 0,
strict: 'false',
};
const passengerAd = {
driver: 'false',
passenger: 'true',
seatsDriver: 0,
seatsPassenger: 1,
strict: 'false',
};
const driverAndPassengerAd = {
driver: 'true',
passenger: 'true',
seatsDriver: 3,
seatsPassenger: 1,
strict: 'false',
};
const punctualAd = {
frequency: `'PUNCTUAL'`,
fromDate: `'2023-01-01'`,
toDate: `'2023-01-01'`,
monTime: 'NULL',
tueTime: 'NULL',
wedTime: 'NULL',
thuTime: 'NULL',
friTime: 'NULL',
satTime: 'NULL',
sunTime: `'07:00'`,
monMargin: 900,
tueMargin: 900,
wedMargin: 900,
thuMargin: 900,
friMargin: 900,
satMargin: 900,
sunMargin: 900,
};
const recurrentAd = {
frequency: `'RECURRENT'`,
fromDate: `'2023-01-01'`,
toDate: `'2023-12-31'`,
monTime: `'07:00'`,
tueTime: `'07:00'`,
wedTime: `'07:00'`,
thuTime: `'07:00'`,
friTime: `'07:00'`,
satTime: 'NULL',
sunTime: 'NULL',
monMargin: 900,
tueMargin: 900,
wedMargin: 900,
thuMargin: 900,
friMargin: 900,
satMargin: 900,
sunMargin: 900,
};
const address0 = {
position: 0,
lon: 43.7102,
lat: 7.262,
locality: "'Nice'",
postalCode: "'06000'",
country: "'France'",
};
const address1 = {
position: 1,
lon: 43.2965,
lat: 5.3698,
locality: "'Marseille'",
postalCode: "'13000'",
country: "'France'",
};
const createPunctualDriverAds = async (nbToCreate = 10) => {
const adToCreate = {
...baseUuid,
...baseUserUuid,
...driverAd,
...punctualAd,
};
for (let i = 0; i < nbToCreate; i++) {
adToCreate.uuid = getSeed(i, baseUuid.uuid);
await executeInsertCommand('ad', adToCreate);
await executeInsertCommand('address', {
uuid: getSeed(i, baseAdress0Uuid.uuid),
adUuid: adToCreate.uuid,
...address0,
});
await executeInsertCommand('address', {
uuid: getSeed(i, baseAdress1Uuid.uuid),
adUuid: adToCreate.uuid,
...address1,
});
}
};
const createRecurrentDriverAds = async (nbToCreate = 10) => {
const adToCreate = {
...baseUuid,
...baseUserUuid,
...driverAd,
...punctualAd,
};
for (let i = 0; i < nbToCreate; i++) {
adToCreate.uuid = getSeed(i, baseUuid.uuid);
await executeInsertCommand('ad', adToCreate);
await executeInsertCommand('address', {
uuid: getSeed(i, baseAdress0Uuid.uuid),
adUuid: adToCreate.uuid,
...address0,
});
await executeInsertCommand('address', {
uuid: getSeed(i, baseAdress1Uuid.uuid),
adUuid: adToCreate.uuid,
...address1,
});
}
};
const createPunctualPassengerAds = async (nbToCreate = 10) => {
const adToCreate = {
...baseUuid,
...baseUserUuid,
...passengerAd,
...punctualAd,
};
for (let i = 0; i < nbToCreate; i++) {
adToCreate.uuid = getSeed(i, baseUuid.uuid);
await executeInsertCommand('ad', adToCreate);
await executeInsertCommand('address', {
uuid: getSeed(i, baseAdress0Uuid.uuid),
adUuid: adToCreate.uuid,
...address0,
});
await executeInsertCommand('address', {
uuid: getSeed(i, baseAdress1Uuid.uuid),
adUuid: adToCreate.uuid,
...address1,
});
}
};
const createRecurrentPassengerAds = async (nbToCreate = 10) => {
const adToCreate = {
...baseUuid,
...baseUserUuid,
...passengerAd,
...recurrentAd,
};
for (let i = 0; i < nbToCreate; i++) {
adToCreate.uuid = getSeed(i, baseUuid.uuid);
await executeInsertCommand('ad', adToCreate);
await executeInsertCommand('address', {
uuid: getSeed(i, baseAdress0Uuid.uuid),
adUuid: adToCreate.uuid,
...address0,
});
await executeInsertCommand('address', {
uuid: getSeed(i, baseAdress1Uuid.uuid),
adUuid: adToCreate.uuid,
...address1,
});
}
};
const createPunctualDriverPassengerAds = async (nbToCreate = 10) => {
const adToCreate = {
...baseUuid,
...baseUserUuid,
...driverAndPassengerAd,
...punctualAd,
};
for (let i = 0; i < nbToCreate; i++) {
adToCreate.uuid = getSeed(i, baseUuid.uuid);
await executeInsertCommand('ad', adToCreate);
await executeInsertCommand('address', {
uuid: getSeed(i, baseAdress0Uuid.uuid),
adUuid: adToCreate.uuid,
...address0,
});
await executeInsertCommand('address', {
uuid: getSeed(i, baseAdress1Uuid.uuid),
adUuid: adToCreate.uuid,
...address1,
});
}
};
const createRecurrentDriverPassengerAds = async (nbToCreate = 10) => {
const adToCreate = {
...baseUuid,
...baseUserUuid,
...driverAndPassengerAd,
...recurrentAd,
};
for (let i = 0; i < nbToCreate; i++) {
adToCreate.uuid = getSeed(i, baseUuid.uuid);
await executeInsertCommand('ad', adToCreate);
await executeInsertCommand('address', {
uuid: getSeed(i, baseAdress0Uuid.uuid),
adUuid: adToCreate.uuid,
...address0,
});
await executeInsertCommand('address', {
uuid: getSeed(i, baseAdress1Uuid.uuid),
adUuid: adToCreate.uuid,
...address1,
});
}
};
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [DatabaseModule],
providers: [PrismaService, AdsRepository],
}).compile();
prismaService = module.get<PrismaService>(PrismaService);
adsRepository = module.get<AdsRepository>(AdsRepository);
});
afterAll(async () => {
await prismaService.$disconnect();
});
beforeEach(async () => {
await prismaService.ad.deleteMany();
});
describe('findAll', () => {
it('should return an empty data array', async () => {
const res = await adsRepository.findAll();
expect(res).toEqual({
data: [],
total: 0,
});
});
describe('drivers', () => {
it('should return a data array with 8 punctual driver ads', async () => {
await createPunctualDriverAds(8);
const ads = await adsRepository.findAll();
expect(ads.data.length).toBe(8);
expect(ads.total).toBe(8);
expect(ads.data[0].driver).toBeTruthy();
expect(ads.data[0].passenger).toBeFalsy();
});
it('should return a data array limited to 10 punctual driver ads', async () => {
await createPunctualDriverAds(20);
const ads = await adsRepository.findAll();
expect(ads.data.length).toBe(10);
expect(ads.total).toBe(20);
expect(ads.data[1].driver).toBeTruthy();
expect(ads.data[1].passenger).toBeFalsy();
});
it('should return a data array with 8 recurrent driver ads', async () => {
await createRecurrentDriverAds(8);
const ads = await adsRepository.findAll();
expect(ads.data.length).toBe(8);
expect(ads.total).toBe(8);
expect(ads.data[2].driver).toBeTruthy();
expect(ads.data[2].passenger).toBeFalsy();
});
it('should return a data array limited to 10 recurrent driver ads', async () => {
await createRecurrentDriverAds(20);
const ads = await adsRepository.findAll();
expect(ads.data.length).toBe(10);
expect(ads.total).toBe(20);
expect(ads.data[3].driver).toBeTruthy();
expect(ads.data[3].passenger).toBeFalsy();
});
});
describe('passengers', () => {
it('should return a data array with 7 punctual passenger ads', async () => {
await createPunctualPassengerAds(7);
const ads = await adsRepository.findAll();
expect(ads.data.length).toBe(7);
expect(ads.total).toBe(7);
expect(ads.data[0].passenger).toBeTruthy();
expect(ads.data[0].driver).toBeFalsy();
});
it('should return a data array limited to 10 punctual passenger ads', async () => {
await createPunctualPassengerAds(15);
const ads = await adsRepository.findAll();
expect(ads.data.length).toBe(10);
expect(ads.total).toBe(15);
expect(ads.data[1].passenger).toBeTruthy();
expect(ads.data[1].driver).toBeFalsy();
});
it('should return a data array with 7 recurrent passenger ads', async () => {
await createRecurrentPassengerAds(7);
const ads = await adsRepository.findAll();
expect(ads.data.length).toBe(7);
expect(ads.total).toBe(7);
expect(ads.data[2].passenger).toBeTruthy();
expect(ads.data[2].driver).toBeFalsy();
});
it('should return a data array limited to 10 recurrent passenger ads', async () => {
await createRecurrentPassengerAds(15);
const ads = await adsRepository.findAll();
expect(ads.data.length).toBe(10);
expect(ads.total).toBe(15);
expect(ads.data[3].passenger).toBeTruthy();
expect(ads.data[3].driver).toBeFalsy();
});
});
describe('drivers and passengers', () => {
it('should return a data array with 6 punctual driver and passenger ads', async () => {
await createPunctualDriverPassengerAds(6);
const ads = await adsRepository.findAll();
expect(ads.data.length).toBe(6);
expect(ads.total).toBe(6);
expect(ads.data[0].passenger).toBeTruthy();
expect(ads.data[0].driver).toBeTruthy();
});
it('should return a data array limited to 10 punctual driver and passenger ads', async () => {
await createPunctualDriverPassengerAds(16);
const ads = await adsRepository.findAll();
expect(ads.data.length).toBe(10);
expect(ads.total).toBe(16);
expect(ads.data[1].passenger).toBeTruthy();
expect(ads.data[1].driver).toBeTruthy();
});
it('should return a data array with 6 recurrent driver and passenger ads', async () => {
await createRecurrentDriverPassengerAds(6);
const ads = await adsRepository.findAll();
expect(ads.data.length).toBe(6);
expect(ads.total).toBe(6);
expect(ads.data[2].passenger).toBeTruthy();
expect(ads.data[2].driver).toBeTruthy();
});
it('should return a data array limited to 10 recurrent driver and passenger ads', async () => {
await createRecurrentDriverPassengerAds(16);
const ads = await adsRepository.findAll();
expect(ads.data.length).toBe(10);
expect(ads.total).toBe(16);
expect(ads.data[3].passenger).toBeTruthy();
expect(ads.data[3].driver).toBeTruthy();
});
});
});
describe('findOneByUuid', () => {
it('should return an ad', async () => {
await createPunctualDriverAds(1);
const ad = await adsRepository.findOneByUuid(baseUuid.uuid);
expect(ad.uuid).toBe(baseUuid.uuid);
});
it('should return null', async () => {
const ad = await adsRepository.findOneByUuid(
'544572be-11fb-4244-8235-587221fc9104',
);
expect(ad).toBeNull();
});
});
describe('create', () => {
it('should create an punctual ad', async () => {
const beforeCount = await prismaService.ad.count();
const adToCreate: AdCreation = new AdCreation();
adToCreate.uuid = 'be459a29-7a41-4c0b-b371-abe90bfb6f00';
adToCreate.userUuid = '4e52b54d-a729-4dbd-9283-f84a11bb2200';
adToCreate.driver = true;
adToCreate.passenger = false;
adToCreate.frequency = Frequency.PUNCTUAL;
adToCreate.fromDate = new Date('05-22-2023 09:36');
adToCreate.toDate = new Date('05-22-2023 09:36');
adToCreate.monTime = '09:36';
adToCreate.monMargin = 900;
adToCreate.tueMargin = 900;
adToCreate.wedMargin = 900;
adToCreate.thuMargin = 900;
adToCreate.friMargin = 900;
adToCreate.satMargin = 900;
adToCreate.sunMargin = 900;
adToCreate.seatsDriver = 3;
adToCreate.seatsPassenger = 0;
adToCreate.strict = false;
adToCreate.addresses = {
create: [address0 as Address, address1 as Address],
};
const ad = await adsRepository.create(adToCreate);
const afterCount = await prismaService.ad.count();
expect(afterCount - beforeCount).toBe(1);
expect(ad.uuid).toBe('be459a29-7a41-4c0b-b371-abe90bfb6f00');
});
it('should create an recurrent ad', async () => {
const beforeCount = await prismaService.ad.count();
const adToCreate: AdCreation = new AdCreation();
adToCreate.uuid = '137a26fa-4b38-48ba-aecf-1a75f6b20f3d';
adToCreate.userUuid = '4e52b54d-a729-4dbd-9283-f84a11bb2200';
adToCreate.driver = true;
adToCreate.passenger = false;
adToCreate.frequency = Frequency.RECURRENT;
adToCreate.fromDate = new Date('01-15-2023 ');
adToCreate.toDate = new Date('10-31-2023');
adToCreate.monTime = '07:30';
adToCreate.friTime = '07:45';
adToCreate.thuTime = '08:00';
adToCreate.monMargin = 900;
adToCreate.tueMargin = 900;
adToCreate.wedMargin = 900;
adToCreate.thuMargin = 900;
adToCreate.friMargin = 900;
adToCreate.satMargin = 900;
adToCreate.sunMargin = 900;
adToCreate.seatsDriver = 2;
adToCreate.seatsPassenger = 0;
adToCreate.strict = false;
adToCreate.addresses = {
create: [address0 as Address, address1 as Address],
};
const ad = await adsRepository.create(adToCreate);
const afterCount = await prismaService.ad.count();
expect(afterCount - beforeCount).toBe(1);
expect(ad.uuid).toBe('137a26fa-4b38-48ba-aecf-1a75f6b20f3d');
});
});
});

View File

@@ -0,0 +1,40 @@
import { ConfigService } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import { DefaultParamsProvider } from '../../../../adapters/secondaries/default-params.provider';
import { DefaultParams } from '../../../../domain/types/default-params.type';
const mockConfigService = {
get: jest.fn().mockImplementation(() => 'some_default_value'),
};
//TODO complete coverage
describe('DefaultParamsProvider', () => {
let defaultParamsProvider: DefaultParamsProvider;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [],
providers: [
DefaultParamsProvider,
{
provide: ConfigService,
useValue: mockConfigService,
},
],
}).compile();
defaultParamsProvider = module.get<DefaultParamsProvider>(
DefaultParamsProvider,
);
});
it('should be defined', () => {
expect(defaultParamsProvider).toBeDefined();
});
it('should provide default params', async () => {
const params: DefaultParams = defaultParamsProvider.getParams();
expect(params.SUN_MARGIN).toBeNaN();
expect(params.PASSENGER).toBe(false);
expect(params.DRIVER).toBe(false);
});
});

View File

@@ -1,7 +1,7 @@
import { AmqpConnection } from '@golevelup/nestjs-rabbitmq';
import { ConfigService } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import { Messager } from '../../adapters/secondaries/messager';
import { Messager } from '../../../../adapters/secondaries/messager';
const mockAmqpConnection = {
publish: jest.fn().mockImplementation(),

View File

@@ -0,0 +1,256 @@
import { Test, TestingModule } from '@nestjs/testing';
import { CreateAdUseCase } from '../../../domain/usecases/create-ad.usecase';
import { CreateAdRequest } from '../../../domain/dtos/create-ad.request';
import { Messager } from '../../../adapters/secondaries/messager';
import { AdsRepository } from '../../../adapters/secondaries/ads.repository';
import { CreateAdCommand } from '../../../commands/create-ad.command';
import { AutomapperModule } from '@automapper/nestjs';
import { classes } from '@automapper/classes';
import { Frequency } from '../../../domain/types/frequency.enum';
import { Ad } from '../../../domain/entities/ad';
import { AdProfile } from '../../../mappers/ad.profile';
import { AddressRequestDTO } from '../../../domain/dtos/create.address.request';
import { AdCreation } from '../../../domain/dtos/ad.creation';
import { Address } from 'src/modules/ad/domain/entities/address';
const mockAddress1: AddressRequestDTO = {
position: 0,
lon: 48.68944505415954,
lat: 6.176510296462267,
houseNumber: '5',
street: 'Avenue Foch',
locality: 'Nancy',
postalCode: '54000',
country: 'France',
};
const mockAddress2: AddressRequestDTO = {
position: 1,
lon: 48.8566,
lat: 2.3522,
locality: 'Paris',
postalCode: '75000',
country: 'France',
};
const mockAddressWithoutPos1: AddressRequestDTO = {
lon: 43.2965,
lat: 5.3698,
locality: 'Marseille',
postalCode: '13000',
country: 'France',
};
const mockAddressWithoutPos2: AddressRequestDTO = {
lon: 43.7102,
lat: 7.262,
locality: 'Nice',
postalCode: '06000',
country: 'France',
};
const minimalRecurrentAdREquest: CreateAdRequest = {
userUuid: '224e0000-0000-4000-a000-000000000000',
frequency: Frequency.RECURRENT,
fromDate: new Date('01-05-2023'),
toDate: new Date('01-05-2024'),
schedule: {
mon: '08:00',
},
marginDurations: {},
addresses: [mockAddress1, mockAddress2],
};
const newAdRequest: CreateAdRequest = {
userUuid: '113e0000-0000-4000-a000-000000000000',
driver: true,
passenger: false,
frequency: Frequency.RECURRENT,
fromDate: new Date('01-05-2023'),
toDate: new Date('20-08-2023'),
schedule: {
tue: '08:00',
wed: '08:30',
},
marginDurations: {
mon: undefined,
tue: undefined,
wed: undefined,
thu: undefined,
fri: undefined,
sat: undefined,
sun: undefined,
},
seatsDriver: 2,
addresses: [mockAddress1, mockAddress2],
};
const mockMessager = {
publish: jest.fn().mockImplementation(),
};
const mockDefaultParamsProvider = {
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_PROVIDED: 0,
PASSENGER: true,
SEATS_REQUESTED: 1,
STRICT: false,
};
},
};
const mockAdRepository = {
create: jest
.fn()
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.mockImplementationOnce((command?: CreateAdCommand) => {
return Promise.resolve({
...newAdRequest,
uuid: 'ad000000-0000-4000-a000-000000000000',
createdAt: new Date('01-05-2023'),
});
})
.mockImplementationOnce(() => {
throw new Error('Already exists');
})
.mockImplementation(),
};
describe('CreateAdUseCase', () => {
let createAdUseCase: CreateAdUseCase;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [AutomapperModule.forRoot({ strategyInitializer: classes() })],
providers: [
{
provide: AdsRepository,
useValue: mockAdRepository,
},
{
provide: Messager,
useValue: mockMessager,
},
CreateAdUseCase,
AdProfile,
{
provide: 'ParamsProvider',
useValue: mockDefaultParamsProvider,
},
],
}).compile();
createAdUseCase = module.get<CreateAdUseCase>(CreateAdUseCase);
});
it('should be defined', () => {
expect(createAdUseCase).toBeDefined();
});
describe('execution', () => {
const newAdCommand = new CreateAdCommand(newAdRequest);
it('should create an new ad', async () => {
const newAd: Ad = await createAdUseCase.execute(newAdCommand);
expect(newAd.userUuid).toBe(newAdRequest.userUuid);
expect(newAd.uuid).toBeDefined();
});
it('should throw an error if the ad already exists', async () => {
await expect(
createAdUseCase.execute(newAdCommand),
).rejects.toBeInstanceOf(Error);
});
});
describe('Ad parameter default setting ', () => {
beforeEach(() => {
mockAdRepository.create.mockClear();
});
it('should define mimimal ad as 1 passager add', async () => {
const newAdCommand = new CreateAdCommand(minimalRecurrentAdREquest);
await createAdUseCase.execute(newAdCommand);
const expectedAdCreation = {
userUuid: minimalRecurrentAdREquest.userUuid,
frequency: minimalRecurrentAdREquest.frequency,
fromDate: minimalRecurrentAdREquest.fromDate,
toDate: minimalRecurrentAdREquest.toDate,
monTime: minimalRecurrentAdREquest.schedule.mon,
tueTime: undefined,
wedTime: undefined,
thuTime: undefined,
friTime: undefined,
satTime: undefined,
sunTime: undefined,
monMargin: mockDefaultParamsProvider.getParams().MON_MARGIN,
tueMargin: mockDefaultParamsProvider.getParams().TUE_MARGIN,
wedMargin: mockDefaultParamsProvider.getParams().WED_MARGIN,
thuMargin: mockDefaultParamsProvider.getParams().THU_MARGIN,
friMargin: mockDefaultParamsProvider.getParams().FRI_MARGIN,
satMargin: mockDefaultParamsProvider.getParams().SAT_MARGIN,
sunMargin: mockDefaultParamsProvider.getParams().SUN_MARGIN,
driver: mockDefaultParamsProvider.getParams().DRIVER,
seatsDriver: mockDefaultParamsProvider.getParams().SEATS_PROVIDED,
passenger: mockDefaultParamsProvider.getParams().PASSENGER,
seatsPassenger: mockDefaultParamsProvider.getParams().SEATS_REQUESTED,
strict: mockDefaultParamsProvider.getParams().STRICT,
addresses: {
create: minimalRecurrentAdREquest.addresses as Address[],
},
createdAt: undefined,
} as AdCreation;
expect(mockAdRepository.create).toBeCalledWith(expectedAdCreation);
});
it('should create an passengerAd with addresses without position ', async () => {
const newPunctualPassengerAdRequest: CreateAdRequest = {
userUuid: '113e0000-0000-4000-a000-000000000000',
passenger: true,
frequency: Frequency.PUNCTUAL,
departure: new Date('05-22-2023 09:36'),
marginDurations: {
mon: undefined,
tue: undefined,
wed: undefined,
thu: undefined,
fri: undefined,
sat: undefined,
sun: undefined,
},
seatsPassenger: 1,
addresses: [mockAddressWithoutPos1, mockAddressWithoutPos2],
schedule: {},
};
const newAdCommand = new CreateAdCommand(newPunctualPassengerAdRequest);
await createAdUseCase.execute(newAdCommand);
const expectedAdCreation = {
userUuid: newPunctualPassengerAdRequest.userUuid,
frequency: newPunctualPassengerAdRequest.frequency,
fromDate: newPunctualPassengerAdRequest.departure,
toDate: newPunctualPassengerAdRequest.departure,
monTime: '09:36',
tueTime: undefined,
wedTime: undefined,
thuTime: undefined,
friTime: undefined,
satTime: undefined,
sunTime: undefined,
monMargin: mockDefaultParamsProvider.getParams().MON_MARGIN,
tueMargin: mockDefaultParamsProvider.getParams().TUE_MARGIN,
wedMargin: mockDefaultParamsProvider.getParams().WED_MARGIN,
thuMargin: mockDefaultParamsProvider.getParams().THU_MARGIN,
friMargin: mockDefaultParamsProvider.getParams().FRI_MARGIN,
satMargin: mockDefaultParamsProvider.getParams().SAT_MARGIN,
sunMargin: mockDefaultParamsProvider.getParams().SUN_MARGIN,
driver: false,
seatsDriver: 0,
passenger: newPunctualPassengerAdRequest.passenger,
seatsPassenger: newPunctualPassengerAdRequest.seatsPassenger,
strict: mockDefaultParamsProvider.getParams().STRICT,
addresses: {
create: newPunctualPassengerAdRequest.addresses as Address[],
},
createdAt: undefined,
} as AdCreation;
expect(mockAdRepository.create).toBeCalledWith(expectedAdCreation);
});
});
});

View File

@@ -1,10 +1,10 @@
import { NotFoundException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { Messager } from '../../adapters/secondaries/messager';
import { FindAdByUuidQuery } from '../../queries/find-ad-by-uuid.query';
import { AdsRepository } from '../../adapters/secondaries/ads.repository';
import { FindAdByUuidUseCase } from '../../domain/usecases/find-ad-by-uuid.usecase';
import { FindAdByUuidRequest } from '../../domain/dtos/find-ad-by-uuid.request';
import { Messager } from '../../../adapters/secondaries/messager';
import { FindAdByUuidQuery } from '../../../queries/find-ad-by-uuid.query';
import { AdsRepository } from '../../../adapters/secondaries/ads.repository';
import { FindAdByUuidUseCase } from '../../../domain/usecases/find-ad-by-uuid.usecase';
import { FindAdByUuidRequest } from '../../../domain/dtos/find-ad-by-uuid.request';
const mockAd = {
uuid: 'bb281075-1b98-4456-89d6-c643d3044a91',
@@ -18,7 +18,7 @@ const mockAdRepository = {
return Promise.resolve(mockAd);
})
.mockImplementation(() => {
return Promise.resolve(undefined);
return Promise.resolve(null);
}),
};

View File

@@ -0,0 +1,15 @@
import { mappingKeyToFrequency } from '../../../domain/dtos/validators/frequency.mapping';
import { Frequency } from '../../../domain/types/frequency.enum';
describe('frequency mapping function ', () => {
it('should return punctual', () => {
expect(mappingKeyToFrequency(1)).toBe(Frequency.PUNCTUAL);
});
it('should return recurent', () => {
expect(mappingKeyToFrequency(2)).toBe(Frequency.RECURRENT);
});
it('should return undefined', () => {
expect(mappingKeyToFrequency(0)).toBeUndefined();
expect(mappingKeyToFrequency(3)).toBeUndefined();
});
});

View File

@@ -0,0 +1,100 @@
import { hasProperDriverSeats } from '../../../domain/dtos/validators/has-driver-seats';
describe('driver and/or driver seats validator', () => {
it('should validate if driver and drivers seats is not provided ', () => {
expect(
hasProperDriverSeats({
value: undefined,
constraints: [],
targetName: '',
object: { driver: undefined },
property: '',
}),
).toBe(true);
expect(
hasProperDriverSeats({
value: undefined,
constraints: [],
targetName: '',
object: { driver: false },
property: '',
}),
).toBe(true);
expect(
hasProperDriverSeats({
value: undefined,
constraints: [],
targetName: '',
object: { driver: null },
property: '',
}),
).toBe(true);
});
it('should not validate if driver is set to true but not the related seats is not provided or 0', () => {
expect(
hasProperDriverSeats({
value: undefined,
constraints: [],
targetName: '',
object: { driver: true },
property: '',
}),
).toBe(false);
expect(
hasProperDriverSeats({
value: undefined,
constraints: [],
targetName: '',
object: { driver: true, seatsDriver: 0 },
property: '',
}),
).toBe(false);
expect(
hasProperDriverSeats({
value: undefined,
constraints: [],
targetName: '',
object: { driver: true, seatsDriver: undefined },
property: '',
}),
).toBe(false);
expect(
hasProperDriverSeats({
value: undefined,
constraints: [],
targetName: '',
object: { driver: true, seatsDriver: null },
property: '',
}),
).toBe(false);
});
it('should not validate if driver seats are provided but driver is not set or set to false ', () => {
expect(
hasProperDriverSeats({
value: undefined,
constraints: [],
targetName: '',
object: { driver: false, seatsDriver: 1 },
property: '',
}),
).toBe(false);
expect(
hasProperDriverSeats({
value: undefined,
constraints: [],
targetName: '',
object: { driver: undefined, seatsDriver: 1 },
property: '',
}),
).toBe(false);
expect(
hasProperDriverSeats({
value: undefined,
constraints: [],
targetName: '',
object: { driver: null, seatsDriver: 1 },
property: '',
}),
).toBe(false);
});
});

View File

@@ -0,0 +1,100 @@
import { hasProperPassengerSeats } from '../../../domain/dtos/validators/has-passenger-seats';
describe('driver and/or passenger seats validator', () => {
it('should validate if passenger and passengers seats is not provided ', () => {
expect(
hasProperPassengerSeats({
value: undefined,
constraints: [],
targetName: '',
object: { passenger: undefined },
property: '',
}),
).toBe(true);
expect(
hasProperPassengerSeats({
value: undefined,
constraints: [],
targetName: '',
object: { passenger: false },
property: '',
}),
).toBe(true);
expect(
hasProperPassengerSeats({
value: undefined,
constraints: [],
targetName: '',
object: { passenger: null },
property: '',
}),
).toBe(true);
});
it('should not validate if passenger is set to true but not the related seats is not provided or 0', () => {
expect(
hasProperPassengerSeats({
value: undefined,
constraints: [],
targetName: '',
object: { passenger: true },
property: '',
}),
).toBe(false);
expect(
hasProperPassengerSeats({
value: undefined,
constraints: [],
targetName: '',
object: { passenger: true, seatsPassenger: 0 },
property: '',
}),
).toBe(false);
expect(
hasProperPassengerSeats({
value: undefined,
constraints: [],
targetName: '',
object: { passenger: true, seatsPassenger: undefined },
property: '',
}),
).toBe(false);
expect(
hasProperPassengerSeats({
value: undefined,
constraints: [],
targetName: '',
object: { passenger: true, seatsPassenger: null },
property: '',
}),
).toBe(false);
});
it('should not validate if passenger seats are provided but passenger is not set or set to false ', () => {
expect(
hasProperPassengerSeats({
value: undefined,
constraints: [],
targetName: '',
object: { passenger: false, seatsPassenger: 1 },
property: '',
}),
).toBe(false);
expect(
hasProperPassengerSeats({
value: undefined,
constraints: [],
targetName: '',
object: { passenger: undefined, seatsPassenger: 1 },
property: '',
}),
).toBe(false);
expect(
hasProperPassengerSeats({
value: undefined,
constraints: [],
targetName: '',
object: { passenger: null, seatsPassenger: 1 },
property: '',
}),
).toBe(false);
});
});

View File

@@ -0,0 +1,70 @@
import { AddressRequestDTO } from '../../../domain/dtos/create.address.request';
import { hasProperPositionIndexes } from '../../../domain/dtos/validators/address-position';
describe('addresses position validators', () => {
const mockAddress1: AddressRequestDTO = {
lon: 48.68944505415954,
lat: 6.176510296462267,
houseNumber: '5',
street: 'Avenue Foch',
locality: 'Nancy',
postalCode: '54000',
country: 'France',
};
const mockAddress2: AddressRequestDTO = {
lon: 48.8566,
lat: 2.3522,
locality: 'Paris',
postalCode: '75000',
country: 'France',
};
const mockAddress3: AddressRequestDTO = {
lon: 49.2628,
lat: 4.0347,
locality: 'Reims',
postalCode: '51454',
country: 'France',
};
it('should validate if none of position is definded ', () => {
expect(
hasProperPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
).toBeTruthy();
});
it('should throw an error if position are partialy defined ', () => {
mockAddress1.position = 0;
expect(
hasProperPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
).toBeFalsy();
});
it('should throw an error if position are partialy defined ', () => {
mockAddress1.position = 0;
mockAddress2.position = null;
mockAddress3.position = undefined;
expect(
hasProperPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
).toBeFalsy();
});
it('should throw an error if positions are not incremented ', () => {
mockAddress1.position = 0;
mockAddress2.position = 1;
mockAddress3.position = 1;
expect(
hasProperPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
).toBeFalsy();
});
it('should validate if all positions are defined and incremented', () => {
mockAddress1.position = 0;
mockAddress2.position = 1;
mockAddress3.position = 2;
expect(
hasProperPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
).toBeTruthy();
mockAddress1.position = 10;
mockAddress2.position = 0;
mockAddress3.position = 3;
expect(
hasProperPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
).toBeTruthy();
});
});

View File

@@ -0,0 +1,113 @@
import { isPunctualOrRecurrent } from '../../../domain/dtos/validators/is-punctual-or-recurrent';
import { Frequency } from '../../../domain/types/frequency.enum';
describe('punctual or reccurent validators', () => {
describe('punctual case ', () => {
describe('valid cases', () => {
it('should validate with valid departure and empty schedule ', () => {
expect(
isPunctualOrRecurrent({
value: undefined,
constraints: [],
targetName: '',
object: {
frequency: Frequency.PUNCTUAL,
departure: new Date('01-02-2023'),
schedule: {},
},
property: '',
}),
).toBeTruthy();
});
});
describe('invalid cases ', () => {
it('should not validate with invalid departure and empty schedule and margin', () => {
expect(
isPunctualOrRecurrent({
value: undefined,
constraints: [],
targetName: '',
object: {
frequency: Frequency.PUNCTUAL,
fromDate: new Date('20-10-2023'),
toDate: new Date('30-10-2023'),
},
property: '',
}),
).toBeFalsy();
});
it('should not validate with no empty schedule', () => {
expect(
isPunctualOrRecurrent({
value: undefined,
constraints: [],
targetName: '',
object: {
frequency: Frequency.PUNCTUAL,
departure: new Date('01-02-2023'),
schedule: {
mon: '08:30',
},
},
property: '',
}),
).toBeFalsy();
});
});
});
describe('reccurent case ', () => {
describe('valid cases', () => {
it('should validate with valid from date, to date and non empty schedule ', () => {
expect(
isPunctualOrRecurrent({
value: undefined,
constraints: [],
targetName: '',
object: {
frequency: Frequency.RECURRENT,
fromDate: new Date('01-15-2023'),
toDate: new Date('06-30-2023'),
schedule: {
mon: '08:30',
},
},
property: '',
}),
).toBeTruthy();
});
});
describe('invalid cases ', () => {
it('should not validate with empty schedule ', () => {
expect(
isPunctualOrRecurrent({
value: undefined,
constraints: [],
targetName: '',
object: {
frequency: Frequency.RECURRENT,
fromDate: new Date('01-15-2023'),
toDate: new Date('06-30-2023'),
schedule: {},
},
property: '',
}),
).toBeFalsy();
});
it('should not validate with invalid from date to date and empty schedule and margin', () => {
expect(
isPunctualOrRecurrent({
value: undefined,
constraints: [],
targetName: '',
object: {
frequency: Frequency.RECURRENT,
departure: new Date('20-10-2023'),
toDate: new Date('30-10-2023'),
},
property: '',
}),
).toBeFalsy();
});
});
});
});

View File

@@ -0,0 +1,92 @@
import { CreateAdRequest } from '../../../domain/dtos/create-ad.request';
import { ScheduleDTO } from '../../../domain/dtos/create.schedule.dto';
import { RecurrentNormaliser } from '../../../domain/entities/recurrent-normaliser';
import { Frequency } from '../../../domain/types/frequency.enum';
describe('recurrent normalizer transformer for punctual ad ', () => {
const recurrentNormaliser = new RecurrentNormaliser();
it('should transform punctual ad into recurrent ad ', () => {
const punctualAd: CreateAdRequest = {
userUuid: '',
frequency: Frequency.PUNCTUAL,
departure: new Date('05-03-2023 12:39:39 '),
schedule: {} as ScheduleDTO,
addresses: [],
};
expect(recurrentNormaliser.fromDateResolver(punctualAd)).toBe(
punctualAd.departure,
);
expect(recurrentNormaliser.toDateResolver(punctualAd)).toBe(
punctualAd.departure,
);
expect(recurrentNormaliser.scheduleMonResolver(punctualAd)).toBeUndefined();
expect(recurrentNormaliser.scheduleTueResolver(punctualAd)).toBeUndefined();
expect(recurrentNormaliser.scheduleWedResolver(punctualAd)).toBe('12:39');
expect(recurrentNormaliser.scheduleThuResolver(punctualAd)).toBeUndefined();
expect(recurrentNormaliser.scheduleFriResolver(punctualAd)).toBeUndefined();
expect(recurrentNormaliser.scheduleSatResolver(punctualAd)).toBeUndefined();
expect(recurrentNormaliser.scheduleSunResolver(punctualAd)).toBeUndefined();
});
it('should leave recurrent ad as is', () => {
const recurrentAd: CreateAdRequest = {
userUuid: '',
frequency: Frequency.RECURRENT,
schedule: {
mon: '08:30',
tue: '08:30',
wed: '09:00',
fri: '09:00',
},
addresses: [],
};
expect(recurrentNormaliser.fromDateResolver(recurrentAd)).toBe(
recurrentAd.departure,
);
expect(recurrentNormaliser.toDateResolver(recurrentAd)).toBe(
recurrentAd.departure,
);
expect(recurrentNormaliser.scheduleMonResolver(recurrentAd)).toBe(
recurrentAd.schedule.mon,
);
expect(recurrentNormaliser.scheduleTueResolver(recurrentAd)).toBe(
recurrentAd.schedule.tue,
);
expect(recurrentNormaliser.scheduleWedResolver(recurrentAd)).toBe(
recurrentAd.schedule.wed,
);
expect(recurrentNormaliser.scheduleThuResolver(recurrentAd)).toBe(
recurrentAd.schedule.thu,
);
expect(recurrentNormaliser.scheduleFriResolver(recurrentAd)).toBe(
recurrentAd.schedule.fri,
);
expect(recurrentNormaliser.scheduleSatResolver(recurrentAd)).toBe(
recurrentAd.schedule.sat,
);
expect(recurrentNormaliser.scheduleSunResolver(recurrentAd)).toBe(
recurrentAd.schedule.sun,
);
});
it('should pass for each day of the week of a deprarture ', () => {
const punctualAd: CreateAdRequest = {
userUuid: '',
frequency: Frequency.PUNCTUAL,
departure: undefined,
schedule: {} as ScheduleDTO,
addresses: [],
};
punctualAd.departure = new Date('05-01-2023 ');
expect(recurrentNormaliser.scheduleMonResolver(punctualAd)).toBe('00:00');
punctualAd.departure = new Date('05-02-2023 06:32:45');
expect(recurrentNormaliser.scheduleTueResolver(punctualAd)).toBe('06:32');
punctualAd.departure = new Date('05-03-2023 10:21');
expect(recurrentNormaliser.scheduleWedResolver(punctualAd)).toBe('10:21');
punctualAd.departure = new Date('05-04-2023 11:06:00');
expect(recurrentNormaliser.scheduleThuResolver(punctualAd)).toBe('11:06');
punctualAd.departure = new Date('05-05-2023 05:20');
expect(recurrentNormaliser.scheduleFriResolver(punctualAd)).toBe('05:20');
punctualAd.departure = new Date('05-06-2023');
expect(recurrentNormaliser.scheduleSatResolver(punctualAd)).toBe('00:00');
punctualAd.departure = new Date('05-07-2023');
expect(recurrentNormaliser.scheduleSunResolver(punctualAd)).toBe('00:00');
});
});

View File

@@ -85,7 +85,6 @@ export abstract class PrismaRepository<T> implements IRepository<T> {
data: entity,
include: include,
});
return res;
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError) {