8 Commits
2.4.5 ... 2.5.0

Author SHA1 Message Date
Romain Thouvenin
51ca6cf9c4 Update documentation about the delete command 2024-04-29 08:34:40 +02:00
Romain Thouvenin
be2af64f60 Update the documentation about integration events 2024-04-29 08:29:50 +02:00
Romain Thouvenin
9fb7ef2eac Listen to user.deleted events to delete the corresponding user ads 2024-04-26 12:31:16 +02:00
Romain Thouvenin
492bb3ca44 Expose the debugger in the dev container 2024-04-26 12:29:06 +02:00
Romain Thouvenin
e8903099d7 Implement the delete GRPC command 2024-04-26 10:58:44 +02:00
Romain Thouvenin
b17fc32a12 Fix tests path in build config 2024-04-26 10:57:45 +02:00
Romain Thouvenin
8c7512b6c3 Use common test and build jobs 2024-04-03 08:51:29 +02:00
Romain Thouvenin
15236904e3 Prepare release 2.5 2024-04-03 08:48:51 +02:00
24 changed files with 486 additions and 132 deletions

View File

@@ -7,52 +7,7 @@ stages:
include:
- template: Security/SAST.gitlab-ci.yml
- template: Security/Secret-Detection.gitlab-ci.yml
##############
# TEST STAGE #
##############
test:
stage: test
image: docker/compose:latest
variables:
DOCKER_TLS_CERTDIR: ''
services:
- docker:dind
script:
- docker-compose -f docker-compose.ci.tools.yml -p ad-tools --env-file ci/.env.ci up -d
- sh ci/wait-up.sh
- docker-compose -f docker-compose.ci.service.yml -p ad-service --env-file ci/.env.ci up -d
- docker exec -t v3-ad-api sh -c "npm run test:integration:ci"
coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/
rules:
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $CI_DEFAULT_BRANCH || $CI_COMMIT_MESSAGE =~ /--check/ || $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
when: always
###############
# BUILD STAGE #
###############
build:
stage: build
image: docker:20.10.22
variables:
DOCKER_TLS_CERTDIR: ''
services:
- docker:dind
before_script:
- echo -n $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
script:
- export VERSION=$(docker run --rm -v "$PWD":/usr/src/app:ro -w /usr/src/app node:slim node -p "require('./package.json').version")
- docker pull $CI_REGISTRY_IMAGE:latest || true
- >
docker build
--pull
--cache-from $CI_REGISTRY_IMAGE:latest
--tag $CI_REGISTRY_IMAGE:$VERSION
--tag $CI_REGISTRY_IMAGE:latest
.
- docker push $CI_REGISTRY_IMAGE:$VERSION
- docker push $CI_REGISTRY_IMAGE:latest
only:
- main
- project: mobicoop/v3/gitlab-templates
file:
- /ci/service.test-job.yml
- /ci/release.build-job.yml

View File

