mirror of
https://gitlab.com/mobicoop/v3/service/matcher.git
synced 2026-01-01 04:12:40 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d48d01f051 | ||
|
|
1701fbbeb1 | ||
|
|
a7c281d740 | ||
|
|
01ebac7e74 |
@@ -15,6 +15,9 @@ export const MATCHER_AD_CREATION_FAILED_ROUTING_KEY =
|
||||
export const AD_CREATED_MESSAGE_HANDLER = 'adCreated';
|
||||
export const AD_CREATED_ROUTING_KEY = 'ad.created';
|
||||
export const AD_CREATED_QUEUE = 'matcher.ad.created';
|
||||
export const AD_DELETED_MESSAGE_HANDLER = 'adDeleted';
|
||||
export const AD_DELETED_ROUTING_KEY = 'ad.deleted';
|
||||
export const AD_DELETED_QUEUE = 'matcher.ad.deleted';
|
||||
|
||||
// health
|
||||
export const GRPC_HEALTH_PACKAGE_NAME = 'health';
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { ExtendedMapper } from '@mobicoop/ddd-library';
|
||||
import { DirectionEncoderPort } from '@modules/geography/core/application/ports/direction-encoder.port';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { AdEntity } from './core/domain/ad.entity';
|
||||
import {
|
||||
AdWriteModel,
|
||||
AdReadModel,
|
||||
ScheduleItemModel,
|
||||
AdWriteExtraModel,
|
||||
} from './infrastructure/ad.repository';
|
||||
import { v4 } from 'uuid';
|
||||
import { AD_DIRECTION_ENCODER } from './ad.di-tokens';
|
||||
import { AdEntity } from './core/domain/ad.entity';
|
||||
import {
|
||||
ScheduleItem,
|
||||
ScheduleItemProps,
|
||||
} from './core/domain/value-objects/schedule-item.value-object';
|
||||
import { DirectionEncoderPort } from '@modules/geography/core/application/ports/direction-encoder.port';
|
||||
import { AD_DIRECTION_ENCODER } from './ad.di-tokens';
|
||||
import { ExtendedMapper } from '@mobicoop/ddd-library';
|
||||
import {
|
||||
AdReadModel,
|
||||
AdWriteExtraModel,
|
||||
AdWriteModel,
|
||||
ScheduleItemModel,
|
||||
} from './infrastructure/ad.repository';
|
||||
|
||||
/**
|
||||
* Mapper constructs objects that are used in different layers:
|
||||
@@ -97,7 +97,7 @@ export class AdMapper
|
||||
frequency: record.frequency,
|
||||
fromDate: record.fromDate.toISOString().split('T')[0],
|
||||
toDate: record.toDate.toISOString().split('T')[0],
|
||||
schedule: record.schedule.map(
|
||||
schedule: record.schedule?.map(
|
||||
(scheduleItem: ScheduleItemModel) =>
|
||||
new ScheduleItem({
|
||||
day: scheduleItem.day,
|
||||
@@ -111,12 +111,14 @@ export class AdMapper
|
||||
margin: scheduleItem.margin,
|
||||
}),
|
||||
),
|
||||
waypoints: this.directionEncoder
|
||||
.decode(record.waypoints)
|
||||
.map((coordinates, index) => ({
|
||||
position: index,
|
||||
...coordinates,
|
||||
})),
|
||||
waypoints: record.waypoints
|
||||
? this.directionEncoder
|
||||
.decode(record.waypoints)
|
||||
.map((coordinates, index) => ({
|
||||
position: index,
|
||||
...coordinates,
|
||||
}))
|
||||
: [],
|
||||
fwdAzimuth: record.fwdAzimuth,
|
||||
backAzimuth: record.backAzimuth,
|
||||
points: [],
|
||||
|
||||
@@ -1,49 +1,51 @@
|
||||
import { Module, Provider } from '@nestjs/common';
|
||||
import { CqrsModule } from '@nestjs/cqrs';
|
||||
import {
|
||||
AD_MESSAGE_PUBLISHER,
|
||||
AD_REPOSITORY,
|
||||
AD_DIRECTION_ENCODER,
|
||||
AD_ROUTE_PROVIDER,
|
||||
TIMEZONE_FINDER,
|
||||
TIME_CONVERTER,
|
||||
INPUT_DATETIME_TRANSFORMER,
|
||||
OUTPUT_DATETIME_TRANSFORMER,
|
||||
MATCHING_REPOSITORY,
|
||||
AD_CONFIGURATION_REPOSITORY,
|
||||
GEOGRAPHY_PACKAGE,
|
||||
} from './ad.di-tokens';
|
||||
import { ConfigurationRepository } from '@mobicoop/configuration-module';
|
||||
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
|
||||
import { AdRepository } from './infrastructure/ad.repository';
|
||||
import { PrismaService } from './infrastructure/prisma.service';
|
||||
import { AdMapper } from './ad.mapper';
|
||||
import { AdCreatedMessageHandler } from './interface/message-handlers/ad-created.message-handler';
|
||||
import { PostgresDirectionEncoder } from '@modules/geography/infrastructure/postgres-direction-encoder';
|
||||
import { GeographyModule } from '@modules/geography/geography.module';
|
||||
import { CreateAdService } from './core/application/commands/create-ad/create-ad.service';
|
||||
import { MatchGrpcController } from './interface/grpc-controllers/match.grpc-controller';
|
||||
import { MatchQueryHandler } from './core/application/queries/match/match.query-handler';
|
||||
import { TimezoneFinder } from './infrastructure/timezone-finder';
|
||||
import { TimeConverter } from './infrastructure/time-converter';
|
||||
import { InputDateTimeTransformer } from './infrastructure/input-datetime-transformer';
|
||||
import { MatchMapper } from './match.mapper';
|
||||
import { OutputDateTimeTransformer } from './infrastructure/output-datetime-transformer';
|
||||
import { MatchingRepository } from './infrastructure/matching.repository';
|
||||
import { MatchingMapper } from './matching.mapper';
|
||||
import { PostgresDirectionEncoder } from '@modules/geography/infrastructure/postgres-direction-encoder';
|
||||
import { CacheModule } from '@nestjs/cache-manager';
|
||||
import { Module, Provider } from '@nestjs/common';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import { redisStore } from 'cache-manager-ioredis-yet';
|
||||
import { CqrsModule } from '@nestjs/cqrs';
|
||||
import { ClientsModule, Transport } from '@nestjs/microservices';
|
||||
import {
|
||||
RedisClientOptions,
|
||||
RedisModule,
|
||||
RedisModuleOptions,
|
||||
} from '@songkeys/nestjs-redis';
|
||||
import { ConfigurationRepository } from '@mobicoop/configuration-module';
|
||||
import { PublishMessageWhenMatcherAdIsCreatedDomainEventHandler } from './core/application/event-handlers/publish-message-when-matcher-ad-is-created.domain-event-handler';
|
||||
import { Georouter } from './infrastructure/georouter';
|
||||
import { ClientsModule, Transport } from '@nestjs/microservices';
|
||||
import { GRPC_GEOGRAPHY_PACKAGE_NAME } from '@src/app.constants';
|
||||
import { redisStore } from 'cache-manager-ioredis-yet';
|
||||
import { join } from 'path';
|
||||
import {
|
||||
AD_CONFIGURATION_REPOSITORY,
|
||||
AD_DIRECTION_ENCODER,
|
||||
AD_MESSAGE_PUBLISHER,
|
||||
AD_REPOSITORY,
|
||||
AD_ROUTE_PROVIDER,
|
||||
GEOGRAPHY_PACKAGE,
|
||||
INPUT_DATETIME_TRANSFORMER,
|
||||
MATCHING_REPOSITORY,
|
||||
OUTPUT_DATETIME_TRANSFORMER,
|
||||
TIMEZONE_FINDER,
|
||||
TIME_CONVERTER,
|
||||
} from './ad.di-tokens';
|
||||
import { AdMapper } from './ad.mapper';
|
||||
import { CreateAdService } from './core/application/commands/create-ad/create-ad.service';
|
||||
import { DeleteAdService } from './core/application/commands/delete-ad/delete-ad.service';
|
||||
import { PublishMessageWhenMatcherAdIsCreatedDomainEventHandler } from './core/application/event-handlers/publish-message-when-matcher-ad-is-created.domain-event-handler';
|
||||
import { MatchQueryHandler } from './core/application/queries/match/match.query-handler';
|
||||
import { AdRepository } from './infrastructure/ad.repository';
|
||||
import { Georouter } from './infrastructure/georouter';
|
||||
import { InputDateTimeTransformer } from './infrastructure/input-datetime-transformer';
|
||||
import { MatchingRepository } from './infrastructure/matching.repository';
|
||||
import { OutputDateTimeTransformer } from './infrastructure/output-datetime-transformer';
|
||||
import { PrismaService } from './infrastructure/prisma.service';
|
||||
import { TimeConverter } from './infrastructure/time-converter';
|
||||
import { TimezoneFinder } from './infrastructure/timezone-finder';
|
||||
import { MatchGrpcController } from './interface/grpc-controllers/match.grpc-controller';
|
||||
import { AdCreatedMessageHandler } from './interface/message-handlers/ad-created.message-handler';
|
||||
import { AdDeletedMessageHandler } from './interface/message-handlers/ad-deleted.message-handler';
|
||||
import { MatchMapper } from './match.mapper';
|
||||
import { MatchingMapper } from './matching.mapper';
|
||||
|
||||
const imports = [
|
||||
CqrsModule,
|
||||
@@ -96,13 +98,13 @@ const imports = [
|
||||
|
||||
const grpcControllers = [MatchGrpcController];
|
||||
|
||||
const messageHandlers = [AdCreatedMessageHandler];
|
||||
const messageHandlers = [AdCreatedMessageHandler, AdDeletedMessageHandler];
|
||||
|
||||
const eventHandlers: Provider[] = [
|
||||
PublishMessageWhenMatcherAdIsCreatedDomainEventHandler,
|
||||
];
|
||||
|
||||
const commandHandlers: Provider[] = [CreateAdService];
|
||||
const commandHandlers: Provider[] = [CreateAdService, DeleteAdService];
|
||||
|
||||
const queryHandlers: Provider[] = [MatchQueryHandler];
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Command, CommandProps } from '@mobicoop/ddd-library';
|
||||
|
||||
export class DeleteAdCommand extends Command {
|
||||
constructor(props: CommandProps<DeleteAdCommand>) {
|
||||
super(props);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
|
||||
import { Inject } from '@nestjs/common';
|
||||
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||
import { AdRepositoryPort } from '../../ports/ad.repository.port';
|
||||
import { DeleteAdCommand } from './delete-ad.command';
|
||||
|
||||
@CommandHandler(DeleteAdCommand)
|
||||
export class DeleteAdService implements ICommandHandler {
|
||||
constructor(
|
||||
@Inject(AD_REPOSITORY) private readonly adRepository: AdRepositoryPort,
|
||||
) {}
|
||||
|
||||
async execute(command: DeleteAdCommand): Promise<boolean> {
|
||||
const ad = await this.adRepository.findOneById(command.id);
|
||||
ad.delete();
|
||||
return this.adRepository.delete(ad);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
|
||||
import { AggregateID, AggregateRoot } from '@mobicoop/ddd-library';
|
||||
import { AdProps, CreateAdProps } from './ad.types';
|
||||
import { AdDeletedDomainEvent } from './events/ad-delete.domain-event';
|
||||
import { MatcherAdCreatedDomainEvent } from './events/matcher-ad-created.domain-event';
|
||||
|
||||
export class AdEntity extends AggregateRoot<AdProps> {
|
||||
@@ -26,6 +27,14 @@ export class AdEntity extends AggregateRoot<AdProps> {
|
||||
return ad;
|
||||
};
|
||||
|
||||
delete(): void {
|
||||
this.addEvent(
|
||||
new AdDeletedDomainEvent({
|
||||
aggregateId: this.id,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
validate(): void {
|
||||
// entity business rules validation to protect it's invariant before saving entity to a database
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@ import {
|
||||
AggregateID,
|
||||
AggregateRoot,
|
||||
ArgumentInvalidException,
|
||||
ValueObject,
|
||||
} from '@mobicoop/ddd-library';
|
||||
import { Role } from './ad.types';
|
||||
import { CalendarTools } from './calendar-tools.service';
|
||||
import {
|
||||
CandidateProps,
|
||||
CreateCandidateProps,
|
||||
DateInterval,
|
||||
Target,
|
||||
} from './candidate.types';
|
||||
import { ActorTime } from './value-objects/actor-time.value-object';
|
||||
@@ -101,57 +103,13 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
|
||||
* Create the driver schedule based on the passenger schedule
|
||||
*/
|
||||
private _createDriverSchedule = (): void => {
|
||||
let driverSchedule: Array<ScheduleItemProps | undefined> =
|
||||
this.props.passengerSchedule!.map(
|
||||
(scheduleItemProps: ScheduleItemProps) => ({
|
||||
day: scheduleItemProps.day,
|
||||
time: scheduleItemProps.time,
|
||||
margin: scheduleItemProps.margin,
|
||||
}),
|
||||
);
|
||||
// adjust the driver theoretical schedule :
|
||||
// we guess the ideal driver departure time based on the duration to
|
||||
// reach the passenger starting point from the driver starting point
|
||||
driverSchedule = driverSchedule
|
||||
.map((scheduleItemProps: ScheduleItemProps) => {
|
||||
try {
|
||||
const driverDate: Date = CalendarTools.firstDate(
|
||||
scheduleItemProps.day,
|
||||
this.props.dateInterval,
|
||||
);
|
||||
const driverStartDatetime: Date = CalendarTools.datetimeWithSeconds(
|
||||
driverDate,
|
||||
scheduleItemProps.time,
|
||||
-this._passengerStartDuration(),
|
||||
);
|
||||
return <ScheduleItemProps>{
|
||||
day: driverDate.getUTCDay(),
|
||||
margin: scheduleItemProps.margin,
|
||||
time: `${driverStartDatetime
|
||||
.getUTCHours()
|
||||
.toString()
|
||||
.padStart(2, '0')}:${driverStartDatetime
|
||||
.getUTCMinutes()
|
||||
.toString()
|
||||
.padStart(2, '0')}`,
|
||||
};
|
||||
} catch (e) {
|
||||
// no possible driver date or time
|
||||
// TODO : find a test case !
|
||||
return undefined;
|
||||
}
|
||||
})
|
||||
.filter(
|
||||
(scheduleItemProps: ScheduleItemProps | undefined) =>
|
||||
scheduleItemProps !== undefined,
|
||||
);
|
||||
this.props.driverSchedule = driverSchedule.map(
|
||||
(scheduleItemProps: ScheduleItemProps) => ({
|
||||
day: scheduleItemProps.day,
|
||||
time: scheduleItemProps.time,
|
||||
margin: scheduleItemProps.margin,
|
||||
}),
|
||||
const passengerSchedule = new Schedule(
|
||||
this.props.passengerSchedule!,
|
||||
this.props.dateInterval,
|
||||
);
|
||||
this.props.driverSchedule = passengerSchedule
|
||||
.adjust(-this._passengerStartDuration())
|
||||
.unpack().items;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -174,57 +132,14 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
|
||||
* Create the passenger schedule based on the driver schedule
|
||||
*/
|
||||
private _createPassengerSchedule = (): void => {
|
||||
let passengerSchedule: Array<ScheduleItemProps | undefined> =
|
||||
this.props.driverSchedule!.map(
|
||||
(scheduleItemProps: ScheduleItemProps) => ({
|
||||
day: scheduleItemProps.day,
|
||||
time: scheduleItemProps.time,
|
||||
margin: scheduleItemProps.margin,
|
||||
}),
|
||||
);
|
||||
// adjust the passenger theoretical schedule :
|
||||
// we guess the ideal passenger departure time based on the duration to
|
||||
// reach the passenger starting point from the driver starting point
|
||||
passengerSchedule = passengerSchedule
|
||||
.map((scheduleItemProps: ScheduleItemProps) => {
|
||||
try {
|
||||
const passengerDate: Date = CalendarTools.firstDate(
|
||||
scheduleItemProps.day,
|
||||
this.props.dateInterval,
|
||||
);
|
||||
const passengeStartDatetime: Date = CalendarTools.datetimeWithSeconds(
|
||||
passengerDate,
|
||||
scheduleItemProps.time,
|
||||
this._passengerStartDuration(),
|
||||
);
|
||||
return {
|
||||
day: passengerDate.getUTCDay(),
|
||||
margin: scheduleItemProps.margin,
|
||||
time: `${passengeStartDatetime
|
||||
.getUTCHours()
|
||||
.toString()
|
||||
.padStart(2, '0')}:${passengeStartDatetime
|
||||
.getUTCMinutes()
|
||||
.toString()
|
||||
.padStart(2, '0')}`,
|
||||
};
|
||||
} catch (e) {
|
||||
// no possible passenger date or time
|
||||
// TODO : find a test case !
|
||||
return undefined;
|
||||
}
|
||||
})
|
||||
.filter(
|
||||
(scheduleItemProps: ScheduleItemProps | undefined) =>
|
||||
scheduleItemProps !== undefined,
|
||||
);
|
||||
this.props.passengerSchedule = passengerSchedule.map(
|
||||
(scheduleItemProps: ScheduleItemProps) => ({
|
||||
day: scheduleItemProps.day,
|
||||
time: scheduleItemProps.time,
|
||||
margin: scheduleItemProps.margin,
|
||||
}),
|
||||
const driverSchedule = new Schedule(
|
||||
this.props.driverSchedule!,
|
||||
this.props.dateInterval,
|
||||
);
|
||||
|
||||
this.props.passengerSchedule = driverSchedule
|
||||
.adjust(this._passengerStartDuration())
|
||||
.unpack().items;
|
||||
};
|
||||
|
||||
private _createJourney = (driverScheduleItem: ScheduleItem): Journey =>
|
||||
@@ -407,6 +322,60 @@ export class CandidateEntity extends AggregateRoot<CandidateProps> {
|
||||
}
|
||||
}
|
||||
|
||||
//TODO Use this class as part of the CandidateEntity aggregate
|
||||
class Schedule extends ValueObject<{
|
||||
items: ScheduleItemProps[];
|
||||
dateInterval: DateInterval;
|
||||
}> {
|
||||
constructor(items: ScheduleItemProps[], dateInterval: DateInterval) {
|
||||
super({ items, dateInterval });
|
||||
}
|
||||
|
||||
protected validate(): void {}
|
||||
|
||||
/**
|
||||
* Add the given duration to each schedule item
|
||||
* unless the expected new datetime is not possible,
|
||||
* in which case the item is removed from the adjusted schedule
|
||||
* @param duration time increment in seconds (can be negative)
|
||||
* @returns the new adjusted schedule
|
||||
*/
|
||||
adjust(duration: number): Schedule {
|
||||
const newItems = this.props.items.reduce((acc, scheduleItemProps) => {
|
||||
try {
|
||||
const itemDate: Date = CalendarTools.firstDate(
|
||||
scheduleItemProps.day,
|
||||
this.props.dateInterval,
|
||||
);
|
||||
const driverStartDatetime: Date = CalendarTools.datetimeWithSeconds(
|
||||
itemDate,
|
||||
scheduleItemProps.time,
|
||||
duration,
|
||||
);
|
||||
acc.push({
|
||||
day: itemDate.getUTCDay(),
|
||||
margin: scheduleItemProps.margin,
|
||||
time: this._formatTime(driverStartDatetime),
|
||||
});
|
||||
} catch (e) {
|
||||
// no possible driver date or time
|
||||
// TODO : find a test case !
|
||||
}
|
||||
return acc;
|
||||
}, new Array<ScheduleItemProps>());
|
||||
|
||||
return new Schedule(newItems, this.props.dateInterval);
|
||||
}
|
||||
|
||||
private _formatTime(dateTime: Date) {
|
||||
return (
|
||||
dateTime.getUTCHours().toString().padStart(2, '0') +
|
||||
':' +
|
||||
dateTime.getUTCMinutes().toString().padStart(2, '0')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type ScheduleItemRange = {
|
||||
scheduleItem: ScheduleItem;
|
||||
range: Date[];
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { DomainEvent, DomainEventProps } from '@mobicoop/ddd-library';
|
||||
|
||||
export class AdDeletedDomainEvent extends DomainEvent {
|
||||
constructor(props: DomainEventProps<AdDeletedDomainEvent>) {
|
||||
super(props);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { RabbitSubscribe } from '@mobicoop/message-broker-module';
|
||||
import { DeleteAdCommand } from '@modules/ad/core/application/commands/delete-ad/delete-ad.command';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { CommandBus } from '@nestjs/cqrs';
|
||||
import {
|
||||
AD_DELETED_MESSAGE_HANDLER,
|
||||
AD_DELETED_ROUTING_KEY,
|
||||
} from '@src/app.constants';
|
||||
import { AdReference } from './ad.types';
|
||||
|
||||
@Injectable()
|
||||
export class AdDeletedMessageHandler {
|
||||
constructor(private readonly commandBus: CommandBus) {}
|
||||
|
||||
@RabbitSubscribe({
|
||||
name: AD_DELETED_MESSAGE_HANDLER,
|
||||
routingKey: AD_DELETED_ROUTING_KEY,
|
||||
})
|
||||
public async adDeleted(message: string): Promise<void> {
|
||||
try {
|
||||
const deletedAd: AdReference = JSON.parse(message);
|
||||
await this.commandBus.execute(
|
||||
new DeleteAdCommand({
|
||||
id: deletedAd.aggregateId,
|
||||
}),
|
||||
);
|
||||
} catch (error: any) {
|
||||
// do not throw error to acknowledge incoming message
|
||||
// error handling should be done in the command handler, if relevant
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
import { Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
|
||||
export type Ad = {
|
||||
export type AdReference = {
|
||||
aggregateId: string;
|
||||
};
|
||||
|
||||
export type Ad = AdReference & {
|
||||
driver: boolean;
|
||||
passenger: boolean;
|
||||
frequency: Frequency;
|
||||
|
||||
@@ -1,52 +1,17 @@
|
||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||
import { CreateAdProps, Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import { PointProps } from '@modules/ad/core/domain/value-objects/point.value-object';
|
||||
|
||||
const originPointProps: PointProps = {
|
||||
lat: 48.689445,
|
||||
lon: 6.17651,
|
||||
};
|
||||
const destinationPointProps: PointProps = {
|
||||
lat: 48.8566,
|
||||
lon: 2.3522,
|
||||
};
|
||||
|
||||
const createAdProps: CreateAdProps = {
|
||||
id: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36',
|
||||
driver: true,
|
||||
passenger: true,
|
||||
fromDate: '2023-06-21',
|
||||
toDate: '2023-06-21',
|
||||
schedule: [
|
||||
{
|
||||
day: 3,
|
||||
time: '08:30',
|
||||
margin: 900,
|
||||
},
|
||||
],
|
||||
frequency: Frequency.PUNCTUAL,
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 1,
|
||||
strict: false,
|
||||
waypoints: [originPointProps, destinationPointProps],
|
||||
driverDistance: 23000,
|
||||
driverDuration: 900,
|
||||
passengerDistance: 23000,
|
||||
passengerDuration: 900,
|
||||
fwdAzimuth: 283,
|
||||
backAzimuth: 93,
|
||||
points: [],
|
||||
};
|
||||
import { createAdProps } from './ad.fixtures';
|
||||
|
||||
describe('Ad entity create', () => {
|
||||
it('should create a new entity', async () => {
|
||||
const ad: AdEntity = AdEntity.create(createAdProps);
|
||||
expect(ad.id.length).toBe(36);
|
||||
expect(ad.getProps().schedule.length).toBe(1);
|
||||
expect(ad.getProps().schedule[0].day).toBe(3);
|
||||
expect(ad.getProps().schedule[0].time).toBe('08:30');
|
||||
expect(ad.getProps().driver).toBeTruthy();
|
||||
expect(ad.getProps().passenger).toBeTruthy();
|
||||
expect(ad.getProps().driverDistance).toBe(23000);
|
||||
describe('create', () => {
|
||||
it('should create a new entity', async () => {
|
||||
const ad: AdEntity = AdEntity.create(createAdProps());
|
||||
expect(ad.id.length).toBe(36);
|
||||
expect(ad.getProps().schedule.length).toBe(1);
|
||||
expect(ad.getProps().schedule[0].day).toBe(3);
|
||||
expect(ad.getProps().schedule[0].time).toBe('08:30');
|
||||
expect(ad.getProps().driver).toBeTruthy();
|
||||
expect(ad.getProps().passenger).toBeTruthy();
|
||||
expect(ad.getProps().driverDistance).toBe(23000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
40
src/modules/ad/tests/unit/core/ad.fixtures.ts
Normal file
40
src/modules/ad/tests/unit/core/ad.fixtures.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { CreateAdProps, Frequency } from '@modules/ad/core/domain/ad.types';
|
||||
import { PointProps } from '@modules/ad/core/domain/value-objects/point.value-object';
|
||||
|
||||
const originPointProps: PointProps = {
|
||||
lat: 48.689445,
|
||||
lon: 6.17651,
|
||||
};
|
||||
const destinationPointProps: PointProps = {
|
||||
lat: 48.8566,
|
||||
lon: 2.3522,
|
||||
};
|
||||
|
||||
export function createAdProps(): CreateAdProps {
|
||||
return {
|
||||
id: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36',
|
||||
driver: true,
|
||||
passenger: true,
|
||||
fromDate: '2023-06-21',
|
||||
toDate: '2023-06-21',
|
||||
schedule: [
|
||||
{
|
||||
day: 3,
|
||||
time: '08:30',
|
||||
margin: 900,
|
||||
},
|
||||
],
|
||||
frequency: Frequency.PUNCTUAL,
|
||||
seatsProposed: 3,
|
||||
seatsRequested: 1,
|
||||
strict: false,
|
||||
waypoints: [originPointProps, destinationPointProps],
|
||||
driverDistance: 23000,
|
||||
driverDuration: 900,
|
||||
passengerDistance: 23000,
|
||||
passengerDuration: 900,
|
||||
fwdAzimuth: 283,
|
||||
backAzimuth: 93,
|
||||
points: [],
|
||||
};
|
||||
}
|
||||
41
src/modules/ad/tests/unit/core/delete-ad.service.spec.ts
Normal file
41
src/modules/ad/tests/unit/core/delete-ad.service.spec.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
|
||||
import { DeleteAdCommand } from '@modules/ad/core/application/commands/delete-ad/delete-ad.command';
|
||||
import { DeleteAdService } from '@modules/ad/core/application/commands/delete-ad/delete-ad.service';
|
||||
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { createAdProps } from './ad.fixtures';
|
||||
|
||||
const ad: AdEntity = AdEntity.create(createAdProps());
|
||||
const mockAdRepository = {
|
||||
findOneById: jest.fn().mockImplementation(() => ad),
|
||||
delete: jest.fn(),
|
||||
};
|
||||
|
||||
describe('DeleteAdService', () => {
|
||||
let deleteAdService: DeleteAdService;
|
||||
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
{
|
||||
provide: AD_REPOSITORY,
|
||||
useValue: mockAdRepository,
|
||||
},
|
||||
DeleteAdService,
|
||||
],
|
||||
}).compile();
|
||||
|
||||
deleteAdService = module.get<DeleteAdService>(DeleteAdService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(deleteAdService).toBeDefined();
|
||||
});
|
||||
|
||||
it('should execute the delete logic and delete the ad from the repository', async () => {
|
||||
jest.spyOn(ad, 'delete');
|
||||
await deleteAdService.execute(new DeleteAdCommand(ad.id));
|
||||
expect(ad.delete).toHaveBeenCalled();
|
||||
expect(mockAdRepository.delete).toHaveBeenCalledWith(ad);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
import { AdDeletedMessageHandler } from '@modules/ad/interface/message-handlers/ad-deleted.message-handler';
|
||||
import { CommandBus } from '@nestjs/cqrs';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
const adDeletedMessage =
|
||||
'{"aggregateId":"4eb6a6af-ecfd-41c3-9118-473a507014d4"}';
|
||||
|
||||
const mockCommandBus = {
|
||||
execute: jest.fn(),
|
||||
};
|
||||
|
||||
describe('Ad Deleted Message Handler', () => {
|
||||
let adDeletedMessageHandler: AdDeletedMessageHandler;
|
||||
|
||||
beforeAll(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
{
|
||||
provide: CommandBus,
|
||||
useValue: mockCommandBus,
|
||||
},
|
||||
AdDeletedMessageHandler,
|
||||
],
|
||||
}).compile();
|
||||
|
||||
adDeletedMessageHandler = module.get<AdDeletedMessageHandler>(
|
||||
AdDeletedMessageHandler,
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(adDeletedMessageHandler).toBeDefined();
|
||||
});
|
||||
|
||||
it('should call the delete command', async () => {
|
||||
jest.spyOn(mockCommandBus, 'execute');
|
||||
await adDeletedMessageHandler.adDeleted(adDeletedMessage);
|
||||
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -1,17 +1,20 @@
|
||||
import { Module, Provider } from '@nestjs/common';
|
||||
import { MESSAGE_PUBLISHER } from './messager.di-tokens';
|
||||
import {
|
||||
MessageBrokerModule,
|
||||
MessageBrokerModuleOptions,
|
||||
MessageBrokerPublisher,
|
||||
} from '@mobicoop/message-broker-module';
|
||||
import { Module, Provider } from '@nestjs/common';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import {
|
||||
AD_CREATED_MESSAGE_HANDLER,
|
||||
AD_CREATED_QUEUE,
|
||||
AD_CREATED_ROUTING_KEY,
|
||||
AD_DELETED_MESSAGE_HANDLER,
|
||||
AD_DELETED_QUEUE,
|
||||
AD_DELETED_ROUTING_KEY,
|
||||
SERVICE_NAME,
|
||||
} from '@src/app.constants';
|
||||
import { MESSAGE_PUBLISHER } from './messager.di-tokens';
|
||||
|
||||
const imports = [
|
||||
MessageBrokerModule.forRootAsync({
|
||||
@@ -33,6 +36,10 @@ const imports = [
|
||||
routingKey: AD_CREATED_ROUTING_KEY,
|
||||
queue: AD_CREATED_QUEUE,
|
||||
},
|
||||
[AD_DELETED_MESSAGE_HANDLER]: {
|
||||
routingKey: AD_DELETED_ROUTING_KEY,
|
||||
queue: AD_DELETED_QUEUE,
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user