mirror of
https://gitlab.com/mobicoop/v3/service/ad.git
synced 2026-01-08 12:12:40 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51ca6cf9c4 | ||
|
|
be2af64f60 | ||
|
|
9fb7ef2eac | ||
|
|
492bb3ca44 | ||
|
|
e8903099d7 | ||
|
|
b17fc32a12 | ||
|
|
8c7512b6c3 | ||
|
|
15236904e3 |
@@ -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
|
||||
|
||||
19
README.md
19
README.md
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Command, CommandProps } from '@mobicoop/ddd-library';
|
||||
|
||||
export class DeleteUserAdsCommand extends Command {
|
||||
constructor(props: CommandProps<DeleteUserAdsCommand>) {
|
||||
super(props);
|
||||
}
|
||||
}
|
||||
@@ -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 })),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class DeleteAdRequestDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
id: string;
|
||||
}
|
||||
@@ -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 }),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
|
||||
55
tests/unit/ad/core/ad.fixtures.ts
Normal file
55
tests/unit/ad/core/ad.fixtures.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
42
tests/unit/ad/core/delete-ad.service.spec.ts
Normal file
42
tests/unit/ad/core/delete-ad.service.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
53
tests/unit/ad/core/delete-user-ads.service.spec.ts
Normal file
53
tests/unit/ad/core/delete-user-ads.service.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
@@ -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),
|
||||
|
||||
42
tests/unit/ad/interface/delete-ad.grpc.controller.spec.ts
Normal file
42
tests/unit/ad/interface/delete-ad.grpc.controller.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
43
tests/unit/ad/interface/user-deleted.message-handler.spec.ts
Normal file
43
tests/unit/ad/interface/user-deleted.message-handler.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||
"exclude": ["node_modules", "tests", "dist", "**/*spec.ts"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user