@@ -232,11 +232,28 @@ The app exposes the following [gRPC](https://grpc.io/) services :
- waypoints: an array of addresses that represent the waypoints of the journey (only first and last waypoints are used for passenger ads). Note that positions are **required** and **must** be consecutives
- comment: optional freetext comment / description about the ad
- **Delete** : Delete permanently an ad
```json
{
"id": "80126a61-d128-4f96-afdb-92e33c75a3e1"
}
```
## Messages
### Handled
The service listens to these RabbitMQ messages:
- **user.deleted** (to delete the associated ads)
### Emitted
As mentionned earlier, RabbitMQ messages are sent after these events :
- **Create** (message : the created ad informations)
- **ad.created** (message: the created ad informations)
- **ad.deleted** (message: the id of the deleted ad)
## Tests / ESLint / Prettier

View File

@@ -11,10 +11,11 @@ services:
- .:/usr/src/app
env_file:
- .env
command: npm run start:dev
command: npm run start:debug
ports:
- ${SERVICE_PORT:-5006}:${SERVICE_PORT:-5006}
- ${HEALTH_SERVICE_PORT:-6006}:${HEALTH_SERVICE_PORT:-6006}
- 9226:9229
networks:
v3-network:
aliases:

View File

@@ -1,6 +1,6 @@
{
"name": "@mobicoop/ad",
"version": "2.4.5",
"version": "2.5.0",
"description": "Mobicoop V3 Ad",
"author": "sbriat",
"private": true,
@@ -11,7 +11,7 @@
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:debug": "nest start --debug 0.0.0.0:9229 --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"lint:check": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix-dry-run --ignore-path .gitignore",

View File

@@ -7,6 +7,8 @@ export const GRPC_SERVICE_NAME = 'AdService';
// messaging output
export const AD_CREATED_ROUTING_KEY = 'ad.created';
// messaging output
export const AD_DELETED_ROUTING_KEY = 'ad.deleted';
// messaging input
export const MATCHER_AD_CREATED_MESSAGE_HANDLER = 'matcherAdCreated';
@@ -18,6 +20,10 @@ export const MATCHER_AD_CREATION_FAILED_ROUTING_KEY =
'matcher-ad.creation-failed';
export const MATCHER_AD_CREATION_FAILED_QUEUE = 'ad.matcher-ad.creation-failed';
export const USER_DELETED_MESSAGE_HANDLER = 'userDeleted';
export const USER_DELETED_ROUTING_KEY = 'user.deleted';
export const USER_DELETED_QUEUE = 'ad.user.deleted';
// configuration
export const SERVICE_CONFIGURATION_SET_QUEUE = 'ad-configuration-set';
export const SERVICE_CONFIGURATION_DELETE_QUEUE = 'ad-configuration-delete';

View File

@@ -1,5 +1,5 @@
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
import { Module, Provider } from '@nestjs/common';
import { CreateAdGrpcController } from './interface/grpc-controllers/create-ad.grpc.controller';
import { CqrsModule } from '@nestjs/cqrs';
import {
AD_MESSAGE_PUBLISHER,
@@ -9,29 +9,35 @@ import {
TIMEZONE_FINDER,
TIME_CONVERTER,
} from './ad.di-tokens';
import { AdRepository } from './infrastructure/ad.repository';
import { AdMapper } from './ad.mapper';
import { CreateAdService } from './core/application/commands/create-ad/create-ad.service';
import { TimezoneFinder } from './infrastructure/timezone-finder';
import { TimeConverter } from './infrastructure/time-converter';
import { FindAdByIdGrpcController } from './interface/grpc-controllers/find-ad-by-id.grpc.controller';
import { FindAdByIdQueryHandler } from './core/application/queries/find-ad-by-id/find-ad-by-id.query-handler';
import { DeleteAdService } from './core/application/commands/delete-ad/delete-ad.service';
import { DeleteUserAdsService } from './core/application/commands/delete-user-ads/delete-user-ads.service';
import { InvalidateAdService } from './core/application/commands/invalidate-ad/invalidate-ad.service';
import { ValidateAdService } from './core/application/commands/validate-ad/validate-ad.service';
import { PublishMessageWhenAdIsDeletedDomainEventHandler } from './core/application/event-handlers/publish-message-when-ad-deleted.domain-event-handler';
import { PublishMessageWhenAdIsCreatedDomainEventHandler } from './core/application/event-handlers/publish-message-when-ad-is-created.domain-event-handler';
import { PrismaService } from './infrastructure/prisma.service';
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
import { FindAdByIdQueryHandler } from './core/application/queries/find-ad-by-id/find-ad-by-id.query-handler';
import { FindAdsByIdsQueryHandler } from './core/application/queries/find-ads-by-ids/find-ads-by-ids.query-handler';
import { FindAdsByUserIdQueryHandler } from './core/application/queries/find-ads-by-user-id/find-ads-by-user-id.query-handler';
import { AdRepository } from './infrastructure/ad.repository';
import { InputDateTimeTransformer } from './infrastructure/input-datetime-transformer';
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 { CreateAdGrpcController } from './interface/grpc-controllers/create-ad.grpc.controller';
import { DeleteAdGrpcController } from './interface/grpc-controllers/delete-ad.grpc.controller';
import { FindAdByIdGrpcController } from './interface/grpc-controllers/find-ad-by-id.grpc.controller';
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';
import { FindAdsByUserIdGrpcController } from './interface/grpc-controllers/find-ads-by-user-id.grpc.controller';
import { FindAdsByUserIdQueryHandler } from './core/application/queries/find-ads-by-user-id/find-ads-by-user-id.query-handler';
import { MatcherAdCreatedMessageHandler } from './interface/message-handlers/matcher-ad-created.message-handler';
import { MatcherAdCreationFailedMessageHandler } from './interface/message-handlers/matcher-ad-creation-failed.message-handler';
import { UserDeletedMessageHandler } from './interface/message-handlers/user-deleted.message-handler';
const grpcControllers = [
CreateAdGrpcController,
DeleteAdGrpcController,
FindAdByIdGrpcController,
FindAdsByIdsGrpcController,
FindAdsByUserIdGrpcController,
@@ -40,14 +46,18 @@ const grpcControllers = [
const messageHandlers = [
MatcherAdCreatedMessageHandler,
MatcherAdCreationFailedMessageHandler,
UserDeletedMessageHandler,
];
const eventHandlers: Provider[] = [
PublishMessageWhenAdIsCreatedDomainEventHandler,
PublishMessageWhenAdIsDeletedDomainEventHandler,
];
const commandHandlers: Provider[] = [
CreateAdService,
DeleteAdService,
DeleteUserAdsService,
ValidateAdService,
InvalidateAdService,
];

View File

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

View File

@@ -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);
}
}

View File

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

View File

@@ -0,0 +1,29 @@
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
import {
CommandBus,
CommandHandler,
ICommandHandler,
QueryBus,
} from '@nestjs/cqrs';
import { FindAdsByUserIdQuery } from '../../queries/find-ads-by-user-id/find-ads-by-user-id.query';
import { DeleteAdCommand } from '../delete-ad/delete-ad.command';
import { DeleteUserAdsCommand } from './delete-user-ads.command';
@CommandHandler(DeleteUserAdsCommand)
export class DeleteUserAdsService implements ICommandHandler {
constructor(
private readonly queryBus: QueryBus,
private readonly commandBus: CommandBus,
) {}
async execute(command: DeleteUserAdsCommand): Promise<void> {
const ads: AdEntity[] = await this.queryBus.execute(
new FindAdsByUserIdQuery(command.id),
);
await Promise.all(
ads.map((ad) =>
this.commandBus.execute(new DeleteAdCommand({ id: ad.id })),
),
);
}
}

View File

@@ -0,0 +1,22 @@
import { MessagePublisherPort } from '@mobicoop/ddd-library';
import { AD_MESSAGE_PUBLISHER } from '@modules/ad/ad.di-tokens';
import { Inject, Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { AD_DELETED_ROUTING_KEY } from '@src/app.constants';
import { AdDeletedDomainEvent } from '../../domain/events/ad-delete.domain-event';
@Injectable()
export class PublishMessageWhenAdIsDeletedDomainEventHandler {
constructor(
@Inject(AD_MESSAGE_PUBLISHER)
private readonly messagePublisher: MessagePublisherPort,
) {}
@OnEvent(AdDeletedDomainEvent.name, { async: true, promisify: true })
async handle(event: AdDeletedDomainEvent): Promise<void> {
this.messagePublisher.publish(
AD_DELETED_ROUTING_KEY,
JSON.stringify(event),
);
}
}

View File

@@ -1,12 +1,13 @@
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
import { AggregateID, AggregateRoot } from '@mobicoop/ddd-library';
import { v4 } from 'uuid';
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 { AdCreatedDomainEvent } from './events/ad-created.domain-event';
import { AdDeletedDomainEvent } from './events/ad-delete.domain-event';
import { AdInvalidatedDomainEvent } from './events/ad-invalidated.domain-event';
import { AdSuspendedDomainEvent } from './events/ad-suspended.domain-event';
import { AdValidatedDomainEvent } from './events/ad-validated.domain-event';
import { ScheduleItemProps } from './value-objects/schedule-item.value-object';
import { WaypointProps } from './value-objects/waypoint.value-object';
export class AdEntity extends AggregateRoot<AdProps> {
protected readonly _id: AggregateID;
@@ -95,6 +96,14 @@ export class AdEntity extends AggregateRoot<AdProps> {
return this;
};
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
}

View File

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

View File

@@ -0,0 +1,45 @@
import {
DatabaseErrorException,
NotFoundException,
RpcExceptionCode,
RpcValidationPipe,
} from '@mobicoop/ddd-library';
import { DeleteAdCommand } from '@modules/ad/core/application/commands/delete-ad/delete-ad.command';
import { Controller, UsePipes } from '@nestjs/common';
import { CommandBus } from '@nestjs/cqrs';
import { GrpcMethod, RpcException } from '@nestjs/microservices';
import { GRPC_SERVICE_NAME } from '@src/app.constants';
import { DeleteAdRequestDto } from './dtos/delete-ad.request.dto';
@UsePipes(
new RpcValidationPipe({
whitelist: false,
forbidUnknownValues: false,
}),
)
@Controller()
export class DeleteAdGrpcController {
constructor(private readonly commandBus: CommandBus) {}
@GrpcMethod(GRPC_SERVICE_NAME, 'Delete')
async delete(data: DeleteAdRequestDto): Promise<void> {
try {
await this.commandBus.execute(new DeleteAdCommand(data));
} catch (error: any) {
if (error instanceof NotFoundException)
throw new RpcException({
code: RpcExceptionCode.NOT_FOUND,
message: error.message,
});
if (error instanceof DatabaseErrorException)
throw new RpcException({
code: RpcExceptionCode.INTERNAL,
message: error.message,
});
throw new RpcException({
code: RpcExceptionCode.UNKNOWN,
message: error.message,
});
}
}
}

View File

@@ -0,0 +1,7 @@
import { IsNotEmpty, IsString } from 'class-validator';
export class DeleteAdRequestDto {
@IsString()
@IsNotEmpty()
id: string;
}

View File

@@ -0,0 +1,23 @@
import { IntegrationEvent } from '@mobicoop/ddd-library';
import { RabbitSubscribe } from '@mobicoop/message-broker-module';
import { DeleteUserAdsCommand } from '@modules/ad/core/application/commands/delete-user-ads/delete-user-ads.command';
import { Injectable } from '@nestjs/common';
import { CommandBus } from '@nestjs/cqrs';
import { USER_DELETED_MESSAGE_HANDLER } from '@src/app.constants';
type UserDeletedEvent = IntegrationEvent;
@Injectable()
export class UserDeletedMessageHandler {
constructor(private readonly commandBus: CommandBus) {}
@RabbitSubscribe({
name: USER_DELETED_MESSAGE_HANDLER,
})
public async userDeleted(message: string) {
const deletedUser: UserDeletedEvent = JSON.parse(message);
await this.commandBus.execute(
new DeleteUserAdsCommand({ id: deletedUser.id }),
);
}
}

View File

@@ -1,6 +1,11 @@
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 {
MATCHER_AD_CREATED_MESSAGE_HANDLER,
@@ -10,12 +15,10 @@ import {
MATCHER_AD_CREATION_FAILED_QUEUE,
MATCHER_AD_CREATION_FAILED_ROUTING_KEY,
SERVICE_NAME,
USER_DELETED_MESSAGE_HANDLER,
USER_DELETED_QUEUE,
USER_DELETED_ROUTING_KEY,
} from '@src/app.constants';
import {
MessageBrokerModule,
MessageBrokerModuleOptions,
MessageBrokerPublisher,
} from '@mobicoop/message-broker-module';
const imports = [
MessageBrokerModule.forRootAsync({
@@ -41,6 +44,10 @@ const imports = [
routingKey: MATCHER_AD_CREATION_FAILED_ROUTING_KEY,
queue: MATCHER_AD_CREATION_FAILED_QUEUE,
},
[USER_DELETED_MESSAGE_HANDLER]: {
routingKey: USER_DELETED_ROUTING_KEY,
queue: USER_DELETED_QUEUE,
},
},
}),
}),

View File

@@ -0,0 +1,55 @@
import { CreateAdProps, Frequency } from '@modules/ad/core/domain/ad.types';
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,
};
export function punctualPassengerCreateAdProps(): CreateAdProps {
return {
...baseCreateAdProps,
...punctualCreateAdProps,
driver: false,
passenger: true,
};
}

View File

@@ -0,0 +1,42 @@
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 { punctualPassengerCreateAdProps } from './ad.fixtures';
const ad: AdEntity = AdEntity.create(punctualPassengerCreateAdProps());
jest.spyOn(ad, 'delete');
const mockAdRepository = {
findOneById: jest.fn().mockImplementation(() => ad),
delete: jest.fn(),
};
describe('delete-ad.service', () => {
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 trigger the delete logic and delete the ad from the repository', async () => {
await deleteAdService.execute(new DeleteAdCommand({ id: ad.id }));
expect(ad.delete).toHaveBeenCalled();
expect(mockAdRepository.delete).toHaveBeenCalledWith(ad);
});
});

