Merge branch 'adStatus' into 'main'

Handle matcher messages, handle ad statuses

See merge request v3/service/ad!31
This commit is contained in:
Sylvain Briat 2023-12-06 15:13:02 +00:00
commit 976a3c3779
31 changed files with 592 additions and 14 deletions

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "@mobicoop/ad",
"version": "2.3.0",
"version": "2.4.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@mobicoop/ad",
"version": "2.3.0",
"version": "2.4.0",
"license": "AGPL",
"dependencies": {
"@grpc/grpc-js": "^1.9.11",

View File

@ -1,6 +1,6 @@
{
"name": "@mobicoop/ad",
"version": "2.3.0",
"version": "2.4.0",
"description": "Mobicoop V3 Ad",
"author": "sbriat",
"private": true,

View File

@ -0,0 +1,5 @@
-- CreateEnum
CREATE TYPE "Status" AS ENUM ('PENDING', 'VALID', 'INVALID', 'SUSPENDED');
-- AlterTable
ALTER TABLE "ad" ADD COLUMN "status" "Status" NOT NULL DEFAULT 'PENDING';

View File

@ -14,6 +14,7 @@ datasource db {
model Ad {
uuid String @id @default(uuid()) @db.Uuid
userUuid String @db.Uuid
status Status @default(PENDING)
driver Boolean
passenger Boolean
frequency Frequency
@ -66,3 +67,10 @@ enum Frequency {
PUNCTUAL
RECURRENT
}
enum Status {
PENDING
VALID
INVALID
SUSPENDED
}

View File

@ -7,6 +7,14 @@ export const GRPC_SERVICE_NAME = 'AdService';
// messaging
export const AD_CREATED_ROUTING_KEY = 'ad.created';
export const MATCHER_AD_CREATED_MESSAGE_HANDLER = 'matcherAdCreated';
export const MATCHER_AD_CREATED_ROUTING_KEY = 'matcher.ad.created';
export const MATCHER_AD_CREATED_QUEUE = 'matcher-ad-created';
export const MATCHER_AD_CREATION_FAILED_MESSAGE_HANDLER =
'matcherAdCreationFailed';
export const MATCHER_AD_CREATION_FAILED_ROUTING_KEY =
'matcher.ad.creation.failed';
export const MATCHER_AD_CREATION_FAILED_QUEUE = 'matcher-ad-creation-failed';
// configuration
export const SERVICE_CONFIGURATION_SET_QUEUE = 'ad-configuration-set';

View File

@ -8,7 +8,7 @@ import {
WaypointModel,
ScheduleItemModel,
} from './infrastructure/ad.repository';
import { Frequency } from './core/domain/ad.types';
import { Frequency, Status } from './core/domain/ad.types';
import { WaypointProps } from './core/domain/value-objects/waypoint.value-object';
import { v4 } from 'uuid';
import { ScheduleItemProps } from './core/domain/value-objects/schedule-item.value-object';
@ -39,6 +39,7 @@ export class AdMapper
userUuid: copy.userId,
driver: copy.driver as boolean,
passenger: copy.passenger as boolean,
status: copy.status,
frequency: copy.frequency,
fromDate: new Date(copy.fromDate),
toDate: new Date(copy.toDate),
@ -92,6 +93,7 @@ export class AdMapper
userId: record.userUuid,
driver: record.driver,
passenger: record.passenger,
status: record.status as Status,
frequency: record.frequency as Frequency,
fromDate: record.fromDate.toISOString().split('T')[0],
toDate: record.toDate.toISOString().split('T')[0],
@ -135,6 +137,7 @@ export class AdMapper
response.userId = props.userId;
response.driver = props.driver as boolean;
response.passenger = props.passenger as boolean;
response.status = props.status;
response.frequency = props.frequency;
response.fromDate = this.outputDatetimeTransformer.fromDate(
{

View File

@ -23,6 +23,10 @@ import { InputDateTimeTransformer } from './infrastructure/input-datetime-transf
import { OutputDateTimeTransformer } from './infrastructure/output-datetime-transformer';
import { FindAdsByIdsGrpcController } from './interface/grpc-controllers/find-ads-by-ids.grpc.controller';
import { FindAdsByIdsQueryHandler } from './core/application/queries/find-ads-by-ids/find-ads-by-ids.query-handler';
import { MatcherAdCreatedMessageHandler } from './interface/message-handlers/matcher-ad-created.message-handler';
import { ValidateAdService } from './core/application/commands/validate-ad/validate-ad.service';
import { MatcherAdCreationFailedMessageHandler } from './interface/message-handlers/matcher-ad-creation-failed.message-handler';
import { InvalidateAdService } from './core/application/commands/invalidate-ad/invalidate-ad.service';
const grpcControllers = [
CreateAdGrpcController,
@ -30,11 +34,20 @@ const grpcControllers = [
FindAdsByIdsGrpcController,
];
const messageHandlers = [
MatcherAdCreatedMessageHandler,
MatcherAdCreationFailedMessageHandler,
];
const eventHandlers: Provider[] = [
PublishMessageWhenAdIsCreatedDomainEventHandler,
];
const commandHandlers: Provider[] = [CreateAdService];
const commandHandlers: Provider[] = [
CreateAdService,
ValidateAdService,
InvalidateAdService,
];
const queryHandlers: Provider[] = [
FindAdByIdQueryHandler,
@ -81,6 +94,7 @@ const adapters: Provider[] = [
imports: [CqrsModule],
controllers: [...grpcControllers],
providers: [
...messageHandlers,
...eventHandlers,
...commandHandlers,
...queryHandlers,

View File

@ -0,0 +1,7 @@
import { Command, CommandProps } from '@mobicoop/ddd-library';
export class InvalidateAdCommand extends Command {
constructor(props: CommandProps<InvalidateAdCommand>) {
super(props);
}
}

View File

@ -0,0 +1,25 @@
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { InvalidateAdCommand } from './invalidate-ad.command';
import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
import { Inject } from '@nestjs/common';
import { AdRepositoryPort } from '../../ports/ad.repository.port';
import { AggregateID } from '@mobicoop/ddd-library';
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
@CommandHandler(InvalidateAdCommand)
export class InvalidateAdService implements ICommandHandler {
constructor(
@Inject(AD_REPOSITORY)
private readonly repository: AdRepositoryPort,
) {}
async execute(command: InvalidateAdCommand): Promise<AggregateID> {
const ad: AdEntity = await this.repository.findOneById(command.id, {
waypoints: true,
schedule: true,
});
ad.invalid();
await this.repository.update(ad.id, ad);
return ad.id;
}
}

View File

@ -0,0 +1,7 @@
import { Command, CommandProps } from '@mobicoop/ddd-library';
export class ValidateAdCommand extends Command {
constructor(props: CommandProps<ValidateAdCommand>) {
super(props);
}
}

View File

@ -0,0 +1,25 @@
import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { ValidateAdCommand } from './validate-ad.command';
import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
import { Inject } from '@nestjs/common';
import { AdRepositoryPort } from '../../ports/ad.repository.port';
import { AggregateID } from '@mobicoop/ddd-library';
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
@CommandHandler(ValidateAdCommand)
export class ValidateAdService implements ICommandHandler {
constructor(
@Inject(AD_REPOSITORY)
private readonly repository: AdRepositoryPort,
) {}
async execute(command: ValidateAdCommand): Promise<AggregateID> {
const ad: AdEntity = await this.repository.findOneById(command.id, {
waypoints: true,
schedule: true,
});
ad.valid();
await this.repository.update(ad.id, ad);
return ad.id;
}
}

View File

@ -1,6 +1,6 @@
import { Inject, Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { AdCreatedDomainEvent } from '../../domain/events/ad-created.domain-events';
import { AdCreatedDomainEvent } from '../../domain/events/ad-created.domain-event';
import { MessagePublisherPort } from '@mobicoop/ddd-library';
import { AD_MESSAGE_PUBLISHER } from '@modules/ad/ad.di-tokens';
import { AD_CREATED_ROUTING_KEY } from '@src/app.constants';

View File

@ -1,19 +1,26 @@
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
import { v4 } from 'uuid';
import { AdCreatedDomainEvent } from './events/ad-created.domain-events';
import { AdProps, CreateAdProps } from './ad.types';
import { AdCreatedDomainEvent } from './events/ad-created.domain-event';
import { AdProps, CreateAdProps, Status } from './ad.types';
import { ScheduleItemProps } from './value-objects/schedule-item.value-object';
import { WaypointProps } from './value-objects/waypoint.value-object';
import { AdValidatedDomainEvent } from './events/ad-validated.domain-event';
import { AdInvalidatedDomainEvent } from './events/ad-invalidated.domain-event';
import { AdSuspendedDomainEvent } from './events/ad-suspended.domain-event';
export class AdEntity extends AggregateRoot<AdProps> {
protected readonly _id: AggregateID;
static create = (create: CreateAdProps): AdEntity => {
const id = v4();
const props: AdProps = { ...create };
const props: AdProps = { ...create, status: Status.PENDING };
const ad = new AdEntity({ id, props });
ad.addEvent(
new AdCreatedDomainEvent({
metadata: {
correlationId: id,
timestamp: Date.now(),
},
aggregateId: id,
userId: props.userId,
driver: props.driver,
@ -45,6 +52,48 @@ export class AdEntity extends AggregateRoot<AdProps> {
return ad;
};
valid = (): AdEntity => {
this.props.status = Status.VALID;
this.addEvent(
new AdValidatedDomainEvent({
metadata: {
correlationId: this.id,
timestamp: Date.now(),
},
aggregateId: this.id,
}),
);
return this;
};
invalid = (): AdEntity => {
this.props.status = Status.INVALID;
this.addEvent(
new AdInvalidatedDomainEvent({
metadata: {
correlationId: this.id,
timestamp: Date.now(),
},
aggregateId: this.id,
}),
);
return this;
};
suspend = (): AdEntity => {
this.props.status = Status.SUSPENDED;
this.addEvent(
new AdSuspendedDomainEvent({
metadata: {
correlationId: this.id,
timestamp: Date.now(),
},
aggregateId: this.id,
}),
);
return this;
};
validate(): void {
// entity business rules validation to protect it's invariant before saving entity to a database
}

View File

@ -5,6 +5,7 @@ import { WaypointProps } from './value-objects/waypoint.value-object';
export interface AdProps {
userId: string;
driver: boolean;
status: Status;
passenger: boolean;
frequency: Frequency;
fromDate: string;
@ -35,3 +36,10 @@ export enum Frequency {
PUNCTUAL = 'PUNCTUAL',
RECURRENT = 'RECURRENT',
}
export enum Status {
PENDING = 'PENDING',
VALID = 'VALID',
INVALID = 'INVALID',
SUSPENDED = 'SUSPENDED',
}

View File

@ -0,0 +1,7 @@
import { DomainEvent, DomainEventProps } from '@mobicoop/ddd-library';
export class AdInvalidatedDomainEvent extends DomainEvent {
constructor(props: DomainEventProps<AdInvalidatedDomainEvent>) {
super(props);
}
}

View File

@ -0,0 +1,7 @@
import { DomainEvent, DomainEventProps } from '@mobicoop/ddd-library';
export class AdSuspendedDomainEvent extends DomainEvent {
constructor(props: DomainEventProps<AdSuspendedDomainEvent>) {
super(props);
}
}

View File

@ -0,0 +1,7 @@
import { DomainEvent, DomainEventProps } from '@mobicoop/ddd-library';
export class AdValidatedDomainEvent extends DomainEvent {
constructor(props: DomainEventProps<AdValidatedDomainEvent>) {
super(props);
}
}

View File

@ -17,6 +17,7 @@ export type AdBaseModel = {
userUuid: string;
driver: boolean;
passenger: boolean;
status: string;
frequency: string;
fromDate: Date;
toDate: Date;

View File

@ -1,10 +1,11 @@
import { ResponseBase } from '@mobicoop/ddd-library';
import { Frequency } from '@modules/ad/core/domain/ad.types';
import { Frequency, Status } from '@modules/ad/core/domain/ad.types';
export class AdResponseDto extends ResponseBase {
userId: string;
driver: boolean;
passenger: boolean;
status: Status;
frequency: Frequency;
fromDate: string;
toDate: string;

View File

@ -0,0 +1,24 @@
import { Injectable } from '@nestjs/common';
import { RabbitSubscribe } from '@mobicoop/message-broker-module';
import { CommandBus } from '@nestjs/cqrs';
import { MATCHER_AD_CREATED_MESSAGE_HANDLER } from '@src/app.constants';
import { MatcherAdCreatedIntegrationEvent } from './matcher-ad.types';
import { ValidateAdCommand } from '@modules/ad/core/application/commands/validate-ad/validate-ad.command';
@Injectable()
export class MatcherAdCreatedMessageHandler {
constructor(private readonly commandBus: CommandBus) {}
@RabbitSubscribe({
name: MATCHER_AD_CREATED_MESSAGE_HANDLER,
})
public async matcherAdCreated(message: string) {
const matcherAdCreatedIntegrationEvent: MatcherAdCreatedIntegrationEvent =
JSON.parse(message);
await this.commandBus.execute(
new ValidateAdCommand({
id: matcherAdCreatedIntegrationEvent.id,
}),
);
}
}

View File

@ -0,0 +1,24 @@
import { Injectable } from '@nestjs/common';
import { RabbitSubscribe } from '@mobicoop/message-broker-module';
import { CommandBus } from '@nestjs/cqrs';
import { MATCHER_AD_CREATION_FAILED_MESSAGE_HANDLER } from '@src/app.constants';
import { MatcherAdCreationFailedIntegrationEvent } from './matcher-ad.types';
import { InvalidateAdCommand } from '@modules/ad/core/application/commands/invalidate-ad/invalidate-ad.command';
@Injectable()
export class MatcherAdCreationFailedMessageHandler {
constructor(private readonly commandBus: CommandBus) {}
@RabbitSubscribe({
name: MATCHER_AD_CREATION_FAILED_MESSAGE_HANDLER,
})
public async matcherAdCreationFailed(message: string) {
const matcherAdCreationFailedIntegrationEvent: MatcherAdCreationFailedIntegrationEvent =
JSON.parse(message);
await this.commandBus.execute(
new InvalidateAdCommand({
id: matcherAdCreationFailedIntegrationEvent.id,
}),
);
}
}

View File

@ -0,0 +1,4 @@
import { IntegrationEvent } from '@mobicoop/ddd-library';
export type MatcherAdCreatedIntegrationEvent = IntegrationEvent;
export type MatcherAdCreationFailedIntegrationEvent = IntegrationEvent;

View File

@ -2,7 +2,7 @@ import { OUTPUT_DATETIME_TRANSFORMER } from '@modules/ad/ad.di-tokens';
import { AdMapper } from '@modules/ad/ad.mapper';
import { DateTimeTransformerPort } from '@modules/ad/core/application/ports/datetime-transformer.port';
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
import { Frequency } from '@modules/ad/core/domain/ad.types';
import { Frequency, Status } from '@modules/ad/core/domain/ad.types';
import {
AdReadModel,
AdWriteModel,
@ -17,6 +17,7 @@ const adEntity: AdEntity = new AdEntity({
userId: '7ca2490b-d04d-4ac5-8d6c-5c416fab922e',
driver: false,
passenger: true,
status: Status.PENDING,
frequency: Frequency.PUNCTUAL,
fromDate: '2023-06-21',
toDate: '2023-06-21',
@ -67,6 +68,7 @@ const adReadModel: AdReadModel = {
userUuid: '7ca2490b-d04d-4ac5-8d6c-5c416fab922e',
driver: false,
passenger: true,
status: Status.PENDING,
frequency: Frequency.PUNCTUAL,
fromDate: new Date('2023-06-21'),
toDate: new Date('2023-06-21'),

View File

@ -1,5 +1,9 @@
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
import { CreateAdProps, Frequency } from '@modules/ad/core/domain/ad.types';
import {
CreateAdProps,
Frequency,
Status,
} from '@modules/ad/core/domain/ad.types';
import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
const originWaypointProps: WaypointProps = {
@ -124,6 +128,7 @@ describe('Ad entity create', () => {
punctualPassengerCreateAdProps,
);
expect(punctualPassengerAd.id.length).toBe(36);
expect(punctualPassengerAd.getProps().status).toBe(Status.PENDING);
expect(punctualPassengerAd.getProps().schedule.length).toBe(1);
expect(punctualPassengerAd.getProps().schedule[0].day).toBe(3);
expect(punctualPassengerAd.getProps().schedule[0].time).toBe('08:30');
@ -191,3 +196,33 @@ describe('Ad entity create', () => {
});
});
});
describe('Ad entity validate status', () => {
it('should validate status of a pending ad entity', async () => {
const punctualPassengerAd: AdEntity = AdEntity.create(
punctualPassengerCreateAdProps,
);
punctualPassengerAd.valid();
expect(punctualPassengerAd.getProps().status).toBe(Status.VALID);
});
});
describe('Ad entity invalidate status', () => {
it('should invalidate status of a pending ad entity', async () => {
const punctualPassengerAd: AdEntity = AdEntity.create(
punctualPassengerCreateAdProps,
);
punctualPassengerAd.invalid();
expect(punctualPassengerAd.getProps().status).toBe(Status.INVALID);
});
});
describe('Ad entity suspend status', () => {
it('should suspend status of a pending ad entity', async () => {
const punctualPassengerAd: AdEntity = AdEntity.create(
punctualPassengerCreateAdProps,
);
punctualPassengerAd.suspend();
expect(punctualPassengerAd.getProps().status).toBe(Status.SUSPENDED);
});
});

View File

@ -0,0 +1,98 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
import { AggregateID } from '@mobicoop/ddd-library';
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
import { CreateAdProps, Frequency } from '@modules/ad/core/domain/ad.types';
import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
import { InvalidateAdService } from '@modules/ad/core/application/commands/invalidate-ad/invalidate-ad.service';
import { InvalidateAdCommand } from '@modules/ad/core/application/commands/invalidate-ad/invalidate-ad.command';
const originWaypointProps: WaypointProps = {
position: 0,
address: {
houseNumber: '5',
street: 'Avenue Foch',
locality: 'Nancy',
postalCode: '54000',
country: 'France',
coordinates: {
lat: 48.689445,
lon: 6.17651,
},
},
};
const destinationWaypointProps: WaypointProps = {
position: 1,
address: {
locality: 'Paris',
postalCode: '75000',
country: 'France',
coordinates: {
lat: 48.8566,
lon: 2.3522,
},
},
};
const baseCreateAdProps = {
userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36',
seatsProposed: 3,
seatsRequested: 1,
strict: false,
waypoints: [originWaypointProps, destinationWaypointProps],
};
const punctualCreateAdProps = {
fromDate: '2023-06-22',
toDate: '2023-06-22',
schedule: [
{
time: '08:30',
},
],
frequency: Frequency.PUNCTUAL,
};
const punctualPassengerCreateAdProps: CreateAdProps = {
...baseCreateAdProps,
...punctualCreateAdProps,
driver: false,
passenger: true,
};
const ad: AdEntity = AdEntity.create(punctualPassengerCreateAdProps);
const mockAdRepository = {
findOneById: jest.fn().mockImplementation(() => ad),
update: jest.fn().mockImplementation(() => ad.id),
};
describe('Invalidate Ad Service', () => {
let invalidateAdService: InvalidateAdService;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: AD_REPOSITORY,
useValue: mockAdRepository,
},
InvalidateAdService,
],
}).compile();
invalidateAdService = module.get<InvalidateAdService>(InvalidateAdService);
});
it('should be defined', () => {
expect(invalidateAdService).toBeDefined();
});
describe('execution', () => {
it('should invalidate an ad', async () => {
jest.spyOn(ad, 'invalid');
const invalidateAdCommand = new InvalidateAdCommand(ad.id);
const result: AggregateID =
await invalidateAdService.execute(invalidateAdCommand);
expect(result).toBe(ad.id);
expect(ad.invalid).toHaveBeenCalledTimes(1);
});
});
});

View File

@ -1,6 +1,6 @@
import { Frequency } from '@modules/ad/core/domain/ad.types';
import { PublishMessageWhenAdIsCreatedDomainEventHandler } from '@modules/ad/core/application/event-handlers/publish-message-when-ad-is-created.domain-event-handler';
import { AdCreatedDomainEvent } from '@modules/ad/core/domain/events/ad-created.domain-events';
import { AdCreatedDomainEvent } from '@modules/ad/core/domain/events/ad-created.domain-event';
import { Test, TestingModule } from '@nestjs/testing';
import { AD_MESSAGE_PUBLISHER } from '@modules/ad/ad.di-tokens';
import { AD_CREATED_ROUTING_KEY } from '@src/app.constants';

View File

@ -0,0 +1,98 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
import { AggregateID } from '@mobicoop/ddd-library';
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
import { CreateAdProps, Frequency } from '@modules/ad/core/domain/ad.types';
import { ValidateAdService } from '@modules/ad/core/application/commands/validate-ad/validate-ad.service';
import { ValidateAdCommand } from '@modules/ad/core/application/commands/validate-ad/validate-ad.command';
import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
const originWaypointProps: WaypointProps = {
position: 0,
address: {
houseNumber: '5',
street: 'Avenue Foch',
locality: 'Nancy',
postalCode: '54000',
country: 'France',
coordinates: {
lat: 48.689445,
lon: 6.17651,
},
},
};
const destinationWaypointProps: WaypointProps = {
position: 1,
address: {
locality: 'Paris',
postalCode: '75000',
country: 'France',
coordinates: {
lat: 48.8566,
lon: 2.3522,
},
},
};
const baseCreateAdProps = {
userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36',
seatsProposed: 3,
seatsRequested: 1,
strict: false,
waypoints: [originWaypointProps, destinationWaypointProps],
};
const punctualCreateAdProps = {
fromDate: '2023-06-22',
toDate: '2023-06-22',
schedule: [
{
time: '08:30',
},
],
frequency: Frequency.PUNCTUAL,
};
const punctualPassengerCreateAdProps: CreateAdProps = {
...baseCreateAdProps,
...punctualCreateAdProps,
driver: false,
passenger: true,
};
const ad: AdEntity = AdEntity.create(punctualPassengerCreateAdProps);
const mockAdRepository = {
findOneById: jest.fn().mockImplementation(() => ad),
update: jest.fn().mockImplementation(() => ad.id),
};
describe('Validate Ad Service', () => {
let validateAdService: ValidateAdService;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: AD_REPOSITORY,
useValue: mockAdRepository,
},
ValidateAdService,
],
}).compile();
validateAdService = module.get<ValidateAdService>(ValidateAdService);
});
it('should be defined', () => {
expect(validateAdService).toBeDefined();
});
describe('execution', () => {
it('should validate an ad', async () => {
jest.spyOn(ad, 'valid');
const validateAdCommand = new ValidateAdCommand(ad.id);
const result: AggregateID =
await validateAdService.execute(validateAdCommand);
expect(result).toBe(ad.id);
expect(ad.valid).toHaveBeenCalledTimes(1);
});
});
});

View File

@ -0,0 +1,46 @@
import { MatcherAdCreatedMessageHandler } from '@modules/ad/interface/message-handlers/matcher-ad-created.message-handler';
import { CommandBus } from '@nestjs/cqrs';
import { Test, TestingModule } from '@nestjs/testing';
const matcherAdCreatedMessage =
'{"id":"4eb6a6af-ecfd-41c3-9118-473a507014d4","driverDuration":"3512","driverDistance":"65845","fwdAzimuth":"90","backAzimuth":"270"}';
const mockCommandBus = {
execute: jest.fn(),
};
describe('Matcher Ad Created Message Handler', () => {
let matcherAdCreatedMessageHandler: MatcherAdCreatedMessageHandler;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: CommandBus,
useValue: mockCommandBus,
},
MatcherAdCreatedMessageHandler,
],
}).compile();
matcherAdCreatedMessageHandler = module.get<MatcherAdCreatedMessageHandler>(
MatcherAdCreatedMessageHandler,
);
});
afterEach(async () => {
jest.clearAllMocks();
});
it('should be defined', () => {
expect(matcherAdCreatedMessageHandler).toBeDefined();
});
it('should validate an ad', async () => {
jest.spyOn(mockCommandBus, 'execute');
await matcherAdCreatedMessageHandler.matcherAdCreated(
matcherAdCreatedMessage,
);
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
});
});

View File

@ -0,0 +1,47 @@
import { MatcherAdCreationFailedMessageHandler } from '@modules/ad/interface/message-handlers/matcher-ad-creation-failed.message-handler';
import { CommandBus } from '@nestjs/cqrs';
import { Test, TestingModule } from '@nestjs/testing';
const matcherAdCreationFailedMessage =
'{"id":"4eb6a6af-ecfd-41c3-9118-473a507014d4"}';
const mockCommandBus = {
execute: jest.fn(),
};
describe('Matcher Ad Creation Failed Message Handler', () => {
let matcherAdCreationFailedMessageHandler: MatcherAdCreationFailedMessageHandler;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: CommandBus,
useValue: mockCommandBus,
},
MatcherAdCreationFailedMessageHandler,
],
}).compile();
matcherAdCreationFailedMessageHandler =
module.get<MatcherAdCreationFailedMessageHandler>(
MatcherAdCreationFailedMessageHandler,
);
});
afterEach(async () => {
jest.clearAllMocks();
});
it('should be defined', () => {
expect(matcherAdCreationFailedMessageHandler).toBeDefined();
});
it('should invalidate an ad', async () => {
jest.spyOn(mockCommandBus, 'execute');
await matcherAdCreationFailedMessageHandler.matcherAdCreationFailed(
matcherAdCreationFailedMessage,
);
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
});
});

