basic match action without functional behaviour

This commit is contained in:
sbriat 2023-04-06 17:05:25 +02:00
parent afca685e3d
commit 3b0f4b8c49
13 changed files with 262 additions and 22 deletions

View File

@ -84,6 +84,12 @@
"json", "json",
"ts" "ts"
], ],
"modulePathIgnorePatterns": [
".controller.ts",
".module.ts",
".request.ts",
"main.ts"
],
"rootDir": "src", "rootDir": "src",
"testRegex": ".*\\.spec\\.ts$", "testRegex": ".*\\.spec\\.ts$",
"transform": { "transform": {

View File

@ -4,6 +4,7 @@ import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config'; import { ConfigModule } from '@nestjs/config';
import { ConfigurationModule } from './modules/configuration/configuration.module'; import { ConfigurationModule } from './modules/configuration/configuration.module';
import { HealthModule } from './modules/health/health.module'; import { HealthModule } from './modules/health/health.module';
import { MatcherModule } from './modules/matcher/matcher.module';
@Module({ @Module({
imports: [ imports: [
@ -11,6 +12,7 @@ import { HealthModule } from './modules/health/health.module';
AutomapperModule.forRoot({ strategyInitializer: classes() }), AutomapperModule.forRoot({ strategyInitializer: classes() }),
ConfigurationModule, ConfigurationModule,
HealthModule, HealthModule,
MatcherModule,
], ],
controllers: [], controllers: [],
providers: [], providers: [],

View File

@ -1,6 +1,6 @@
import { NestFactory } from '@nestjs/core'; import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { MicroserviceOptions, Transport } from '@nestjs/microservices';
// import { join } from 'path'; import { join } from 'path';
import { AppModule } from './app.module'; import { AppModule } from './app.module';
async function bootstrap() { async function bootstrap() {
@ -8,19 +8,18 @@ async function bootstrap() {
app.connectMicroservice<MicroserviceOptions>({ app.connectMicroservice<MicroserviceOptions>({
transport: Transport.TCP, transport: Transport.TCP,
}); });
// app.connectMicroservice<MicroserviceOptions>({ app.connectMicroservice<MicroserviceOptions>({
// transport: Transport.GRPC, transport: Transport.GRPC,
// options: { options: {
// // package: ['matcher', 'health'], package: ['matcher', 'health'],
// package: ['health'], protoPath: [
// protoPath: [ join(__dirname, 'modules/matcher/adapters/primaries/matcher.proto'),
// // join(__dirname, 'modules/matcher/adapters/primaries/matcher.proto'), join(__dirname, 'modules/health/adapters/primaries/health.proto'),
// join(__dirname, 'modules/health/adapters/primaries/health.proto'), ],
// ], url: process.env.SERVICE_URL + ':' + process.env.SERVICE_PORT,
// url: process.env.SERVICE_URL + ':' + process.env.SERVICE_PORT, loader: { keepCase: true },
// loader: { keepCase: true }, },
// }, });
// });
await app.startAllMicroservices(); await app.startAllMicroservices();
await app.listen(process.env.HEALTH_SERVICE_PORT); await app.listen(process.env.HEALTH_SERVICE_PORT);

View File

@ -1,10 +1,10 @@
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { PrismaHealthIndicatorUseCase } from '../../domain/usecases/prisma.health-indicator.usecase'; import { PrismaHealthIndicatorUseCase } from '../../domain/usecases/prisma.health-indicator.usecase';
import { TerritoriesRepository } from '../../../territory/adapters/secondaries/territories.repository';
import { PrismaClientKnownRequestError } from '@prisma/client/runtime';
import { HealthCheckError, HealthIndicatorResult } from '@nestjs/terminus'; import { HealthCheckError, HealthIndicatorResult } from '@nestjs/terminus';
import { AdRepository } from '../../../matcher/adapters/secondaries/ad.repository';
import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library';
const mockTerritoriesRepository = { const mockAdRepository = {
healthCheck: jest healthCheck: jest
.fn() .fn()
.mockImplementationOnce(() => { .mockImplementationOnce(() => {
@ -25,8 +25,8 @@ describe('PrismaHealthIndicatorUseCase', () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
providers: [ providers: [
{ {
provide: TerritoriesRepository, provide: AdRepository,
useValue: mockTerritoriesRepository, useValue: mockAdRepository,
}, },
PrismaHealthIndicatorUseCase, PrismaHealthIndicatorUseCase,
], ],

View File

@ -1,7 +1,7 @@
import { Mapper } from '@automapper/core'; import { Mapper } from '@automapper/core';
import { InjectMapper } from '@automapper/nestjs'; import { InjectMapper } from '@automapper/nestjs';
import { Controller, UsePipes } from '@nestjs/common'; import { Controller, UsePipes } from '@nestjs/common';
import { CommandBus, QueryBus } from '@nestjs/cqrs'; import { QueryBus } from '@nestjs/cqrs';
import { GrpcMethod } from '@nestjs/microservices'; import { GrpcMethod } from '@nestjs/microservices';
import { RpcValidationPipe } from 'src/modules/utils/pipes/rpc.validation-pipe'; import { RpcValidationPipe } from 'src/modules/utils/pipes/rpc.validation-pipe';
import { MatchRequest } from '../../domain/dtos/match.request'; import { MatchRequest } from '../../domain/dtos/match.request';
@ -19,7 +19,6 @@ import { MatchPresenter } from '../secondaries/match.presenter';
@Controller() @Controller()
export class MatcherController { export class MatcherController {
constructor( constructor(
private readonly _commandBus: CommandBus,
private readonly _queryBus: QueryBus, private readonly _queryBus: QueryBus,
@InjectMapper() private readonly _mapper: Mapper, @InjectMapper() private readonly _mapper: Mapper,
) {} ) {}

View File

@ -7,7 +7,12 @@ service MatcherService {
} }
message MatchRequest { message MatchRequest {
string uuid = 1; repeated Point waypoints = 1;
}
message Point {
float lon = 1;
float lat = 2;
} }
message Match { message Match {

View File

@ -0,0 +1,12 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export abstract class IMessageBroker {
exchange: string;
constructor(exchange: string) {
this.exchange = exchange;
}
abstract publish(routingKey: string, message: string): void;
}

View File

@ -0,0 +1,18 @@
import { AmqpConnection } from '@golevelup/nestjs-rabbitmq';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { IMessageBroker } from './message-broker';
@Injectable()
export class Messager extends IMessageBroker {
constructor(
private readonly _amqpConnection: AmqpConnection,
configService: ConfigService,
) {
super(configService.get<string>('RMQ_EXCHANGE'));
}
publish(routingKey: string, message: string): void {
this._amqpConnection.publish(this.exchange, routingKey, message);
}
}

View File

@ -0,0 +1,38 @@
import { Mapper } from '@automapper/core';
import { InjectMapper } from '@automapper/nestjs';
import { QueryHandler } from '@nestjs/cqrs';
import { Messager } from '../../adapters/secondaries/messager';
import { MatchQuery } from '../../queries/match.query';
import { AdRepository } from '../../adapters/secondaries/ad.repository';
import { Match } from '../entities/match';
import { ICollection } from 'src/modules/database/src/interfaces/collection.interface';
@QueryHandler(MatchQuery)
export class MatchUseCase {
constructor(
private readonly _repository: AdRepository,
private readonly _messager: Messager,
@InjectMapper() private readonly _mapper: Mapper,
) {}
async execute(matchQuery: MatchQuery): Promise<ICollection<Match>> {
try {
const match = new Match();
match.uuid = 'e23f9725-2c19-49a0-9ef6-17d8b9a5ec85';
this._messager.publish('matcher.match', 'match !');
return {
data: [match],
total: 1,
};
} catch (error) {
this._messager.publish(
'logging.matcher.match.crit',
JSON.stringify({
matchQuery,
error,
}),
);
throw error;
}
}
}

View File

@ -0,0 +1,35 @@
import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq';
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { CqrsModule } from '@nestjs/cqrs';
import { DatabaseModule } from '../database/database.module';
import { MatcherController } from './adapters/primaries/matcher.controller';
import { MatchProfile } from './mappers/match.profile';
import { AdRepository } from './adapters/secondaries/ad.repository';
import { MatchUseCase } from './domain/usecases/match.usecase';
import { Messager } from './adapters/secondaries/messager';
@Module({
imports: [
DatabaseModule,
CqrsModule,
RabbitMQModule.forRootAsync(RabbitMQModule, {
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
exchanges: [
{
name: configService.get<string>('RMQ_EXCHANGE'),
type: 'topic',
},
],
uri: configService.get<string>('RMQ_URI'),
connectionInitOptions: { wait: false },
}),
inject: [ConfigService],
}),
],
controllers: [MatcherController],
providers: [MatchProfile, AdRepository, Messager, MatchUseCase],
exports: [],
})
export class MatcherModule {}

View File

@ -0,0 +1,59 @@
import { Test, TestingModule } from '@nestjs/testing';
import { Messager } from '../../adapters/secondaries/messager';
import { MatchUseCase } from '../../domain/usecases/match.usecase';
import { MatchRequest } from '../../domain/dtos/match.request';
import { MatchQuery } from '../../queries/match.query';
import { AdRepository } from '../../adapters/secondaries/ad.repository';
import { AutomapperModule } from '@automapper/nestjs';
import { classes } from '@automapper/classes';
const mockAdRepository = {};
const mockMessager = {
publish: jest.fn().mockImplementation(),
};
describe('MatchUseCase', () => {
let matchUseCase: MatchUseCase;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [AutomapperModule.forRoot({ strategyInitializer: classes() })],
providers: [
{
provide: AdRepository,
useValue: mockAdRepository,
},
{
provide: Messager,
useValue: mockMessager,
},
MatchUseCase,
],
}).compile();
matchUseCase = module.get<MatchUseCase>(MatchUseCase);
});
it('should be defined', () => {
expect(matchUseCase).toBeDefined();
});
describe('execute', () => {
it('should return matches', async () => {
const matchRequest: MatchRequest = new MatchRequest();
matchRequest.waypoints = [
{
lon: 1.093912,
lat: 49.440041,
},
{
lat: 50.630992,
lon: 3.045432,
},
];
const matches = await matchUseCase.execute(new MatchQuery(matchRequest));
expect(matches.total).toBe(1);
});
});
});

View File

@ -0,0 +1,47 @@
import { AmqpConnection } from '@golevelup/nestjs-rabbitmq';
import { ConfigService } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import { Messager } from '../../adapters/secondaries/messager';
const mockAmqpConnection = {
publish: jest.fn().mockImplementation(),
};
const mockConfigService = {
get: jest.fn().mockResolvedValue({
RMQ_EXCHANGE: 'mobicoop',
}),
};
describe('Messager', () => {
let messager: Messager;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [],
providers: [
Messager,
{
provide: AmqpConnection,
useValue: mockAmqpConnection,
},
{
provide: ConfigService,
useValue: mockConfigService,
},
],
}).compile();
messager = module.get<Messager>(Messager);
});
it('should be defined', () => {
expect(messager).toBeDefined();
});
it('should publish a message', async () => {
jest.spyOn(mockAmqpConnection, 'publish');
messager.publish('test.create.info', 'my-test');
expect(mockAmqpConnection.publish).toHaveBeenCalledTimes(1);
});
});

View File

@ -0,0 +1,20 @@
import { ArgumentMetadata } from '@nestjs/common';
import { RpcValidationPipe } from '../../pipes/rpc.validation-pipe';
import { MatchRequest } from '../../../matcher/domain/dtos/match.request';
describe('RpcValidationPipe', () => {
it('should not validate request', async () => {
const target: RpcValidationPipe = new RpcValidationPipe({
whitelist: true,
forbidUnknownValues: false,
});
const metadata: ArgumentMetadata = {
type: 'body',
metatype: MatchRequest,
data: '',
};
await target.transform(<MatchRequest>{}, metadata).catch((err) => {
expect(err.message).toEqual('Rpc Exception');
});
});
});