View File

@@ -0,0 +1,53 @@
import { DeleteUserAdsCommand } from '@modules/ad/core/application/commands/delete-user-ads/delete-user-ads.command';
import { DeleteUserAdsService } from '@modules/ad/core/application/commands/delete-user-ads/delete-user-ads.service';
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
import { CommandBus, QueryBus } from '@nestjs/cqrs';
import { Test, TestingModule } from '@nestjs/testing';
import { punctualPassengerCreateAdProps } from './ad.fixtures';
const userAds = [
AdEntity.create(punctualPassengerCreateAdProps()),
AdEntity.create(punctualPassengerCreateAdProps()),
];
const mockQueryBus = {
execute: jest.fn().mockImplementation(() => userAds),
};
const mockCommandBus = {
execute: jest.fn(),
};
describe('delete-user-ads.service', () => {
let deleteUserAdsService: DeleteUserAdsService;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: QueryBus,
useValue: mockQueryBus,
},
{
provide: CommandBus,
useValue: mockCommandBus,
},
DeleteUserAdsService,
],
}).compile();
deleteUserAdsService =
module.get<DeleteUserAdsService>(DeleteUserAdsService);
});
it('should be defined', () => {
expect(deleteUserAdsService).toBeDefined();
});
it('should call the delete command for each ad returned by the query', async () => {
await deleteUserAdsService.execute(
new DeleteUserAdsCommand({ id: userAds[0].getProps().userId }),
);
expect(mockCommandBus.execute).toHaveBeenCalledTimes(userAds.length);
});
});