View File

@ -2,7 +2,15 @@ import { Module, Provider } from '@nestjs/common';
import { MESSAGE_PUBLISHER } from './messager.di-tokens';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { SERVICE_NAME } from '@src/app.constants';
import {
MATCHER_AD_CREATED_MESSAGE_HANDLER,
MATCHER_AD_CREATED_QUEUE,
MATCHER_AD_CREATED_ROUTING_KEY,
MATCHER_AD_CREATION_FAILED_MESSAGE_HANDLER,
MATCHER_AD_CREATION_FAILED_QUEUE,
MATCHER_AD_CREATION_FAILED_ROUTING_KEY,
SERVICE_NAME,
} from '@src/app.constants';
import {
MessageBrokerModule,
MessageBrokerModuleOptions,
@ -24,6 +32,16 @@ const imports = [
) as boolean,
},
name: SERVICE_NAME,
handlers: {
[MATCHER_AD_CREATED_MESSAGE_HANDLER]: {
routingKey: MATCHER_AD_CREATED_ROUTING_KEY,
queue: MATCHER_AD_CREATED_QUEUE,
},
[MATCHER_AD_CREATION_FAILED_MESSAGE_HANDLER]: {
routingKey: MATCHER_AD_CREATION_FAILED_ROUTING_KEY,
queue: MATCHER_AD_CREATION_FAILED_QUEUE,
},
},
}),
}),
];