mirror of
https://gitlab.com/mobicoop/v3/service/ad.git
synced 2026-01-10 21:12:40 +00:00
update packages, remove default values
This commit is contained in:
@@ -26,15 +26,15 @@ import { HEALTH_CRITICAL_LOGGING_KEY, SERVICE_NAME } from './app.constants';
|
||||
useFactory: async (
|
||||
configService: ConfigService,
|
||||
): Promise<ConfigurationModuleOptions> => ({
|
||||
domain: configService.get<string>('SERVICE_CONFIGURATION_DOMAIN'),
|
||||
domain: configService.get<string>('SERVICE_CONFIGURATION_DOMAIN') as string,
|
||||
messageBroker: {
|
||||
uri: configService.get<string>('MESSAGE_BROKER_URI'),
|
||||
exchange: configService.get<string>('MESSAGE_BROKER_EXCHANGE'),
|
||||
uri: configService.get<string>('MESSAGE_BROKER_URI') as string,
|
||||
exchange: configService.get<string>('MESSAGE_BROKER_EXCHANGE') as string,
|
||||
},
|
||||
redis: {
|
||||
host: configService.get<string>('REDIS_HOST'),
|
||||
host: configService.get<string>('REDIS_HOST') as string,
|
||||
password: configService.get<string>('REDIS_PASSWORD'),
|
||||
port: configService.get<number>('REDIS_PORT'),
|
||||
port: configService.get<number>('REDIS_PORT') as number,
|
||||
},
|
||||
setConfigurationBrokerQueue: 'ad-configuration-create-update',
|
||||
deleteConfigurationQueue: 'ad-configuration-delete',
|
||||
|
||||
@@ -22,6 +22,6 @@ async function bootstrap() {
|
||||
});
|
||||
|
||||
await app.startAllMicroservices();
|
||||
await app.listen(process.env.HEALTH_SERVICE_PORT);
|
||||
await app.listen(process.env.HEALTH_SERVICE_PORT as unknown as number);
|
||||
}
|
||||
bootstrap();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export const AD_MESSAGE_PUBLISHER = Symbol('AD_MESSAGE_PUBLISHER');
|
||||
export const PARAMS_PROVIDER = Symbol('PARAMS_PROVIDER');
|
||||
export const TIMEZONE_FINDER = Symbol('TIMEZONE_FINDER');
|
||||
export const TIME_CONVERTER = Symbol('TIME_CONVERTER');
|
||||
export const INPUT_DATETIME_TRANSFORMER = Symbol('INPUT_DATETIME_TRANSFORMER');
|
||||
|
||||
@@ -37,15 +37,15 @@ export class AdMapper
|
||||
const record: AdWriteModel = {
|
||||
uuid: copy.id,
|
||||
userUuid: copy.userId,
|
||||
driver: copy.driver,
|
||||
passenger: copy.passenger,
|
||||
driver: copy.driver as boolean,
|
||||
passenger: copy.passenger as boolean,
|
||||
frequency: copy.frequency,
|
||||
fromDate: new Date(copy.fromDate),
|
||||
toDate: new Date(copy.toDate),
|
||||
schedule: {
|
||||
create: copy.schedule.map((scheduleItem: ScheduleItemProps) => ({
|
||||
uuid: v4(),
|
||||
day: scheduleItem.day,
|
||||
day: scheduleItem.day as number,
|
||||
time: new Date(
|
||||
1970,
|
||||
0,
|
||||
@@ -53,14 +53,14 @@ export class AdMapper
|
||||
parseInt(scheduleItem.time.split(':')[0]),
|
||||
parseInt(scheduleItem.time.split(':')[1]),
|
||||
),
|
||||
margin: scheduleItem.margin,
|
||||
margin: scheduleItem.margin as number,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
})),
|
||||
},
|
||||
seatsProposed: copy.seatsProposed,
|
||||
seatsRequested: copy.seatsRequested,
|
||||
strict: copy.strict,
|
||||
seatsProposed: copy.seatsProposed as number,
|
||||
seatsRequested: copy.seatsRequested as number,
|
||||
strict: copy.strict as boolean,
|
||||
waypoints: {
|
||||
create: copy.waypoints.map((waypoint: WaypointProps) => ({
|
||||
uuid: v4(),
|
||||
@@ -92,7 +92,7 @@ export class AdMapper
|
||||
userId: record.userUuid,
|
||||
driver: record.driver,
|
||||
passenger: record.passenger,
|
||||
frequency: Frequency[record.frequency],
|
||||
frequency: record.frequency as Frequency,
|
||||
fromDate: record.fromDate.toISOString().split('T')[0],
|
||||
toDate: record.toDate.toISOString().split('T')[0],
|
||||
schedule: record.schedule.map((scheduleItem: ScheduleItemModel) => ({
|
||||
@@ -133,8 +133,8 @@ export class AdMapper
|
||||
const props = entity.getProps();
|
||||
const response = new AdResponseDto(entity);
|
||||
response.userId = props.userId;
|
||||
response.driver = props.driver;
|
||||
response.passenger = props.passenger;
|
||||
response.driver = props.driver as boolean;
|
||||
response.passenger = props.passenger as boolean;
|
||||
response.frequency = props.frequency;
|
||||
response.fromDate = this.outputDatetimeTransformer.fromDate(
|
||||
{
|
||||
@@ -156,7 +156,7 @@ export class AdMapper
|
||||
response.schedule = props.schedule.map(
|
||||
(scheduleItem: ScheduleItemProps) => ({
|
||||
day: this.outputDatetimeTransformer.day(
|
||||
scheduleItem.day,
|
||||
scheduleItem.day as number,
|
||||
{
|
||||
date: props.fromDate,
|
||||
time: scheduleItem.time,
|
||||
@@ -172,11 +172,11 @@ export class AdMapper
|
||||
},
|
||||
props.frequency,
|
||||
),
|
||||
margin: scheduleItem.margin,
|
||||
margin: scheduleItem.margin as number,
|
||||
}),
|
||||
);
|
||||
response.seatsProposed = props.seatsProposed;
|
||||
response.seatsRequested = props.seatsRequested;
|
||||
response.seatsProposed = props.seatsProposed as number;
|
||||
response.seatsRequested = props.seatsRequested as number;
|
||||
response.waypoints = props.waypoints.map((waypoint: WaypointProps) => ({
|
||||
position: waypoint.position,
|
||||
name: waypoint.address.name,
|
||||
|
||||
@@ -6,12 +6,10 @@ import {
|
||||
AD_REPOSITORY,
|
||||
INPUT_DATETIME_TRANSFORMER,
|
||||
OUTPUT_DATETIME_TRANSFORMER,
|
||||
PARAMS_PROVIDER,
|
||||
TIMEZONE_FINDER,
|
||||
TIME_CONVERTER,
|
||||
} from './ad.di-tokens';
|
||||
import { AdRepository } from './infrastructure/ad.repository';
|
||||
import { DefaultParamsProvider } from './infrastructure/default-params-provider';
|
||||
import { AdMapper } from './ad.mapper';
|
||||
import { CreateAdService } from './core/application/commands/create-ad/create-ad.service';
|
||||
import { TimezoneFinder } from './infrastructure/timezone-finder';
|
||||
@@ -52,10 +50,6 @@ const messagePublishers: Provider[] = [
|
||||
const orms: Provider[] = [PrismaService];
|
||||
|
||||
const adapters: Provider[] = [
|
||||
{
|
||||
provide: PARAMS_PROVIDER,
|
||||
useClass: DefaultParamsProvider,
|
||||
},
|
||||
{
|
||||
provide: TIMEZONE_FINDER,
|
||||
useClass: TimezoneFinder,
|
||||
@@ -91,7 +85,6 @@ const adapters: Provider[] = [
|
||||
PrismaService,
|
||||
AdMapper,
|
||||
AD_REPOSITORY,
|
||||
PARAMS_PROVIDER,
|
||||
TIMEZONE_FINDER,
|
||||
],
|
||||
})
|
||||
|
||||
@@ -5,15 +5,15 @@ import { Command, CommandProps } from '@mobicoop/ddd-library';
|
||||
|
||||
export class CreateAdCommand extends Command {
|
||||
readonly userId: string;
|
||||
readonly driver?: boolean;
|
||||
readonly passenger?: boolean;
|
||||
readonly frequency?: Frequency;
|
||||
readonly driver: boolean;
|
||||
readonly passenger: boolean;
|
||||
readonly frequency: Frequency;
|
||||
readonly fromDate: string;
|
||||
readonly toDate: string;
|
||||
readonly schedule: ScheduleItem[];
|
||||
readonly seatsProposed?: number;
|
||||
readonly seatsRequested?: number;
|
||||
readonly strict?: boolean;
|
||||
readonly strict: boolean;
|
||||
readonly waypoints: Waypoint[];
|
||||
|
||||
constructor(props: CommandProps<CreateAdCommand>) {
|
||||
|
||||
@@ -4,13 +4,10 @@ import { Inject } from '@nestjs/common';
|
||||
import {
|
||||
AD_REPOSITORY,
|
||||
INPUT_DATETIME_TRANSFORMER,
|
||||
PARAMS_PROVIDER,
|
||||
} from '@modules/ad/ad.di-tokens';
|
||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||
import { Waypoint } from '../../types/waypoint';
|
||||
import { DefaultParams } from '../../ports/default-params.type';
|
||||
import { AdRepositoryPort } from '../../ports/ad.repository.port';
|
||||
import { DefaultParamsProviderPort } from '../../ports/default-params-provider.port';
|
||||
import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
|
||||
import { AggregateID, ConflictException } from '@mobicoop/ddd-library';
|
||||
import { ScheduleItem } from '../../types/schedule-item';
|
||||
@@ -18,17 +15,13 @@ import { DateTimeTransformerPort } from '../../ports/datetime-transformer.port';
|
||||
|
||||
@CommandHandler(CreateAdCommand)
|
||||
export class CreateAdService implements ICommandHandler {
|
||||
private readonly _defaultParams: DefaultParams;
|
||||
|
||||
constructor(
|
||||
@Inject(AD_REPOSITORY)
|
||||
private readonly repository: AdRepositoryPort,
|
||||
@Inject(PARAMS_PROVIDER)
|
||||
private readonly defaultParamsProvider: DefaultParamsProviderPort,
|
||||
@Inject(INPUT_DATETIME_TRANSFORMER)
|
||||
private readonly datetimeTransformer: DateTimeTransformerPort,
|
||||
) {
|
||||
this._defaultParams = defaultParamsProvider.getParams();
|
||||
}
|
||||
|
||||
async execute(command: CreateAdCommand): Promise<AggregateID> {
|
||||
@@ -87,8 +80,8 @@ export class CreateAdService implements ICommandHandler {
|
||||
),
|
||||
margin: scheduleItem.margin,
|
||||
})),
|
||||
seatsProposed: command.seatsProposed,
|
||||
seatsRequested: command.seatsRequested,
|
||||
seatsProposed: command.seatsProposed ?? 0,
|
||||
seatsRequested: command.seatsRequested ?? 0,
|
||||
strict: command.strict,
|
||||
waypoints: command.waypoints.map((waypoint: Waypoint) => ({
|
||||
position: waypoint.position,
|
||||
@@ -106,14 +99,6 @@ export class CreateAdService implements ICommandHandler {
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
driver: this._defaultParams.DRIVER,
|
||||
passenger: this._defaultParams.PASSENGER,
|
||||
marginDuration: this._defaultParams.DEPARTURE_TIME_MARGIN,
|
||||
strict: this._defaultParams.STRICT,
|
||||
seatsProposed: this._defaultParams.SEATS_PROPOSED,
|
||||
seatsRequested: this._defaultParams.SEATS_REQUESTED,
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { DefaultParams } from './default-params.type';
|
||||
|
||||
export interface DefaultParamsProviderPort {
|
||||
getParams(): DefaultParams;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
export type DefaultParams = {
|
||||
DRIVER: boolean;
|
||||
PASSENGER: boolean;
|
||||
SEATS_PROPOSED: number;
|
||||
SEATS_REQUESTED: number;
|
||||
DEPARTURE_TIME_MARGIN: number;
|
||||
STRICT: boolean;
|
||||
TIMEZONE: string;
|
||||
};
|
||||
@@ -1,3 +1,3 @@
|
||||
export interface TimezoneFinderPort {
|
||||
timezones(lon: number, lat: number, defaultTimezone?: string): string[];
|
||||
timezones(lon: number, lat: number): string[];
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export type ScheduleItem = {
|
||||
day?: number;
|
||||
day: number;
|
||||
time: string;
|
||||
margin?: number;
|
||||
margin: number;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Address } from './address';
|
||||
|
||||
export type Waypoint = {
|
||||
position?: number;
|
||||
position: number;
|
||||
} & Address;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
|
||||
import { v4 } from 'uuid';
|
||||
import { AdCreatedDomainEvent } from './events/ad-created.domain-events';
|
||||
import { AdProps, CreateAdProps, DefaultAdProps } from './ad.types';
|
||||
import { AdProps, CreateAdProps } from './ad.types';
|
||||
import { ScheduleItemProps } from './value-objects/schedule-item.value-object';
|
||||
import { WaypointProps } from './value-objects/waypoint.value-object';
|
||||
|
||||
@@ -9,21 +9,11 @@ export class AdEntity extends AggregateRoot<AdProps> {
|
||||
protected readonly _id: AggregateID;
|
||||
|
||||
static create = (
|
||||
create: CreateAdProps,
|
||||
defaultAdProps: DefaultAdProps,
|
||||
create: CreateAdProps
|
||||
): AdEntity => {
|
||||
const id = v4();
|
||||
const props: AdProps = { ...create };
|
||||
const ad = new AdEntity({ id, props })
|
||||
.setMissingMarginDurations(defaultAdProps.marginDuration)
|
||||
.setMissingStrict(defaultAdProps.strict)
|
||||
.setDefaultDriverAndPassengerParameters({
|
||||
driver: defaultAdProps.driver,
|
||||
passenger: defaultAdProps.passenger,
|
||||
seatsProposed: defaultAdProps.seatsProposed,
|
||||
seatsRequested: defaultAdProps.seatsRequested,
|
||||
})
|
||||
.setMissingWaypointsPosition();
|
||||
const ad = new AdEntity({ id, props });
|
||||
ad.addEvent(
|
||||
new AdCreatedDomainEvent({
|
||||
aggregateId: id,
|
||||
@@ -34,9 +24,9 @@ export class AdEntity extends AggregateRoot<AdProps> {
|
||||
fromDate: props.fromDate,
|
||||
toDate: props.toDate,
|
||||
schedule: props.schedule.map((day: ScheduleItemProps) => ({
|
||||
day: day.day,
|
||||
day: day.day as number,
|
||||
time: day.time,
|
||||
margin: day.margin,
|
||||
margin: day.margin as number,
|
||||
})),
|
||||
seatsProposed: props.seatsProposed,
|
||||
seatsRequested: props.seatsRequested,
|
||||
@@ -48,7 +38,7 @@ export class AdEntity extends AggregateRoot<AdProps> {
|
||||
street: waypoint.address.street,
|
||||
postalCode: waypoint.address.postalCode,
|
||||
locality: waypoint.address.locality,
|
||||
country: waypoint.address.postalCode,
|
||||
country: waypoint.address.country,
|
||||
lon: waypoint.address.coordinates.lon,
|
||||
lat: waypoint.address.coordinates.lat,
|
||||
})),
|
||||
@@ -57,60 +47,7 @@ export class AdEntity extends AggregateRoot<AdProps> {
|
||||
return ad;
|
||||
};
|
||||
|
||||
private setMissingMarginDurations = (
|
||||
defaultMarginDuration: number,
|
||||
): AdEntity => {
|
||||
this.props.schedule.forEach((day: ScheduleItemProps) => {
|
||||
if (day.margin === undefined) day.margin = defaultMarginDuration;
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
private setMissingStrict = (strict: boolean): AdEntity => {
|
||||
if (this.props.strict === undefined) this.props.strict = strict;
|
||||
return this;
|
||||
};
|
||||
|
||||
private setDefaultDriverAndPassengerParameters = (
|
||||
defaultDriverAndPassengerParameters: DefaultDriverAndPassengerParameters,
|
||||
): AdEntity => {
|
||||
this.props.driver = !!this.props.driver;
|
||||
this.props.passenger = !!this.props.passenger;
|
||||
if (!this.props.driver && !this.props.passenger) {
|
||||
this.props.driver = defaultDriverAndPassengerParameters.driver;
|
||||
this.props.seatsProposed =
|
||||
defaultDriverAndPassengerParameters.seatsProposed;
|
||||
this.props.passenger = defaultDriverAndPassengerParameters.passenger;
|
||||
this.props.seatsRequested =
|
||||
defaultDriverAndPassengerParameters.seatsRequested;
|
||||
return this;
|
||||
}
|
||||
if (!this.props.seatsProposed || this.props.seatsProposed <= 0)
|
||||
this.props.seatsProposed =
|
||||
defaultDriverAndPassengerParameters.seatsProposed;
|
||||
if (!this.props.seatsRequested || this.props.seatsRequested <= 0)
|
||||
this.props.seatsRequested =
|
||||
defaultDriverAndPassengerParameters.seatsRequested;
|
||||
return this;
|
||||
};
|
||||
|
||||
private setMissingWaypointsPosition = (): AdEntity => {
|
||||
if (this.props.waypoints[0].position === undefined) {
|
||||
for (let i = 0; i < this.props.waypoints.length; i++) {
|
||||
this.props.waypoints[i].position = i;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
validate(): void {
|
||||
// entity business rules validation to protect it's invariant before saving entity to a database
|
||||
}
|
||||
}
|
||||
|
||||
interface DefaultDriverAndPassengerParameters {
|
||||
driver: boolean;
|
||||
passenger: boolean;
|
||||
seatsProposed: number;
|
||||
seatsRequested: number;
|
||||
}
|
||||
|
||||
@@ -31,15 +31,6 @@ export interface CreateAdProps {
|
||||
waypoints: WaypointProps[];
|
||||
}
|
||||
|
||||
export interface DefaultAdProps {
|
||||
driver: boolean;
|
||||
passenger: boolean;
|
||||
marginDuration: number;
|
||||
strict: boolean;
|
||||
seatsProposed: number;
|
||||
seatsRequested: number;
|
||||
}
|
||||
|
||||
export enum Frequency {
|
||||
PUNCTUAL = 'PUNCTUAL',
|
||||
RECURRENT = 'RECURRENT',
|
||||
|
||||
@@ -17,23 +17,23 @@ export interface AddressProps {
|
||||
}
|
||||
|
||||
export class Address extends ValueObject<AddressProps> {
|
||||
get name(): string {
|
||||
get name(): string | undefined {
|
||||
return this.props.name;
|
||||
}
|
||||
|
||||
get houseNumber(): string {
|
||||
get houseNumber(): string | undefined {
|
||||
return this.props.houseNumber;
|
||||
}
|
||||
|
||||
get street(): string {
|
||||
get street(): string | undefined {
|
||||
return this.props.street;
|
||||
}
|
||||
|
||||
get locality(): string {
|
||||
get locality(): string | undefined {
|
||||
return this.props.locality;
|
||||
}
|
||||
|
||||
get postalCode(): string {
|
||||
get postalCode(): string | undefined {
|
||||
return this.props.postalCode;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { DefaultParamsProviderPort } from '../core/application/ports/default-params-provider.port';
|
||||
import { DefaultParams } from '../core/application/ports/default-params.type';
|
||||
|
||||
@Injectable()
|
||||
export class DefaultParamsProvider implements DefaultParamsProviderPort {
|
||||
constructor(private readonly _configService: ConfigService) {}
|
||||
getParams = (): DefaultParams => ({
|
||||
DRIVER: this._configService.get('ROLE') == 'driver',
|
||||
SEATS_PROPOSED: parseInt(this._configService.get('SEATS_PROPOSED')),
|
||||
PASSENGER: this._configService.get('ROLE') == 'passenger',
|
||||
SEATS_REQUESTED: parseInt(this._configService.get('SEATS_REQUESTED')),
|
||||
DEPARTURE_TIME_MARGIN: parseInt(
|
||||
this._configService.get('DEPARTURE_TIME_MARGIN'),
|
||||
),
|
||||
STRICT: this._configService.get('STRICT_FREQUENCY') == 'true',
|
||||
TIMEZONE: this._configService.get('DEFAULT_TIMEZONE'),
|
||||
});
|
||||
}
|
||||
@@ -6,24 +6,18 @@ import {
|
||||
} from '../core/application/ports/datetime-transformer.port';
|
||||
import { TimeConverterPort } from '../core/application/ports/time-converter.port';
|
||||
import {
|
||||
PARAMS_PROVIDER,
|
||||
TIMEZONE_FINDER,
|
||||
TIME_CONVERTER,
|
||||
} from '../ad.di-tokens';
|
||||
import { TimezoneFinderPort } from '../core/application/ports/timezone-finder.port';
|
||||
import { DefaultParamsProviderPort } from '../core/application/ports/default-params-provider.port';
|
||||
|
||||
@Injectable()
|
||||
export class InputDateTimeTransformer implements DateTimeTransformerPort {
|
||||
private readonly _defaultTimezone: string;
|
||||
constructor(
|
||||
@Inject(PARAMS_PROVIDER)
|
||||
private readonly defaultParamsProvider: DefaultParamsProviderPort,
|
||||
@Inject(TIMEZONE_FINDER)
|
||||
private readonly timezoneFinder: TimezoneFinderPort,
|
||||
@Inject(TIME_CONVERTER) private readonly timeConverter: TimeConverterPort,
|
||||
) {
|
||||
this._defaultTimezone = defaultParamsProvider.getParams().TIMEZONE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,7 +33,6 @@ export class InputDateTimeTransformer implements DateTimeTransformerPort {
|
||||
this.timezoneFinder.timezones(
|
||||
geoFromDate.coordinates.lon,
|
||||
geoFromDate.coordinates.lat,
|
||||
this._defaultTimezone,
|
||||
)[0],
|
||||
)
|
||||
.toISOString()
|
||||
@@ -76,7 +69,6 @@ export class InputDateTimeTransformer implements DateTimeTransformerPort {
|
||||
this.timezoneFinder.timezones(
|
||||
geoFromDate.coordinates.lon,
|
||||
geoFromDate.coordinates.lat,
|
||||
this._defaultTimezone,
|
||||
)[0],
|
||||
);
|
||||
return new Date(this.fromDate(geoFromDate, frequency)).getUTCDay();
|
||||
@@ -92,7 +84,6 @@ export class InputDateTimeTransformer implements DateTimeTransformerPort {
|
||||
this.timezoneFinder.timezones(
|
||||
geoFromDate.coordinates.lon,
|
||||
geoFromDate.coordinates.lat,
|
||||
this._defaultTimezone,
|
||||
)[0],
|
||||
);
|
||||
return this.timeConverter
|
||||
@@ -102,7 +93,6 @@ export class InputDateTimeTransformer implements DateTimeTransformerPort {
|
||||
this.timezoneFinder.timezones(
|
||||
geoFromDate.coordinates.lon,
|
||||
geoFromDate.coordinates.lat,
|
||||
this._defaultTimezone,
|
||||
)[0],
|
||||
)
|
||||
.toISOString()
|
||||
|
||||
@@ -6,10 +6,4 @@ export class PrismaService extends PrismaClient implements OnModuleInit {
|
||||
async onModuleInit() {
|
||||
await this.$connect();
|
||||
}
|
||||
|
||||
async enableShutdownHooks(app: INestApplication) {
|
||||
this.$on('beforeExit', async () => {
|
||||
await app.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,5 @@ export class TimezoneFinder implements TimezoneFinderPort {
|
||||
timezones = (
|
||||
lon: number,
|
||||
lat: number,
|
||||
defaultTimezone?: string,
|
||||
): string[] => {
|
||||
const foundTimezones = find(lat, lon);
|
||||
if (defaultTimezone && foundTimezones.length == 0) return [defaultTimezone];
|
||||
return foundTimezones;
|
||||
};
|
||||
): string[] => find(lat, lon);
|
||||
}
|
||||
|
||||
@@ -15,24 +15,29 @@ import { WaypointDto } from './waypoint.dto';
|
||||
import { HasValidPositionIndexes } from './validators/decorators/has-valid-position-indexes.decorator';
|
||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import { IsAfterOrEqual } from './validators/decorators/is-after-or-equal.decorator';
|
||||
import { HasDay } from './validators/decorators/has-day.decorator';
|
||||
import { HasRole } from './validators/decorators/has-role.decorator';
|
||||
import { HasSeats } from './validators/decorators/has-seats.decorator';
|
||||
|
||||
export class CreateAdRequestDto {
|
||||
@IsUUID(4)
|
||||
userId: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
driver?: boolean;
|
||||
@HasRole('passenger', {
|
||||
message: 'At least one of driver or passenger property needs to be truthy'
|
||||
})
|
||||
@HasSeats('seatsProposed', {
|
||||
message: 'Number of seats proposed as a driver is required'
|
||||
})
|
||||
driver: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
passenger?: boolean;
|
||||
@HasSeats('seatsRequested', {
|
||||
message: 'Number of seats requested as a passenger is required'
|
||||
})
|
||||
passenger: boolean;
|
||||
|
||||
@IsEnum(Frequency)
|
||||
@HasDay('schedule', {
|
||||
message: 'At least a day is required for a recurrent ad',
|
||||
})
|
||||
frequency: Frequency;
|
||||
|
||||
@IsISO8601({
|
||||
@@ -56,17 +61,16 @@ export class CreateAdRequestDto {
|
||||
@ValidateNested({ each: true })
|
||||
schedule: ScheduleItemDto[];
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
seatsProposed?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
seatsRequested?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
strict?: boolean;
|
||||
strict: boolean;
|
||||
|
||||
@Type(() => WaypointDto)
|
||||
@IsArray()
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import { IsOptional, IsMilitaryTime, IsInt, Min, Max } from 'class-validator';
|
||||
|
||||
export class ScheduleItemDto {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
@Max(6)
|
||||
day?: number;
|
||||
day: number;
|
||||
|
||||
@IsMilitaryTime()
|
||||
time: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
margin?: number;
|
||||
margin: number;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import {
|
||||
registerDecorator,
|
||||
ValidationOptions,
|
||||
ValidationArguments,
|
||||
} from 'class-validator';
|
||||
|
||||
export function HasDay(
|
||||
export function HasRole(
|
||||
property: string,
|
||||
validationOptions?: ValidationOptions,
|
||||
) {
|
||||
return function (object: object, propertyName: string) {
|
||||
registerDecorator({
|
||||
name: 'hasDay',
|
||||
name: 'hasRole',
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
constraints: [property],
|
||||
@@ -20,13 +19,7 @@ export function HasDay(
|
||||
validate(value: any, args: ValidationArguments) {
|
||||
const [relatedPropertyName] = args.constraints;
|
||||
const relatedValue = (args.object as any)[relatedPropertyName];
|
||||
return (
|
||||
value == Frequency.PUNCTUAL ||
|
||||
(Array.isArray(relatedValue) &&
|
||||
relatedValue.some((scheduleItem) =>
|
||||
scheduleItem.hasOwnProperty('day'),
|
||||
))
|
||||
);
|
||||
return value || relatedValue;
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import {
|
||||
registerDecorator,
|
||||
ValidationOptions,
|
||||
ValidationArguments,
|
||||
} from 'class-validator';
|
||||
|
||||
export function HasSeats(
|
||||
property: string,
|
||||
validationOptions?: ValidationOptions,
|
||||
) {
|
||||
return function (object: object, propertyName: string) {
|
||||
registerDecorator({
|
||||
name: 'hasSeats',
|
||||
target: object.constructor,
|
||||
propertyName: propertyName,
|
||||
constraints: [property],
|
||||
options: validationOptions,
|
||||
validator: {
|
||||
validate(value: any, args: ValidationArguments) {
|
||||
const [relatedPropertyName] = args.constraints;
|
||||
const relatedValue = (args.object as any)[relatedPropertyName];
|
||||
return (value && relatedValue>0) || !value;
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -1,17 +1,10 @@
|
||||
import { WaypointDto } from '../waypoint.dto';
|
||||
|
||||
export const hasValidPositionIndexes = (waypoints: WaypointDto[]): boolean => {
|
||||
if (!waypoints) return false;
|
||||
if (waypoints.length == 0) return false;
|
||||
if (waypoints.every((waypoint) => waypoint.position === undefined))
|
||||
return false;
|
||||
if (waypoints.every((waypoint) => typeof waypoint.position === 'number')) {
|
||||
const positions = Array.from(waypoints, (waypoint) => waypoint.position);
|
||||
positions.sort();
|
||||
for (let i = 1; i < positions.length; i++)
|
||||
if (positions[i] != positions[i - 1] + 1) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
const positions = Array.from(waypoints, (waypoint) => waypoint.position);
|
||||
positions.sort();
|
||||
for (let i = 1; i < positions.length; i++)
|
||||
if (positions[i] != positions[i - 1] + 1) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@ import { IsInt, IsOptional } from 'class-validator';
|
||||
import { AddressDto } from './address.dto';
|
||||
|
||||
export class WaypointDto extends AddressDto {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
position?: number;
|
||||
position: number;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import { AdMapper } from '@modules/ad/ad.mapper';
|
||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||
import {
|
||||
CreateAdProps,
|
||||
DefaultAdProps,
|
||||
Frequency,
|
||||
} from '@modules/ad/core/domain/ad.types';
|
||||
import { AdRepository } from '@modules/ad/infrastructure/ad.repository';
|
||||
@@ -55,7 +54,7 @@ describe('Ad Repository', () => {
|
||||
driver: 'true',
|
||||
passenger: 'false',
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 0,
|
||||
seatsRequested: 1,
|
||||
strict: 'false',
|
||||
};
|
||||
const punctualAd = {
|
||||
@@ -231,18 +230,8 @@ describe('Ad Repository', () => {
|
||||
],
|
||||
};
|
||||
|
||||
const defaultAdProps: DefaultAdProps = {
|
||||
driver: false,
|
||||
passenger: true,
|
||||
marginDuration: 900,
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 1,
|
||||
strict: false,
|
||||
};
|
||||
|
||||
const adToCreate: AdEntity = AdEntity.create(
|
||||
createAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
await adRepository.insert(adToCreate);
|
||||
|
||||
@@ -319,18 +308,8 @@ describe('Ad Repository', () => {
|
||||
],
|
||||
};
|
||||
|
||||
const defaultAdProps: DefaultAdProps = {
|
||||
driver: false,
|
||||
passenger: true,
|
||||
marginDuration: 900,
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 1,
|
||||
strict: false,
|
||||
};
|
||||
|
||||
const adToCreate: AdEntity = AdEntity.create(
|
||||
createAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
await adRepository.insert(adToCreate);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||
import {
|
||||
CreateAdProps,
|
||||
DefaultAdProps,
|
||||
Frequency,
|
||||
} from '@modules/ad/core/domain/ad.types';
|
||||
import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
|
||||
@@ -47,6 +46,7 @@ const punctualCreateAdProps = {
|
||||
{
|
||||
day: 3,
|
||||
time: '08:30',
|
||||
margin: 600,
|
||||
},
|
||||
],
|
||||
frequency: Frequency.PUNCTUAL,
|
||||
@@ -119,21 +119,12 @@ const recurrentDriverPassengerCreateAdProps: CreateAdProps = {
|
||||
driver: true,
|
||||
passenger: true,
|
||||
};
|
||||
const defaultAdProps: DefaultAdProps = {
|
||||
driver: false,
|
||||
passenger: true,
|
||||
marginDuration: 900,
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 1,
|
||||
strict: false,
|
||||
};
|
||||
|
||||
describe('Ad entity create', () => {
|
||||
describe('With complete props', () => {
|
||||
it('should create a new punctual passenger ad entity', async () => {
|
||||
const punctualPassengerAd: AdEntity = AdEntity.create(
|
||||
punctualPassengerCreateAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
expect(punctualPassengerAd.id.length).toBe(36);
|
||||
expect(punctualPassengerAd.getProps().schedule.length).toBe(1);
|
||||
@@ -145,7 +136,6 @@ describe('Ad entity create', () => {
|
||||
it('should create a new punctual driver ad entity', async () => {
|
||||
const punctualDriverAd: AdEntity = AdEntity.create(
|
||||
punctualDriverCreateAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
expect(punctualDriverAd.id.length).toBe(36);
|
||||
expect(punctualDriverAd.getProps().schedule.length).toBe(1);
|
||||
@@ -157,7 +147,6 @@ describe('Ad entity create', () => {
|
||||
it('should create a new punctual driver and passenger ad entity', async () => {
|
||||
const punctualDriverPassengerAd: AdEntity = AdEntity.create(
|
||||
punctualDriverPassengerCreateAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
expect(punctualDriverPassengerAd.id.length).toBe(36);
|
||||
expect(punctualDriverPassengerAd.getProps().schedule.length).toBe(1);
|
||||
@@ -171,7 +160,6 @@ describe('Ad entity create', () => {
|
||||
it('should create a new recurrent passenger ad entity', async () => {
|
||||
const recurrentPassengerAd: AdEntity = AdEntity.create(
|
||||
recurrentPassengerCreateAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
expect(recurrentPassengerAd.id.length).toBe(36);
|
||||
expect(recurrentPassengerAd.getProps().schedule.length).toBe(5);
|
||||
@@ -183,7 +171,6 @@ describe('Ad entity create', () => {
|
||||
it('should create a new recurrent driver ad entity', async () => {
|
||||
const recurrentDriverAd: AdEntity = AdEntity.create(
|
||||
recurrentDriverCreateAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
expect(recurrentDriverAd.id.length).toBe(36);
|
||||
expect(recurrentDriverAd.getProps().schedule.length).toBe(5);
|
||||
@@ -195,7 +182,6 @@ describe('Ad entity create', () => {
|
||||
it('should create a new recurrent driver and passenger ad entity', async () => {
|
||||
const recurrentDriverPassengerAd: AdEntity = AdEntity.create(
|
||||
recurrentDriverPassengerCreateAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
expect(recurrentDriverPassengerAd.id.length).toBe(36);
|
||||
expect(recurrentDriverPassengerAd.getProps().schedule.length).toBe(5);
|
||||
@@ -207,177 +193,4 @@ describe('Ad entity create', () => {
|
||||
expect(recurrentDriverPassengerAd.getProps().passenger).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('With incomplete props', () => {
|
||||
it('should create a new punctual passenger ad entity if no role is given', async () => {
|
||||
const punctualWithoutRoleCreateAdProps: CreateAdProps = {
|
||||
...baseCreateAdProps,
|
||||
...punctualCreateAdProps,
|
||||
driver: false,
|
||||
passenger: false,
|
||||
};
|
||||
const punctualWithoutRoleAd: AdEntity = AdEntity.create(
|
||||
punctualWithoutRoleCreateAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
expect(punctualWithoutRoleAd.id.length).toBe(36);
|
||||
expect(punctualWithoutRoleAd.getProps().schedule.length).toBe(1);
|
||||
expect(punctualWithoutRoleAd.getProps().schedule[0].time).toBe('08:30');
|
||||
expect(punctualWithoutRoleAd.getProps().driver).toBeFalsy();
|
||||
expect(punctualWithoutRoleAd.getProps().passenger).toBeTruthy();
|
||||
});
|
||||
it('should create a new strict punctual passenger ad entity if no strict param is given', async () => {
|
||||
const punctualWithoutStrictCreateAdProps: CreateAdProps = {
|
||||
userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36',
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 1,
|
||||
strict: undefined,
|
||||
waypoints: [originWaypointProps, destinationWaypointProps],
|
||||
...punctualCreateAdProps,
|
||||
driver: false,
|
||||
passenger: true,
|
||||
};
|
||||
const punctualWithoutStrictAd: AdEntity = AdEntity.create(
|
||||
punctualWithoutStrictCreateAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
expect(punctualWithoutStrictAd.id.length).toBe(36);
|
||||
expect(punctualWithoutStrictAd.getProps().schedule.length).toBe(1);
|
||||
expect(punctualWithoutStrictAd.getProps().schedule[0].time).toBe('08:30');
|
||||
expect(punctualWithoutStrictAd.getProps().driver).toBeFalsy();
|
||||
expect(punctualWithoutStrictAd.getProps().passenger).toBeTruthy();
|
||||
expect(punctualWithoutStrictAd.getProps().strict).toBeFalsy();
|
||||
});
|
||||
it('should create a new punctual passenger ad entity with seats requested if no seats requested param is given', async () => {
|
||||
const punctualWithoutSeatsRequestedCreateAdProps: CreateAdProps = {
|
||||
userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36',
|
||||
seatsProposed: 3,
|
||||
seatsRequested: undefined,
|
||||
strict: false,
|
||||
waypoints: [originWaypointProps, destinationWaypointProps],
|
||||
...punctualCreateAdProps,
|
||||
driver: false,
|
||||
passenger: true,
|
||||
};
|
||||
const punctualWithoutSeatsRequestedAd: AdEntity = AdEntity.create(
|
||||
punctualWithoutSeatsRequestedCreateAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
expect(punctualWithoutSeatsRequestedAd.id.length).toBe(36);
|
||||
expect(punctualWithoutSeatsRequestedAd.getProps().schedule.length).toBe(
|
||||
1,
|
||||
);
|
||||
expect(punctualWithoutSeatsRequestedAd.getProps().schedule[0].time).toBe(
|
||||
'08:30',
|
||||
);
|
||||
expect(punctualWithoutSeatsRequestedAd.getProps().driver).toBeFalsy();
|
||||
expect(punctualWithoutSeatsRequestedAd.getProps().passenger).toBeTruthy();
|
||||
expect(punctualWithoutSeatsRequestedAd.getProps().seatsRequested).toBe(1);
|
||||
});
|
||||
it('should create a new punctual driver ad entity with seats proposed if no seats proposed param is given', async () => {
|
||||
const punctualWithoutSeatsProposedCreateAdProps: CreateAdProps = {
|
||||
userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36',
|
||||
seatsProposed: undefined,
|
||||
seatsRequested: 1,
|
||||
strict: false,
|
||||
waypoints: [originWaypointProps, destinationWaypointProps],
|
||||
...punctualCreateAdProps,
|
||||
driver: true,
|
||||
passenger: false,
|
||||
};
|
||||
const punctualWithoutSeatsProposedAd: AdEntity = AdEntity.create(
|
||||
punctualWithoutSeatsProposedCreateAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
expect(punctualWithoutSeatsProposedAd.id.length).toBe(36);
|
||||
expect(punctualWithoutSeatsProposedAd.getProps().schedule.length).toBe(1);
|
||||
expect(punctualWithoutSeatsProposedAd.getProps().schedule[0].time).toBe(
|
||||
'08:30',
|
||||
);
|
||||
expect(punctualWithoutSeatsProposedAd.getProps().driver).toBeTruthy();
|
||||
expect(punctualWithoutSeatsProposedAd.getProps().passenger).toBeFalsy();
|
||||
expect(punctualWithoutSeatsProposedAd.getProps().seatsProposed).toBe(3);
|
||||
});
|
||||
it('should create a new punctual driver ad entity with margin durations if margin durations are empty', async () => {
|
||||
const punctualWithoutMarginDurationCreateAdProps: CreateAdProps = {
|
||||
...baseCreateAdProps,
|
||||
waypoints: [originWaypointProps, destinationWaypointProps],
|
||||
...punctualCreateAdProps,
|
||||
driver: true,
|
||||
passenger: false,
|
||||
};
|
||||
const punctualWithoutMarginDurationAd: AdEntity = AdEntity.create(
|
||||
punctualWithoutMarginDurationCreateAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
expect(punctualWithoutMarginDurationAd.id.length).toBe(36);
|
||||
expect(punctualWithoutMarginDurationAd.getProps().schedule.length).toBe(
|
||||
1,
|
||||
);
|
||||
expect(punctualWithoutMarginDurationAd.getProps().schedule[0].time).toBe(
|
||||
'08:30',
|
||||
);
|
||||
expect(
|
||||
punctualWithoutMarginDurationAd.getProps().schedule[0].margin,
|
||||
).toBe(900);
|
||||
expect(punctualWithoutMarginDurationAd.getProps().driver).toBeTruthy();
|
||||
expect(punctualWithoutMarginDurationAd.getProps().passenger).toBeFalsy();
|
||||
});
|
||||
it('should create a new punctual passenger ad entity with valid positions if positions are missing', async () => {
|
||||
const punctualWithoutPositionsCreateAdProps: CreateAdProps = {
|
||||
userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36',
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 1,
|
||||
strict: undefined,
|
||||
waypoints: [
|
||||
{
|
||||
position: undefined,
|
||||
address: {
|
||||
houseNumber: '5',
|
||||
street: 'Avenue Foch',
|
||||
locality: 'Nancy',
|
||||
postalCode: '54000',
|
||||
country: 'France',
|
||||
coordinates: {
|
||||
lat: 48.689445,
|
||||
lon: 6.17651,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
position: undefined,
|
||||
address: {
|
||||
locality: 'Paris',
|
||||
postalCode: '75000',
|
||||
country: 'France',
|
||||
coordinates: {
|
||||
lat: 48.8566,
|
||||
lon: 2.3522,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
...punctualCreateAdProps,
|
||||
driver: false,
|
||||
passenger: false,
|
||||
};
|
||||
const punctualWithoutPositionsAd: AdEntity = AdEntity.create(
|
||||
punctualWithoutPositionsCreateAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
expect(punctualWithoutPositionsAd.id.length).toBe(36);
|
||||
expect(punctualWithoutPositionsAd.getProps().schedule.length).toBe(1);
|
||||
expect(punctualWithoutPositionsAd.getProps().schedule[0].time).toBe(
|
||||
'08:30',
|
||||
);
|
||||
expect(punctualWithoutPositionsAd.getProps().driver).toBeFalsy();
|
||||
expect(punctualWithoutPositionsAd.getProps().passenger).toBeTruthy();
|
||||
expect(punctualWithoutPositionsAd.getProps().waypoints[0].position).toBe(
|
||||
0,
|
||||
);
|
||||
expect(punctualWithoutPositionsAd.getProps().waypoints[1].position).toBe(
|
||||
1,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Test, TestingModule } from '@nestjs/testing';
|
||||
import {
|
||||
AD_REPOSITORY,
|
||||
INPUT_DATETIME_TRANSFORMER,
|
||||
PARAMS_PROVIDER,
|
||||
} from '@modules/ad/ad.di-tokens';
|
||||
import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto';
|
||||
import { CreateAdRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/create-ad.request.dto';
|
||||
@@ -10,7 +9,6 @@ import { AggregateID } from '@mobicoop/ddd-library';
|
||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||
import { ConflictException } from '@mobicoop/ddd-library';
|
||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import { DefaultParamsProviderPort } from '@modules/ad/core/application/ports/default-params-provider.port';
|
||||
import { CreateAdService } from '@modules/ad/core/application/commands/create-ad/create-ad.service';
|
||||
import { CreateAdCommand } from '@modules/ad/core/application/commands/create-ad/create-ad.command';
|
||||
import { AdAlreadyExistsException } from '@modules/ad/core/domain/ad.errors';
|
||||
@@ -41,11 +39,15 @@ const punctualCreateAdRequest: CreateAdRequestDto = {
|
||||
schedule: [
|
||||
{
|
||||
time: '08:15',
|
||||
margin: 900,
|
||||
day: 4,
|
||||
},
|
||||
],
|
||||
driver: true,
|
||||
passenger: true,
|
||||
seatsRequested: 1,
|
||||
seatsProposed: 3,
|
||||
strict: false,
|
||||
frequency: Frequency.PUNCTUAL,
|
||||
waypoints: [originWaypoint, destinationWaypoint],
|
||||
};
|
||||
@@ -62,20 +64,6 @@ const mockAdRepository = {
|
||||
}),
|
||||
};
|
||||
|
||||
const mockDefaultParamsProvider: DefaultParamsProviderPort = {
|
||||
getParams: () => {
|
||||
return {
|
||||
DEPARTURE_TIME_MARGIN: 900,
|
||||
DRIVER: false,
|
||||
SEATS_PROPOSED: 3,
|
||||
PASSENGER: true,
|
||||
SEATS_REQUESTED: 1,
|
||||
STRICT: false,
|
||||
TIMEZONE: 'Europe/Paris',
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const mockInputDateTimeTransformer: DateTimeTransformerPort = {
|
||||
fromDate: jest.fn(),
|
||||
toDate: jest.fn(),
|
||||
@@ -93,10 +81,6 @@ describe('create-ad.service', () => {
|
||||
provide: AD_REPOSITORY,
|
||||
useValue: mockAdRepository,
|
||||
},
|
||||
{
|
||||
provide: PARAMS_PROVIDER,
|
||||
useValue: mockDefaultParamsProvider,
|
||||
},
|
||||
{
|
||||
provide: INPUT_DATETIME_TRANSFORMER,
|
||||
useValue: mockInputDateTimeTransformer,
|
||||
|
||||
@@ -2,7 +2,6 @@ import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
|
||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||
import {
|
||||
CreateAdProps,
|
||||
DefaultAdProps,
|
||||
Frequency,
|
||||
} from '@modules/ad/core/domain/ad.types';
|
||||
import { FindAdByIdQuery } from '@modules/ad/core/application/queries/find-ad-by-id/find-ad-by-id.query';
|
||||
@@ -60,18 +59,8 @@ const punctualPassengerCreateAdProps: CreateAdProps = {
|
||||
passenger: true,
|
||||
};
|
||||
|
||||
const defaultAdProps: DefaultAdProps = {
|
||||
marginDuration: 900,
|
||||
driver: false,
|
||||
passenger: true,
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 1,
|
||||
strict: false,
|
||||
};
|
||||
|
||||
const ad: AdEntity = AdEntity.create(
|
||||
punctualPassengerCreateAdProps,
|
||||
defaultAdProps,
|
||||
);
|
||||
|
||||
const mockAdRepository = {
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import { DefaultParams } from '@modules/ad/core/application/ports/default-params.type';
|
||||
import { DefaultParamsProvider } from '@modules/ad/infrastructure/default-params-provider';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
const mockConfigService = {
|
||||
get: jest.fn().mockImplementation((value: string) => {
|
||||
switch (value) {
|
||||
case 'DEPARTURE_TIME_MARGIN':
|
||||
return 900;
|
||||
case 'ROLE':
|
||||
return 'passenger';
|
||||
case 'SEATS_PROPOSED':
|
||||
return 3;
|
||||
case 'SEATS_REQUESTED':
|
||||
return 1;
|
||||
case 'STRICT_FREQUENCY':
|
||||
return 'false';
|
||||
case 'DEFAULT_TIMEZONE':
|
||||
return 'Europe/Paris';
|
||||
default:
|
||||
return 'some_default_value';
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
||||
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.DEPARTURE_TIME_MARGIN).toBe(900);
|
||||
expect(params.PASSENGER).toBeTruthy();
|
||||
expect(params.DRIVER).toBeFalsy();
|
||||
expect(params.TIMEZONE).toBe('Europe/Paris');
|
||||
});
|
||||
});
|
||||
@@ -1,29 +1,13 @@
|
||||
import {
|
||||
PARAMS_PROVIDER,
|
||||
TIMEZONE_FINDER,
|
||||
TIME_CONVERTER,
|
||||
} from '@modules/ad/ad.di-tokens';
|
||||
import { Frequency } from '@modules/ad/core/application/ports/datetime-transformer.port';
|
||||
import { DefaultParamsProviderPort } from '@modules/ad/core/application/ports/default-params-provider.port';
|
||||
import { TimeConverterPort } from '@modules/ad/core/application/ports/time-converter.port';
|
||||
import { TimezoneFinderPort } from '@modules/ad/core/application/ports/timezone-finder.port';
|
||||
import { InputDateTimeTransformer } from '@modules/ad/infrastructure/input-datetime-transformer';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
const mockDefaultParamsProvider: DefaultParamsProviderPort = {
|
||||
getParams: () => {
|
||||
return {
|
||||
DEPARTURE_TIME_MARGIN: 900,
|
||||
DRIVER: false,
|
||||
SEATS_PROPOSED: 3,
|
||||
PASSENGER: true,
|
||||
SEATS_REQUESTED: 1,
|
||||
STRICT: false,
|
||||
TIMEZONE: 'Europe/Paris',
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const mockTimezoneFinder: TimezoneFinderPort = {
|
||||
timezones: jest.fn().mockImplementation(() => ['Europe/Paris']),
|
||||
};
|
||||
@@ -56,10 +40,6 @@ describe('Input Datetime Transformer', () => {
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
{
|
||||
provide: PARAMS_PROVIDER,
|
||||
useValue: mockDefaultParamsProvider,
|
||||
},
|
||||
{
|
||||
provide: TIMEZONE_FINDER,
|
||||
useValue: mockTimezoneFinder,
|
||||
|
||||
@@ -1,29 +1,13 @@
|
||||
import {
|
||||
PARAMS_PROVIDER,
|
||||
TIMEZONE_FINDER,
|
||||
TIME_CONVERTER,
|
||||
} from '@modules/ad/ad.di-tokens';
|
||||
import { Frequency } from '@modules/ad/core/application/ports/datetime-transformer.port';
|
||||
import { DefaultParamsProviderPort } from '@modules/ad/core/application/ports/default-params-provider.port';
|
||||
import { TimeConverterPort } from '@modules/ad/core/application/ports/time-converter.port';
|
||||
import { TimezoneFinderPort } from '@modules/ad/core/application/ports/timezone-finder.port';
|
||||
import { OutputDateTimeTransformer } from '@modules/ad/infrastructure/output-datetime-transformer';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
const mockDefaultParamsProvider: DefaultParamsProviderPort = {
|
||||
getParams: () => {
|
||||
return {
|
||||
DEPARTURE_TIME_MARGIN: 900,
|
||||
DRIVER: false,
|
||||
SEATS_PROPOSED: 3,
|
||||
PASSENGER: true,
|
||||
SEATS_REQUESTED: 1,
|
||||
STRICT: false,
|
||||
TIMEZONE: 'Europe/Paris',
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const mockTimezoneFinder: TimezoneFinderPort = {
|
||||
timezones: jest.fn().mockImplementation(() => ['Europe/Paris']),
|
||||
};
|
||||
@@ -56,10 +40,6 @@ describe('Output Datetime Transformer', () => {
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
{
|
||||
provide: PARAMS_PROVIDER,
|
||||
useValue: mockDefaultParamsProvider,
|
||||
},
|
||||
{
|
||||
provide: TIMEZONE_FINDER,
|
||||
useValue: mockTimezoneFinder,
|
||||
|
||||
@@ -34,11 +34,15 @@ const punctualCreateAdRequest: CreateAdRequestDto = {
|
||||
schedule: [
|
||||
{
|
||||
time: '08:15',
|
||||
day: 4,
|
||||
margin: 600,
|
||||
},
|
||||
],
|
||||
driver: false,
|
||||
passenger: true,
|
||||
seatsRequested: 1,
|
||||
seatsProposed: 3,
|
||||
strict: false,
|
||||
frequency: Frequency.PUNCTUAL,
|
||||
waypoints: [originWaypoint, destinationWaypoint],
|
||||
};
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import { ScheduleItemDto } from '@modules/ad/interface/grpc-controllers/dtos/schedule-item.dto';
|
||||
import { HasDay } from '@modules/ad/interface/grpc-controllers/dtos/validators/decorators/has-day.decorator';
|
||||
import { Validator } from 'class-validator';
|
||||
|
||||
describe('Has day decorator', () => {
|
||||
class MyClass {
|
||||
@HasDay('schedule', {
|
||||
message: 'At least a day is required for a recurrent ad',
|
||||
})
|
||||
frequency: Frequency;
|
||||
|
||||
schedule: ScheduleItemDto[];
|
||||
}
|
||||
|
||||
it('should return a property decorator has a function', () => {
|
||||
const hasDay = HasDay('someProperty');
|
||||
expect(typeof hasDay).toBe('function');
|
||||
});
|
||||
|
||||
it('should validate a punctual frequency associated with a valid schedule', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.frequency = Frequency.PUNCTUAL;
|
||||
myClassInstance.schedule = [
|
||||
{
|
||||
time: '07:15',
|
||||
},
|
||||
];
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should validate a recurrent frequency associated with a valid schedule', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.frequency = Frequency.RECURRENT;
|
||||
myClassInstance.schedule = [
|
||||
{
|
||||
time: '07:15',
|
||||
day: 1,
|
||||
},
|
||||
];
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should not validate a recurrent frequency associated with an invalid schedule', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.frequency = Frequency.RECURRENT;
|
||||
myClassInstance.schedule = [
|
||||
{
|
||||
time: '07:15',
|
||||
},
|
||||
];
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
import { HasRole } from '@modules/ad/interface/grpc-controllers/dtos/validators/decorators/has-role.decorator';
|
||||
import { Validator } from 'class-validator';
|
||||
|
||||
describe('has role decorator', () => {
|
||||
class MyClass {
|
||||
@HasRole('passenger')
|
||||
driver: boolean;
|
||||
|
||||
passenger: boolean;
|
||||
}
|
||||
it('should return a property decorator has a function', () => {
|
||||
const hasRole = HasRole('property');
|
||||
expect(typeof hasRole).toBe('function');
|
||||
});
|
||||
it('should validate an instance with driver only set to true', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.driver = true;
|
||||
myClassInstance.passenger = false;
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(0);
|
||||
});
|
||||
it('should validate an instance with passenger only set to true', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.driver = false;
|
||||
myClassInstance.passenger = true;
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(0);
|
||||
});
|
||||
it('should validate an instance with driver and passenger set to true', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.driver = true;
|
||||
myClassInstance.passenger = true;
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(0);
|
||||
});
|
||||
it('should not validate an instance without driver and passenger set to true', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.driver = false;
|
||||
myClassInstance.passenger = false;
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
import { HasSeats } from '@modules/ad/interface/grpc-controllers/dtos/validators/decorators/has-seats.decorator';
|
||||
import { Validator } from 'class-validator';
|
||||
|
||||
describe('has seats decorator', () => {
|
||||
class MyClass {
|
||||
@HasSeats('seatsProposed')
|
||||
driver:boolean;
|
||||
@HasSeats('seatsRequested')
|
||||
passenger: boolean;
|
||||
|
||||
seatsProposed?: number;
|
||||
seatsRequested?: number;
|
||||
}
|
||||
it('should return a property decorator has a function', () => {
|
||||
const hasSeats = HasSeats('property');
|
||||
expect(typeof hasSeats).toBe('function');
|
||||
});
|
||||
it('should validate an instance with seats proposed as driver', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.driver = true;
|
||||
myClassInstance.seatsProposed = 3;
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(0);
|
||||
});
|
||||
it('should validate an instance with seats requested as passenger', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.passenger = true;
|
||||
myClassInstance.seatsRequested = 1;
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(0);
|
||||
});
|
||||
it('should validate an instance with seats proposed as driver and passenger', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.driver = true;
|
||||
myClassInstance.seatsProposed = 3;
|
||||
myClassInstance.passenger = true;
|
||||
myClassInstance.seatsRequested = 1;
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(0);
|
||||
});
|
||||
it('should not validate an instance without seats proposed as driver', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.driver = true;
|
||||
myClassInstance.seatsProposed = 0;
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(1);
|
||||
});
|
||||
it('should not validate an instance without seats requested as passenger', async () => {
|
||||
const myClassInstance = new MyClass();
|
||||
myClassInstance.passenger = true;
|
||||
const validator = new Validator();
|
||||
const validation = await validator.validate(myClassInstance);
|
||||
expect(validation.length).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,9 @@
|
||||
import { hasValidPositionIndexes } from '@modules/ad/interface/grpc-controllers/dtos/validators/has-valid-position-indexes.validator';
|
||||
import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto';
|
||||
import { hasValidPositionIndexes } from "@modules/ad/interface/grpc-controllers/dtos/validators/has-valid-position-indexes.validator";
|
||||
import { WaypointDto } from "@modules/ad/interface/grpc-controllers/dtos/waypoint.dto";
|
||||
|
||||
describe('addresses position validator', () => {
|
||||
const mockAddress1: WaypointDto = {
|
||||
const waypoint1: WaypointDto = {
|
||||
position: 0,
|
||||
lat: 48.689445,
|
||||
lon: 6.17651,
|
||||
houseNumber: '5',
|
||||
@@ -11,14 +12,16 @@ describe('addresses position validator', () => {
|
||||
postalCode: '54000',
|
||||
country: 'France',
|
||||
};
|
||||
const mockAddress2: WaypointDto = {
|
||||
const waypoint2: WaypointDto = {
|
||||
position: 1,
|
||||
lat: 48.8566,
|
||||
lon: 2.3522,
|
||||
locality: 'Paris',
|
||||
postalCode: '75000',
|
||||
country: 'France',
|
||||
};
|
||||
const mockAddress3: WaypointDto = {
|
||||
const waypoint3: WaypointDto = {
|
||||
position: 2,
|
||||
lon: 49.2628,
|
||||
lat: 4.0347,
|
||||
locality: 'Reims',
|
||||
@@ -26,50 +29,26 @@ describe('addresses position validator', () => {
|
||||
country: 'France',
|
||||
};
|
||||
|
||||
it('should not validate if no position is defined', () => {
|
||||
expect(
|
||||
hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
|
||||
).toBeFalsy();
|
||||
});
|
||||
it('should not validate if only one position is defined', () => {
|
||||
mockAddress1.position = 0;
|
||||
expect(
|
||||
hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
|
||||
).toBeFalsy();
|
||||
});
|
||||
it('should not validate if positions are partially defined', () => {
|
||||
mockAddress1.position = 0;
|
||||
mockAddress2.position = null;
|
||||
mockAddress3.position = undefined;
|
||||
expect(
|
||||
hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should not validate if multiple positions have same value', () => {
|
||||
mockAddress1.position = 0;
|
||||
mockAddress2.position = 1;
|
||||
mockAddress3.position = 1;
|
||||
expect(
|
||||
hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
|
||||
hasValidPositionIndexes([waypoint1, waypoint1, waypoint2]),
|
||||
).toBeFalsy();
|
||||
});
|
||||
it('should validate if all positions are ordered', () => {
|
||||
mockAddress1.position = 0;
|
||||
mockAddress2.position = 1;
|
||||
mockAddress3.position = 2;
|
||||
expect(
|
||||
hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
|
||||
).toBeTruthy();
|
||||
mockAddress1.position = 1;
|
||||
mockAddress2.position = 2;
|
||||
mockAddress3.position = 3;
|
||||
expect(
|
||||
hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]),
|
||||
).toBeTruthy();
|
||||
it('should not validate if positions are not consecutives', () => {
|
||||
expect(hasValidPositionIndexes([waypoint1, waypoint3])).toBeFalsy();
|
||||
});
|
||||
it('should not validate if no waypoints are defined', () => {
|
||||
expect(hasValidPositionIndexes(undefined)).toBeFalsy();
|
||||
it('should not validate if waypoints are empty', () => {
|
||||
expect(hasValidPositionIndexes([])).toBeFalsy();
|
||||
});
|
||||
it('should validate if all positions are ordered', () => {
|
||||
expect(
|
||||
hasValidPositionIndexes([waypoint1, waypoint2, waypoint3]),
|
||||
).toBeTruthy();
|
||||
waypoint1.position = 1;
|
||||
waypoint2.position = 2;
|
||||
waypoint3.position = 3;
|
||||
expect(
|
||||
hasValidPositionIndexes([waypoint1, waypoint2, waypoint3]),
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { Module, Provider } from '@nestjs/common';
|
||||
import { MESSAGE_PUBLISHER } from './messager.di-tokens';
|
||||
import {
|
||||
MessageBrokerModule,
|
||||
MessageBrokerModuleOptions,
|
||||
MessageBrokerPublisher,
|
||||
} from '@mobicoop/message-broker-module';
|
||||
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { SERVICE_NAME } from '@src/app.constants';
|
||||
import { MessageBrokerModule, MessageBrokerModuleOptions, MessageBrokerPublisher } from '@mobicoop/message-broker-module';
|
||||
|
||||
const imports = [
|
||||
MessageBrokerModule.forRootAsync({
|
||||
@@ -15,8 +12,11 @@ const imports = [
|
||||
useFactory: async (
|
||||
configService: ConfigService,
|
||||
): Promise<MessageBrokerModuleOptions> => ({
|
||||
uri: configService.get<string>('MESSAGE_BROKER_URI'),
|
||||
exchange: configService.get<string>('MESSAGE_BROKER_EXCHANGE'),
|
||||
uri: configService.get<string>('MESSAGE_BROKER_URI') as string,
|
||||
exchange: {
|
||||
name: configService.get<string>('MESSAGE_BROKER_EXCHANGE') as string,
|
||||
durable: configService.get<boolean>('MESSAGE_BROKER_EXCHANGE_DURABILITY') as boolean
|
||||
},
|
||||
name: SERVICE_NAME,
|
||||
}),
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user