View File

@@ -1,62 +1,11 @@
import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens';
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
import { CreateAdProps, 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';
import { FindAdByIdQueryHandler } from '@modules/ad/core/application/queries/find-ad-by-id/find-ad-by-id.query-handler';
import { WaypointProps } from '@modules/ad/core/domain/value-objects/waypoint.value-object';
import { AdEntity } from '@modules/ad/core/domain/ad.entity';
import { Test, TestingModule } from '@nestjs/testing';
import { punctualPassengerCreateAdProps } from './ad.fixtures';
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 ad: AdEntity = AdEntity.create(punctualPassengerCreateAdProps());
const mockAdRepository = {
findOneById: jest.fn().mockImplementation(() => ad),

View File

@@ -0,0 +1,42 @@
import { DeleteAdGrpcController } from '@modules/ad/interface/grpc-controllers/delete-ad.grpc.controller';
import { CommandBus } from '@nestjs/cqrs';
import { Test, TestingModule } from '@nestjs/testing';
const mockCommandBus = {
execute: jest.fn(),
};
describe('Delete Ad Grpc Controller', () => {
let deleteAdGrpcController: DeleteAdGrpcController;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: CommandBus,
useValue: mockCommandBus,
},
DeleteAdGrpcController,
],
}).compile();
deleteAdGrpcController = module.get<DeleteAdGrpcController>(
DeleteAdGrpcController,
);
});
afterEach(async () => {
jest.clearAllMocks();
});
it('should be defined', () => {
expect(deleteAdGrpcController).toBeDefined();
});
it('should execute the delete ad command', async () => {
await deleteAdGrpcController.delete({
id: '200d61a8-d878-4378-a609-c19ea71633d2',
});
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
});
});

