Merge branch 'testHealth' into 'main'
Use health package See merge request v3/service/auth!43
This commit is contained in:
commit
0825a5f0d9
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mobicoop/auth",
|
"name": "@mobicoop/auth",
|
||||||
"version": "0.1.1",
|
"version": "0.2.0",
|
||||||
"description": "Mobicoop V3 Auth Service",
|
"description": "Mobicoop V3 Auth Service",
|
||||||
"author": "sbriat",
|
"author": "sbriat",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
@ -37,6 +37,7 @@
|
||||||
"@grpc/grpc-js": "^1.8.0",
|
"@grpc/grpc-js": "^1.8.0",
|
||||||
"@grpc/proto-loader": "^0.7.4",
|
"@grpc/proto-loader": "^0.7.4",
|
||||||
"@mobicoop/ddd-library": "^0.3.0",
|
"@mobicoop/ddd-library": "^0.3.0",
|
||||||
|
"@mobicoop/health-module": "^1.1.0",
|
||||||
"@mobicoop/message-broker-module": "^1.2.0",
|
"@mobicoop/message-broker-module": "^1.2.0",
|
||||||
"@nestjs/axios": "^1.0.1",
|
"@nestjs/axios": "^1.0.1",
|
||||||
"@nestjs/common": "^9.0.0",
|
"@nestjs/common": "^9.0.0",
|
||||||
|
|
|
@ -6,8 +6,16 @@ import {
|
||||||
MessageBrokerModule,
|
MessageBrokerModule,
|
||||||
MessageBrokerModuleOptions,
|
MessageBrokerModuleOptions,
|
||||||
} from '@mobicoop/message-broker-module';
|
} from '@mobicoop/message-broker-module';
|
||||||
import { HealthModule } from '@modules/health/health.module';
|
import { HealthModule, HealthModuleOptions } from '@mobicoop/health-module';
|
||||||
import { AuthorizationModule } from '@modules/authorization/authorization.module';
|
import { AuthorizationModule } from '@modules/authorization/authorization.module';
|
||||||
|
import {
|
||||||
|
AUTHENTICATION_REPOSITORY,
|
||||||
|
USERNAME_REPOSITORY,
|
||||||
|
} from '@modules/authentication/authentication.di-tokens';
|
||||||
|
import { MESSAGE_PUBLISHER } from './modules/message-publisher/message-publisher.di-tokens';
|
||||||
|
import { MessagePublisherPort } from '@mobicoop/ddd-library';
|
||||||
|
import { ICheckRepository } from '@mobicoop/health-module/dist/core/domain/types/health.types';
|
||||||
|
import { MessagePublisherModule } from './modules/message-publisher/message-publisher.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -34,11 +42,29 @@ import { AuthorizationModule } from '@modules/authorization/authorization.module
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
HealthModule.forRootAsync({
|
||||||
|
imports: [AuthenticationModule, MessagePublisherModule],
|
||||||
|
inject: [
|
||||||
|
AUTHENTICATION_REPOSITORY,
|
||||||
|
USERNAME_REPOSITORY,
|
||||||
|
MESSAGE_PUBLISHER,
|
||||||
|
],
|
||||||
|
useFactory: async (
|
||||||
|
authenticationRepository: ICheckRepository,
|
||||||
|
usernameRepository: ICheckRepository,
|
||||||
|
messagePublisher: MessagePublisherPort,
|
||||||
|
): Promise<HealthModuleOptions> => ({
|
||||||
|
serviceName: 'auth',
|
||||||
|
criticalLoggingKey: 'logging.auth.health.crit',
|
||||||
|
checkRepositories: [authenticationRepository, usernameRepository],
|
||||||
|
messagePublisher,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
AuthenticationModule,
|
AuthenticationModule,
|
||||||
AuthorizationModule,
|
AuthorizationModule,
|
||||||
HealthModule,
|
MessagePublisherModule,
|
||||||
],
|
],
|
||||||
controllers: [],
|
controllers: [],
|
||||||
providers: [],
|
exports: [AuthenticationModule, MessagePublisherModule],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|
|
@ -21,10 +21,7 @@ async function bootstrap() {
|
||||||
__dirname,
|
__dirname,
|
||||||
'modules/authorization/interface/grpc-controllers/authorization.proto',
|
'modules/authorization/interface/grpc-controllers/authorization.proto',
|
||||||
),
|
),
|
||||||
join(
|
join(__dirname, 'health.proto'),
|
||||||
__dirname,
|
|
||||||
'modules/health/interface/grpc-controllers/health.proto',
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
url: process.env.SERVICE_URL + ':' + process.env.SERVICE_PORT,
|
url: process.env.SERVICE_URL + ':' + process.env.SERVICE_PORT,
|
||||||
loader: { keepCase: true, enums: String },
|
loader: { keepCase: true, enums: String },
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
export const AUTH_MESSAGE_PUBLISHER = Symbol('AUTH_MESSAGE_PUBLISHER');
|
||||||
export const AUTHENTICATION_REPOSITORY = Symbol('AUTHENTICATION_REPOSITORY');
|
export const AUTHENTICATION_REPOSITORY = Symbol('AUTHENTICATION_REPOSITORY');
|
||||||
export const USERNAME_REPOSITORY = Symbol('USERNAME_REPOSITORY');
|
export const USERNAME_REPOSITORY = Symbol('USERNAME_REPOSITORY');
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { CreateAuthenticationGrpcController } from './interface/grpc-controllers
|
||||||
import { CreateAuthenticationService } from './core/application/commands/create-authentication/create-authentication.service';
|
import { CreateAuthenticationService } from './core/application/commands/create-authentication/create-authentication.service';
|
||||||
import { AuthenticationMapper } from './authentication.mapper';
|
import { AuthenticationMapper } from './authentication.mapper';
|
||||||
import {
|
import {
|
||||||
|
AUTH_MESSAGE_PUBLISHER,
|
||||||
AUTHENTICATION_REPOSITORY,
|
AUTHENTICATION_REPOSITORY,
|
||||||
USERNAME_REPOSITORY,
|
USERNAME_REPOSITORY,
|
||||||
} from './authentication.di-tokens';
|
} from './authentication.di-tokens';
|
||||||
|
@ -11,7 +12,6 @@ import { PrismaService } from './infrastructure/prisma.service';
|
||||||
import { CqrsModule } from '@nestjs/cqrs';
|
import { CqrsModule } from '@nestjs/cqrs';
|
||||||
import { DeleteAuthenticationGrpcController } from './interface/grpc-controllers/delete-authentication.grpc.controller';
|
import { DeleteAuthenticationGrpcController } from './interface/grpc-controllers/delete-authentication.grpc.controller';
|
||||||
import { DeleteAuthenticationService } from './core/application/commands/delete-authentication/delete-authentication.service';
|
import { DeleteAuthenticationService } from './core/application/commands/delete-authentication/delete-authentication.service';
|
||||||
import { MESSAGE_PUBLISHER } from '@src/app.di-tokens';
|
|
||||||
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
|
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
|
||||||
import { UsernameRepository } from './infrastructure/username.repository';
|
import { UsernameRepository } from './infrastructure/username.repository';
|
||||||
import { UsernameMapper } from './username.mapper';
|
import { UsernameMapper } from './username.mapper';
|
||||||
|
@ -64,10 +64,10 @@ const repositories: Provider[] = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const messageBrokers: Provider[] = [
|
const messagePublishers: Provider[] = [
|
||||||
{
|
{
|
||||||
provide: MESSAGE_PUBLISHER,
|
provide: AUTH_MESSAGE_PUBLISHER,
|
||||||
useClass: MessageBrokerPublisher,
|
useExisting: MessageBrokerPublisher,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ const orms: Provider[] = [PrismaService];
|
||||||
...queryHandlers,
|
...queryHandlers,
|
||||||
...mappers,
|
...mappers,
|
||||||
...repositories,
|
...repositories,
|
||||||
...messageBrokers,
|
...messagePublishers,
|
||||||
...orms,
|
...orms,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { AuthenticationEntity } from '../core/domain/authentication.entity';
|
||||||
import { AuthenticationRepositoryPort } from '../core/application/ports/authentication.repository.port';
|
import { AuthenticationRepositoryPort } from '../core/application/ports/authentication.repository.port';
|
||||||
import { PrismaService } from './prisma.service';
|
import { PrismaService } from './prisma.service';
|
||||||
import { AuthenticationMapper } from '../authentication.mapper';
|
import { AuthenticationMapper } from '../authentication.mapper';
|
||||||
import { MESSAGE_PUBLISHER } from '@src/app.di-tokens';
|
import { AUTH_MESSAGE_PUBLISHER } from '../authentication.di-tokens';
|
||||||
|
|
||||||
type AuthenticationBaseModel = {
|
type AuthenticationBaseModel = {
|
||||||
uuid: string;
|
uuid: string;
|
||||||
|
@ -49,7 +49,7 @@ export class AuthenticationRepository
|
||||||
prisma: PrismaService,
|
prisma: PrismaService,
|
||||||
mapper: AuthenticationMapper,
|
mapper: AuthenticationMapper,
|
||||||
eventEmitter: EventEmitter2,
|
eventEmitter: EventEmitter2,
|
||||||
@Inject(MESSAGE_PUBLISHER)
|
@Inject(AUTH_MESSAGE_PUBLISHER)
|
||||||
protected readonly messagePublisher: MessagePublisherPort,
|
protected readonly messagePublisher: MessagePublisherPort,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
|
|
|
@ -6,11 +6,11 @@ import {
|
||||||
PrismaRepositoryBase,
|
PrismaRepositoryBase,
|
||||||
} from '@mobicoop/ddd-library';
|
} from '@mobicoop/ddd-library';
|
||||||
import { PrismaService } from './prisma.service';
|
import { PrismaService } from './prisma.service';
|
||||||
import { MESSAGE_PUBLISHER } from '@src/app.di-tokens';
|
|
||||||
import { UsernameEntity } from '../core/domain/username.entity';
|
import { UsernameEntity } from '../core/domain/username.entity';
|
||||||
import { UsernameRepositoryPort } from '../core/application/ports/username.repository.port';
|
import { UsernameRepositoryPort } from '../core/application/ports/username.repository.port';
|
||||||
import { UsernameMapper } from '../username.mapper';
|
import { UsernameMapper } from '../username.mapper';
|
||||||
import { Type } from '../core/domain/username.types';
|
import { Type } from '../core/domain/username.types';
|
||||||
|
import { AUTH_MESSAGE_PUBLISHER } from '../authentication.di-tokens';
|
||||||
|
|
||||||
type UsernameBaseModel = {
|
type UsernameBaseModel = {
|
||||||
username: string;
|
username: string;
|
||||||
|
@ -41,7 +41,7 @@ export class UsernameRepository
|
||||||
prisma: PrismaService,
|
prisma: PrismaService,
|
||||||
mapper: UsernameMapper,
|
mapper: UsernameMapper,
|
||||||
eventEmitter: EventEmitter2,
|
eventEmitter: EventEmitter2,
|
||||||
@Inject(MESSAGE_PUBLISHER)
|
@Inject(AUTH_MESSAGE_PUBLISHER)
|
||||||
protected readonly messagePublisher: MessagePublisherPort,
|
protected readonly messagePublisher: MessagePublisherPort,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { PrismaService } from '@modules/authentication/infrastructure/prisma.ser
|
||||||
import { AuthenticationRepository } from '@modules/authentication/infrastructure/authentication.repository';
|
import { AuthenticationRepository } from '@modules/authentication/infrastructure/authentication.repository';
|
||||||
import { AuthenticationMapper } from '@modules/authentication/authentication.mapper';
|
import { AuthenticationMapper } from '@modules/authentication/authentication.mapper';
|
||||||
import { EventEmitterModule } from '@nestjs/event-emitter';
|
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||||
import { MESSAGE_PUBLISHER } from '@src/app.di-tokens';
|
|
||||||
import {
|
import {
|
||||||
DatabaseErrorException,
|
DatabaseErrorException,
|
||||||
NotFoundException,
|
NotFoundException,
|
||||||
|
@ -14,6 +13,7 @@ import {
|
||||||
import { AuthenticationEntity } from '@modules/authentication/core/domain/authentication.entity';
|
import { AuthenticationEntity } from '@modules/authentication/core/domain/authentication.entity';
|
||||||
import { Type } from '@modules/authentication/core/domain/username.types';
|
import { Type } from '@modules/authentication/core/domain/username.types';
|
||||||
import { CreateAuthenticationProps } from '@modules/authentication/core/domain/authentication.types';
|
import { CreateAuthenticationProps } from '@modules/authentication/core/domain/authentication.types';
|
||||||
|
import { AUTH_MESSAGE_PUBLISHER } from '@modules/authentication/authentication.di-tokens';
|
||||||
|
|
||||||
const uuid = '165192d4-398a-4469-a16b-98c02cc6f531';
|
const uuid = '165192d4-398a-4469-a16b-98c02cc6f531';
|
||||||
|
|
||||||
|
@ -42,17 +42,6 @@ describe('AuthenticationRepository', () => {
|
||||||
let prismaService: PrismaService;
|
let prismaService: PrismaService;
|
||||||
let authenticationRepository: AuthenticationRepository;
|
let authenticationRepository: AuthenticationRepository;
|
||||||
|
|
||||||
// const createAuthentications = async (nbToCreate = 10) => {
|
|
||||||
// for (let i = 0; i < nbToCreate; i++) {
|
|
||||||
// await prismaService.auth.create({
|
|
||||||
// data: {
|
|
||||||
// uuid: v4(),
|
|
||||||
// password: bcrypt.hashSync(`password-${i}`, 10),
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
imports: [EventEmitterModule.forRoot()],
|
imports: [EventEmitterModule.forRoot()],
|
||||||
|
@ -61,7 +50,7 @@ describe('AuthenticationRepository', () => {
|
||||||
PrismaService,
|
PrismaService,
|
||||||
AuthenticationMapper,
|
AuthenticationMapper,
|
||||||
{
|
{
|
||||||
provide: MESSAGE_PUBLISHER,
|
provide: AUTH_MESSAGE_PUBLISHER,
|
||||||
useValue: mockMessagePublisher,
|
useValue: mockMessagePublisher,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -84,30 +73,6 @@ describe('AuthenticationRepository', () => {
|
||||||
await prismaService.auth.deleteMany();
|
await prismaService.auth.deleteMany();
|
||||||
});
|
});
|
||||||
|
|
||||||
// describe('findAll', () => {
|
|
||||||
// it('should return an empty data array', async () => {
|
|
||||||
// const res = await authenticationRepository.findAll();
|
|
||||||
// expect(res).toEqual({
|
|
||||||
// data: [],
|
|
||||||
// total: 0,
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
// it('should return a data array with 8 auths', async () => {
|
|
||||||
// await createAuthentications(8);
|
|
||||||
// const auths = await authenticationRepository.findAll();
|
|
||||||
// expect(auths.data.length).toBe(8);
|
|
||||||
// expect(auths.total).toBe(8);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// it('should return a data array limited to 10 authentications', async () => {
|
|
||||||
// await createAuthentications(20);
|
|
||||||
// const auths = await authenticationRepository.findAll();
|
|
||||||
// expect(auths.data.length).toBe(10);
|
|
||||||
// expect(auths.total).toBe(20);
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
describe('findOneById', () => {
|
describe('findOneById', () => {
|
||||||
it('should return an authentication', async () => {
|
it('should return an authentication', async () => {
|
||||||
const authToFind = await prismaService.auth.create({
|
const authToFind = await prismaService.auth.create({
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { TestingModule, Test } from '@nestjs/testing';
|
import { TestingModule, Test } from '@nestjs/testing';
|
||||||
import { PrismaService } from '@modules/authentication/infrastructure/prisma.service';
|
import { PrismaService } from '@modules/authentication/infrastructure/prisma.service';
|
||||||
import { EventEmitterModule } from '@nestjs/event-emitter';
|
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||||
import { MESSAGE_PUBLISHER } from '@src/app.di-tokens';
|
|
||||||
import {
|
import {
|
||||||
DatabaseErrorException,
|
DatabaseErrorException,
|
||||||
NotFoundException,
|
NotFoundException,
|
||||||
|
@ -15,6 +14,7 @@ import { UsernameRepository } from '@modules/authentication/infrastructure/usern
|
||||||
import { UsernameMapper } from '@modules/authentication/username.mapper';
|
import { UsernameMapper } from '@modules/authentication/username.mapper';
|
||||||
import * as bcrypt from 'bcrypt';
|
import * as bcrypt from 'bcrypt';
|
||||||
import { UsernameEntity } from '@modules/authentication/core/domain/username.entity';
|
import { UsernameEntity } from '@modules/authentication/core/domain/username.entity';
|
||||||
|
import { AUTH_MESSAGE_PUBLISHER } from '@modules/authentication/authentication.di-tokens';
|
||||||
|
|
||||||
const authUuid = 'a4524d22-7be3-46cd-8444-3145470476dc';
|
const authUuid = 'a4524d22-7be3-46cd-8444-3145470476dc';
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ describe('UsernameRepository', () => {
|
||||||
PrismaService,
|
PrismaService,
|
||||||
UsernameMapper,
|
UsernameMapper,
|
||||||
{
|
{
|
||||||
provide: MESSAGE_PUBLISHER,
|
provide: AUTH_MESSAGE_PUBLISHER,
|
||||||
useValue: mockMessagePublisher,
|
useValue: mockMessagePublisher,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
export interface CheckRepositoryPort {
|
|
||||||
healthCheck(): Promise<boolean>;
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
|
||||||
import {
|
|
||||||
HealthCheckError,
|
|
||||||
HealthCheckResult,
|
|
||||||
HealthIndicator,
|
|
||||||
HealthIndicatorResult,
|
|
||||||
} from '@nestjs/terminus';
|
|
||||||
import { CheckRepositoryPort } from '../ports/check-repository.port';
|
|
||||||
import {
|
|
||||||
AUTHENTICATION_REPOSITORY,
|
|
||||||
USERNAME_REPOSITORY,
|
|
||||||
} from '@modules/health/health.di-tokens';
|
|
||||||
import { MESSAGE_PUBLISHER } from '@src/app.di-tokens';
|
|
||||||
import { LOGGING_AUTHENTICATION_HEALTH_CRIT } from '@modules/health/health.constants';
|
|
||||||
import { MessagePublisherPort } from '@mobicoop/ddd-library';
|
|
||||||
import { AuthenticationRepositoryPort } from '@modules/authentication/core/application/ports/authentication.repository.port';
|
|
||||||
import { UsernameRepositoryPort } from '@modules/authentication/core/application/ports/username.repository.port';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class RepositoriesHealthIndicatorUseCase extends HealthIndicator {
|
|
||||||
private _checkRepositories: CheckRepositoryPort[];
|
|
||||||
constructor(
|
|
||||||
@Inject(AUTHENTICATION_REPOSITORY)
|
|
||||||
private readonly authenticationRepository: AuthenticationRepositoryPort,
|
|
||||||
@Inject(USERNAME_REPOSITORY)
|
|
||||||
private readonly usernameRepository: UsernameRepositoryPort,
|
|
||||||
@Inject(MESSAGE_PUBLISHER)
|
|
||||||
private readonly messagePublisher: MessagePublisherPort,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
this._checkRepositories = [authenticationRepository, usernameRepository];
|
|
||||||
}
|
|
||||||
isHealthy = async (key: string): Promise<HealthIndicatorResult> => {
|
|
||||||
try {
|
|
||||||
await Promise.all(
|
|
||||||
this._checkRepositories.map(
|
|
||||||
async (checkRepository: CheckRepositoryPort) => {
|
|
||||||
await checkRepository.healthCheck();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return this.getStatus(key, true);
|
|
||||||
} catch (error) {
|
|
||||||
const healthCheckResult: HealthCheckResult = error;
|
|
||||||
this.messagePublisher.publish(
|
|
||||||
LOGGING_AUTHENTICATION_HEALTH_CRIT,
|
|
||||||
JSON.stringify(healthCheckResult.error),
|
|
||||||
);
|
|
||||||
throw new HealthCheckError('Repository', {
|
|
||||||
repository: error.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
export const LOGGING_AUTHENTICATION_HEALTH_CRIT = 'logging.auth.health.crit';
|
|
|
@ -1,2 +0,0 @@
|
||||||
export const AUTHENTICATION_REPOSITORY = Symbol('AUTHENTICATION_REPOSITORY');
|
|
||||||
export const USERNAME_REPOSITORY = Symbol('USERNAME_REPOSITORY');
|
|
|
@ -1,45 +0,0 @@
|
||||||
import { Module, Provider } from '@nestjs/common';
|
|
||||||
import { HealthHttpController } from './interface/http-controllers/health.http.controller';
|
|
||||||
import { TerminusModule } from '@nestjs/terminus';
|
|
||||||
import { RepositoriesHealthIndicatorUseCase } from './core/application/usecases/repositories.health-indicator.usecase';
|
|
||||||
import { HealthGrpcController } from './interface/grpc-controllers/health.grpc.controller';
|
|
||||||
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
|
|
||||||
import { AuthenticationRepository } from '@modules/authentication/infrastructure/authentication.repository';
|
|
||||||
import {
|
|
||||||
AUTHENTICATION_REPOSITORY,
|
|
||||||
USERNAME_REPOSITORY,
|
|
||||||
} from './health.di-tokens';
|
|
||||||
import { UsernameRepository } from '@modules/authentication/infrastructure/username.repository';
|
|
||||||
import { MESSAGE_PUBLISHER } from '@src/app.di-tokens';
|
|
||||||
import { AuthenticationModule } from '@modules/authentication/authentication.module';
|
|
||||||
|
|
||||||
const grpcControllers = [HealthGrpcController];
|
|
||||||
|
|
||||||
const httpControllers = [HealthHttpController];
|
|
||||||
|
|
||||||
const useCases: Provider[] = [RepositoriesHealthIndicatorUseCase];
|
|
||||||
|
|
||||||
const repositories: Provider[] = [
|
|
||||||
{
|
|
||||||
provide: AUTHENTICATION_REPOSITORY,
|
|
||||||
useClass: AuthenticationRepository,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: USERNAME_REPOSITORY,
|
|
||||||
useClass: UsernameRepository,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const messageBrokers: Provider[] = [
|
|
||||||
{
|
|
||||||
provide: MESSAGE_PUBLISHER,
|
|
||||||
useClass: MessageBrokerPublisher,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [TerminusModule, AuthenticationModule],
|
|
||||||
controllers: [...grpcControllers, ...httpControllers],
|
|
||||||
providers: [...useCases, ...repositories, ...messageBrokers],
|
|
||||||
})
|
|
||||||
export class HealthModule {}
|
|
|
@ -1,42 +0,0 @@
|
||||||
import { Controller } from '@nestjs/common';
|
|
||||||
import { GrpcMethod } from '@nestjs/microservices';
|
|
||||||
import { RepositoriesHealthIndicatorUseCase } from '../../core/application/usecases/repositories.health-indicator.usecase';
|
|
||||||
|
|
||||||
export enum ServingStatus {
|
|
||||||
UNKNOWN = 0,
|
|
||||||
SERVING = 1,
|
|
||||||
NOT_SERVING = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
interface HealthCheckRequest {
|
|
||||||
service: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface HealthCheckResponse {
|
|
||||||
status: ServingStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Controller()
|
|
||||||
export class HealthGrpcController {
|
|
||||||
constructor(
|
|
||||||
private readonly repositoriesHealthIndicatorUseCase: RepositoriesHealthIndicatorUseCase,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
@GrpcMethod('Health', 'Check')
|
|
||||||
async check(
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
data?: HealthCheckRequest,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
metadata?: any,
|
|
||||||
): Promise<HealthCheckResponse> {
|
|
||||||
const healthCheck = await this.repositoriesHealthIndicatorUseCase.isHealthy(
|
|
||||||
'repositories',
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
status:
|
|
||||||
healthCheck['repositories'].status == 'up'
|
|
||||||
? ServingStatus.SERVING
|
|
||||||
: ServingStatus.NOT_SERVING,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
import { RepositoriesHealthIndicatorUseCase } from '@modules/health/core/application/usecases/repositories.health-indicator.usecase';
|
|
||||||
import { Controller, Get } from '@nestjs/common';
|
|
||||||
import {
|
|
||||||
HealthCheckService,
|
|
||||||
HealthCheck,
|
|
||||||
HealthCheckResult,
|
|
||||||
} from '@nestjs/terminus';
|
|
||||||
|
|
||||||
@Controller('health')
|
|
||||||
export class HealthHttpController {
|
|
||||||
constructor(
|
|
||||||
private readonly repositoriesHealthIndicatorUseCase: RepositoriesHealthIndicatorUseCase,
|
|
||||||
private readonly healthCheckService: HealthCheckService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
@Get()
|
|
||||||
@HealthCheck()
|
|
||||||
async check(): Promise<HealthCheckResult> {
|
|
||||||
try {
|
|
||||||
return await this.healthCheckService.check([
|
|
||||||
async () =>
|
|
||||||
this.repositoriesHealthIndicatorUseCase.isHealthy('repositories'),
|
|
||||||
]);
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
import { RepositoriesHealthIndicatorUseCase } from '@modules/health/core/application/usecases/repositories.health-indicator.usecase';
|
|
||||||
import {
|
|
||||||
HealthGrpcController,
|
|
||||||
ServingStatus,
|
|
||||||
} from '@modules/health/interface/grpc-controllers/health.grpc.controller';
|
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
|
|
||||||
const mockRepositoriesHealthIndicatorUseCase = {
|
|
||||||
isHealthy: jest
|
|
||||||
.fn()
|
|
||||||
.mockImplementationOnce(() => ({
|
|
||||||
repositories: {
|
|
||||||
status: 'up',
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
.mockImplementationOnce(() => ({
|
|
||||||
repositories: {
|
|
||||||
status: 'down',
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('Health Grpc Controller', () => {
|
|
||||||
let healthGrpcController: HealthGrpcController;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
providers: [
|
|
||||||
{
|
|
||||||
provide: RepositoriesHealthIndicatorUseCase,
|
|
||||||
useValue: mockRepositoriesHealthIndicatorUseCase,
|
|
||||||
},
|
|
||||||
HealthGrpcController,
|
|
||||||
],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
healthGrpcController =
|
|
||||||
module.get<HealthGrpcController>(HealthGrpcController);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(healthGrpcController).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a Serving status ', async () => {
|
|
||||||
jest.spyOn(mockRepositoriesHealthIndicatorUseCase, 'isHealthy');
|
|
||||||
const servingStatus: { status: ServingStatus } =
|
|
||||||
await healthGrpcController.check();
|
|
||||||
expect(servingStatus).toEqual({
|
|
||||||
status: ServingStatus.SERVING,
|
|
||||||
});
|
|
||||||
expect(
|
|
||||||
mockRepositoriesHealthIndicatorUseCase.isHealthy,
|
|
||||||
).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a Not Serving status ', async () => {
|
|
||||||
jest.spyOn(mockRepositoriesHealthIndicatorUseCase, 'isHealthy');
|
|
||||||
const servingStatus: { status: ServingStatus } =
|
|
||||||
await healthGrpcController.check();
|
|
||||||
expect(servingStatus).toEqual({
|
|
||||||
status: ServingStatus.NOT_SERVING,
|
|
||||||
});
|
|
||||||
expect(
|
|
||||||
mockRepositoriesHealthIndicatorUseCase.isHealthy,
|
|
||||||
).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,90 +0,0 @@
|
||||||
import { RepositoriesHealthIndicatorUseCase } from '@modules/health/core/application/usecases/repositories.health-indicator.usecase';
|
|
||||||
import { HealthHttpController } from '@modules/health/interface/http-controllers/health.http.controller';
|
|
||||||
import { HealthCheckResult, HealthCheckService } from '@nestjs/terminus';
|
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
|
|
||||||
const mockHealthCheckService = {
|
|
||||||
check: jest
|
|
||||||
.fn()
|
|
||||||
.mockImplementationOnce(() => ({
|
|
||||||
status: 'ok',
|
|
||||||
info: {
|
|
||||||
repositories: {
|
|
||||||
status: 'up',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
error: {},
|
|
||||||
details: {
|
|
||||||
repositories: {
|
|
||||||
status: 'up',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
.mockImplementationOnce(() => ({
|
|
||||||
status: 'error',
|
|
||||||
info: {},
|
|
||||||
error: {
|
|
||||||
repository:
|
|
||||||
"\nInvalid `prisma.$queryRaw()` invocation:\n\n\nCan't reach database server at `v3-db`:`5432`\n\nPlease make sure your database server is running at `v3-db`:`5432`.",
|
|
||||||
},
|
|
||||||
details: {
|
|
||||||
repository:
|
|
||||||
"\nInvalid `prisma.$queryRaw()` invocation:\n\n\nCan't reach database server at `v3-db`:`5432`\n\nPlease make sure your database server is running at `v3-db`:`5432`.",
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockRepositoriesHealthIndicatorUseCase = {
|
|
||||||
isHealthy: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('Health Http Controller', () => {
|
|
||||||
let healthHttpController: HealthHttpController;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
providers: [
|
|
||||||
{
|
|
||||||
provide: HealthCheckService,
|
|
||||||
useValue: mockHealthCheckService,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: RepositoriesHealthIndicatorUseCase,
|
|
||||||
useValue: mockRepositoriesHealthIndicatorUseCase,
|
|
||||||
},
|
|
||||||
HealthHttpController,
|
|
||||||
],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
healthHttpController =
|
|
||||||
module.get<HealthHttpController>(HealthHttpController);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(healthHttpController).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return an HealthCheckResult with Ok status ', async () => {
|
|
||||||
jest.spyOn(mockHealthCheckService, 'check');
|
|
||||||
jest.spyOn(mockRepositoriesHealthIndicatorUseCase, 'isHealthy');
|
|
||||||
|
|
||||||
const healthCheckResult: HealthCheckResult =
|
|
||||||
await healthHttpController.check();
|
|
||||||
expect(healthCheckResult.status).toBe('ok');
|
|
||||||
expect(mockHealthCheckService.check).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return an HealthCheckResult with Error status ', async () => {
|
|
||||||
jest.spyOn(mockHealthCheckService, 'check');
|
|
||||||
jest.spyOn(mockRepositoriesHealthIndicatorUseCase, 'isHealthy');
|
|
||||||
|
|
||||||
const healthCheckResult: HealthCheckResult =
|
|
||||||
await healthHttpController.check();
|
|
||||||
expect(healthCheckResult.status).toBe('error');
|
|
||||||
expect(mockHealthCheckService.check).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,84 +0,0 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { HealthCheckError, HealthIndicatorResult } from '@nestjs/terminus';
|
|
||||||
import { RepositoriesHealthIndicatorUseCase } from '../../core/application/usecases/repositories.health-indicator.usecase';
|
|
||||||
import { MESSAGE_PUBLISHER } from '@src/app.di-tokens';
|
|
||||||
import { DatabaseErrorException } from '@mobicoop/ddd-library';
|
|
||||||
import {
|
|
||||||
AUTHENTICATION_REPOSITORY,
|
|
||||||
USERNAME_REPOSITORY,
|
|
||||||
} from '@modules/health/health.di-tokens';
|
|
||||||
|
|
||||||
const mockAuthenticationRepository = {
|
|
||||||
healthCheck: jest
|
|
||||||
.fn()
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return Promise.resolve(true);
|
|
||||||
})
|
|
||||||
.mockImplementation(() => {
|
|
||||||
throw new DatabaseErrorException('An error occured in the database');
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockUsernameRepository = {
|
|
||||||
healthCheck: jest
|
|
||||||
.fn()
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return Promise.resolve(true);
|
|
||||||
})
|
|
||||||
.mockImplementation(() => {
|
|
||||||
throw new DatabaseErrorException('An error occured in the database');
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockMessagePublisher = {
|
|
||||||
publish: jest.fn().mockImplementation(),
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('RepositoriesHealthIndicatorUseCase', () => {
|
|
||||||
let repositoriesHealthIndicatorUseCase: RepositoriesHealthIndicatorUseCase;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
providers: [
|
|
||||||
RepositoriesHealthIndicatorUseCase,
|
|
||||||
{
|
|
||||||
provide: AUTHENTICATION_REPOSITORY,
|
|
||||||
useValue: mockAuthenticationRepository,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: USERNAME_REPOSITORY,
|
|
||||||
useValue: mockUsernameRepository,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: MESSAGE_PUBLISHER,
|
|
||||||
useValue: mockMessagePublisher,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
repositoriesHealthIndicatorUseCase =
|
|
||||||
module.get<RepositoriesHealthIndicatorUseCase>(
|
|
||||||
RepositoriesHealthIndicatorUseCase,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(repositoriesHealthIndicatorUseCase).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('execute', () => {
|
|
||||||
it('should check health successfully', async () => {
|
|
||||||
const healthIndicatorResult: HealthIndicatorResult =
|
|
||||||
await repositoriesHealthIndicatorUseCase.isHealthy('repositories');
|
|
||||||
expect(healthIndicatorResult['repositories'].status).toBe('up');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an error if database is unavailable', async () => {
|
|
||||||
jest.spyOn(mockMessagePublisher, 'publish');
|
|
||||||
await expect(
|
|
||||||
repositoriesHealthIndicatorUseCase.isHealthy('repositories'),
|
|
||||||
).rejects.toBeInstanceOf(HealthCheckError);
|
|
||||||
expect(mockMessagePublisher.publish).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { Module, Provider } from '@nestjs/common';
|
||||||
|
import { MESSAGE_PUBLISHER } from './message-publisher.di-tokens';
|
||||||
|
import { MessageBrokerPublisher } from '@mobicoop/message-broker-module';
|
||||||
|
|
||||||
|
const messagePublishers: Provider[] = [
|
||||||
|
{
|
||||||
|
provide: MESSAGE_PUBLISHER,
|
||||||
|
useClass: MessageBrokerPublisher,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [...messagePublishers],
|
||||||
|
exports: [MESSAGE_PUBLISHER],
|
||||||
|
})
|
||||||
|
export class MessagePublisherModule {}
|
Loading…
Reference in New Issue