View File

@@ -0,0 +1,43 @@
import { UserDeletedMessageHandler } from '@modules/ad/interface/message-handlers/user-deleted.message-handler';
import { CommandBus } from '@nestjs/cqrs';
import { Test, TestingModule } from '@nestjs/testing';
const mockCommandBus = {
execute: jest.fn(),
};
describe('Matcher Ad Created Message Handler', () => {
let userDeletedMessageHandler: UserDeletedMessageHandler;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: CommandBus,
useValue: mockCommandBus,
},
UserDeletedMessageHandler,
],
}).compile();
userDeletedMessageHandler = module.get<UserDeletedMessageHandler>(
UserDeletedMessageHandler,
);
});
afterEach(async () => {
jest.clearAllMocks();
});
it('should be defined', () => {
expect(userDeletedMessageHandler).toBeDefined();
});
it('should call the command bus', async () => {
const userId = '4eb6a6af-ecfd-41c3-9118-473a507014d4';
const userDeletedMessage = `{"id":"${userId}"}`;
await userDeletedMessageHandler.userDeleted(userDeletedMessage);
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
expect(mockCommandBus.execute.mock.lastCall[0].id).toBe(userId);
});
});

View File

@@ -1,4 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
"exclude": ["node_modules", "tests", "dist", "**/*spec.ts"]
}