use configuration package as middleware

This commit is contained in:
sbriat
2023-10-25 16:03:17 +02:00
parent efb35d6d2b
commit 5094e27ad1
28 changed files with 558 additions and 747 deletions

View File

@@ -4,6 +4,10 @@ import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { HEALTH_CRITICAL_LOGGING_KEY, SERVICE_NAME } from './app.constants';
import { MessagePublisherPort } from '@mobicoop/ddd-library';
import {
ConfigurationModuleOptions,
ConfigurationModule as ConfigurationModulePackage,
} from '@mobicoop/configuration-module';
import { MESSAGE_PUBLISHER } from '@modules/messager/messager.di-tokens';
import { ConfigurationModule } from '@modules/configuration/configuration.module';
import { EventEmitterModule } from '@nestjs/event-emitter';
@@ -11,7 +15,6 @@ import brokerConfig from './config/broker.config';
import carpoolConfig from './config/carpool.config';
import paginationConfig from './config/pagination.config';
import serviceConfig from './config/service.config';
import { RedisModule, RedisModuleOptions } from '@songkeys/nestjs-redis';
import redisConfig from './config/redis.config';
import { Transport } from '@nestjs/microservices';
@@ -49,18 +52,16 @@ import { Transport } from '@nestjs/microservices';
],
}),
}),
RedisModule.forRootAsync({
ConfigurationModulePackage.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (
configService: ConfigService,
): Promise<RedisModuleOptions> => {
): Promise<ConfigurationModuleOptions> => {
return {
config: {
host: configService.get<string>('redis.host') as string,
port: configService.get<number>('redis.port') as number,
password: configService.get<string>('redis.password'),
},
host: configService.get<string>('redis.host') as string,
port: configService.get<number>('redis.port') as number,
password: configService.get<string>('redis.password'),
};
},
}),

View File

@@ -1,5 +1,13 @@
import { registerAs } from '@nestjs/config';
export interface CarpoolConfig {
departureTimeMargin: number;
role: string;
seatsProposed: number;
seatsRequested: number;
strictFrequency: boolean;
}
export default registerAs('carpool', () => ({
departureTimeMargin: process.env.DEPARTURE_TIME_MARGIN
? parseInt(process.env.DEPARTURE_TIME_MARGIN, 10)

View File

@@ -1,5 +1,9 @@
import { registerAs } from '@nestjs/config';
export interface PaginationConfig {
perPage: number;
}
export default registerAs('pagination', () => ({
perPage: process.env.PER_PAGE ? parseInt(process.env.PER_PAGE, 10) : 10,
}));

View File

@@ -1,55 +1,20 @@
import { Mapper } from '@mobicoop/ddd-library';
import { Injectable } from '@nestjs/common';
import { ConfigurationEntity } from './core/domain/configuration.entity';
import { ConfigurationResponseDto } from './interface/dtos/configuration.response.dto';
import { ConfigurationDomain } from './core/domain/configuration.types';
import {
ConfigurationReadModel,
ConfigurationWriteModel,
} from './infrastructure/configuration.repository';
import { v4 } from 'uuid';
ConfigurationIdentifier,
ConfigurationValue,
} from '@mobicoop/configuration-module';
@Injectable()
export class ConfigurationMapper
implements
Mapper<
ConfigurationEntity,
ConfigurationReadModel,
ConfigurationWriteModel,
ConfigurationResponseDto
>
{
toPersistence = (entity: ConfigurationEntity): ConfigurationWriteModel => {
const copy = entity.getProps();
const record: ConfigurationWriteModel = {
key: `${copy.identifier.domain}:${copy.identifier.key}`,
value: copy.value,
};
return record;
};
toDomain = (record: ConfigurationReadModel): ConfigurationEntity => {
const entity = new ConfigurationEntity({
id: v4(),
createdAt: new Date(),
updatedAt: new Date(),
props: {
identifier: {
domain: record.key.split(':')[0] as ConfigurationDomain,
key: record.key.split(':')[1],
},
value: record.value,
},
});
return entity;
};
toResponse = (entity: ConfigurationEntity): ConfigurationResponseDto => {
const props = entity.getProps();
const response = new ConfigurationResponseDto(entity);
response.domain = props.identifier.domain;
response.key = props.identifier.key;
response.value = props.value;
export class ConfigurationMapper {
toResponse = (
configurationIdentifier: ConfigurationIdentifier,
configurationValue: ConfigurationValue,
): ConfigurationResponseDto => {
const response = new ConfigurationResponseDto();
response.domain = configurationIdentifier.domain;
response.key = configurationIdentifier.key;
response.value = configurationValue;
return response;
};
}

View File

@@ -7,7 +7,7 @@ import { GetConfigurationQueryHandler } from './core/application/queries/get-con
import { ConfigurationMapper } from './configuration.mapper';
import { CONFIGURATION_REPOSITORY } from './configuration.di-tokens';
import { PopulateService } from './core/application/services/populate.service';
import { ConfigurationRepository } from './infrastructure/configuration.repository';
import { ConfigurationRepository } from '@mobicoop/configuration-module';
const grpcControllers = [
GetConfigurationGrpcController,

View File

@@ -2,18 +2,23 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { Inject } from '@nestjs/common';
import { SetConfigurationCommand } from './set-configuration.command';
import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
import { ConfigurationDomain } from '@modules/configuration/core/domain/configuration.types';
import { ConfigurationRepositoryPort } from '../../ports/configuration.repository.port';
import {
ConfigurationDomain,
ConfigurationIdentifier,
SetConfigurationRepositoryPort,
} from '@mobicoop/configuration-module';
@CommandHandler(SetConfigurationCommand)
export class SetConfigurationService implements ICommandHandler {
constructor(
@Inject(CONFIGURATION_REPOSITORY)
private readonly configurationRepository: ConfigurationRepositoryPort,
private readonly configurationRepository: SetConfigurationRepositoryPort,
) {}
async execute(command: SetConfigurationCommand): Promise<void> {
await this.configurationRepository.set(
async execute(
command: SetConfigurationCommand,
): Promise<ConfigurationIdentifier> {
return await this.configurationRepository.set(
{
domain: command.domain as ConfigurationDomain,
key: command.key,

View File

@@ -1,13 +0,0 @@
import { ConfigurationEntity } from '../../domain/configuration.entity';
import {
ConfigurationIdentifier,
ConfigurationValue,
} from '../../domain/configuration.types';
export interface ConfigurationRepositoryPort {
get(identifier: ConfigurationIdentifier): Promise<ConfigurationEntity>;
set(
identifier: ConfigurationIdentifier,
value: ConfigurationValue,
): Promise<void>;
}

View File

@@ -2,17 +2,19 @@ import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
import { GetConfigurationQuery } from './get-configuration.query';
import { Inject } from '@nestjs/common';
import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
import { ConfigurationRepositoryPort } from '../../ports/configuration.repository.port';
import { ConfigurationDomain } from '@modules/configuration/core/domain/configuration.types';
import {
ConfigurationDomain,
ConfigurationValue,
GetConfigurationRepositoryPort,
} from '@mobicoop/configuration-module';
@QueryHandler(GetConfigurationQuery)
export class GetConfigurationQueryHandler implements IQueryHandler {
constructor(
@Inject(CONFIGURATION_REPOSITORY)
private readonly configurationRepository: ConfigurationRepositoryPort,
private readonly configurationRepository: GetConfigurationRepositoryPort,
) {}
async execute(query: GetConfigurationQuery): Promise<ConfigurationEntity> {
async execute(query: GetConfigurationQuery): Promise<ConfigurationValue> {
return await this.configurationRepository.get({
domain: query.domain as ConfigurationDomain,
key: query.key,

View File

@@ -1,19 +1,23 @@
import {
ConfigurationDomain,
GetConfigurationRepositoryPort,
NotFoundException,
SetConfigurationRepositoryPort,
} from '@mobicoop/configuration-module';
import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
import { Inject, Injectable, OnApplicationBootstrap } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ConfigurationRepositoryPort } from '../ports/configuration.repository.port';
import {
CarpoolConfig,
ConfigurationDomain,
PaginationConfig,
} from '../../domain/configuration.types';
import { NotFoundException } from '@mobicoop/ddd-library';
import { CarpoolConfig } from '@src/config/carpool.config';
import { PaginationConfig } from '@src/config/pagination.config';
@Injectable()
export class PopulateService implements OnApplicationBootstrap {
constructor(
@Inject(CONFIGURATION_REPOSITORY)
private readonly configurationRepository: ConfigurationRepositoryPort,
private readonly getConfigurationRepository: GetConfigurationRepositoryPort,
@Inject(CONFIGURATION_REPOSITORY)
private readonly setConfigurationRepository: SetConfigurationRepositoryPort,
private readonly configService: ConfigService,
) {}
@@ -42,13 +46,13 @@ export class PopulateService implements OnApplicationBootstrap {
let key: keyof typeof config;
for (key in config) {
try {
await this.configurationRepository.get({
await this.getConfigurationRepository.get({
domain,
key,
});
} catch (error: any) {
if (error instanceof NotFoundException) {
this.configurationRepository.set(
this.setConfigurationRepository.set(
{
domain,
key,

View File

@@ -1,21 +0,0 @@
import { AggregateRoot, AggregateID } from '@mobicoop/ddd-library';
import { v4 } from 'uuid';
import {
ConfigurationProps,
CreateConfigurationProps,
} from './configuration.types';
export class ConfigurationEntity extends AggregateRoot<ConfigurationProps> {
protected readonly _id: AggregateID;
static create = (create: CreateConfigurationProps): ConfigurationEntity => {
const id = v4();
const props: ConfigurationProps = { ...create };
const configuration = new ConfigurationEntity({ id, props });
return configuration;
};
validate(): void {
// entity business rules validation to protect it's invariant before saving entity to a database
}
}

View File

@@ -1,36 +0,0 @@
// All properties that a Configuration has
export interface ConfigurationProps {
identifier: ConfigurationIdentifier;
value: ConfigurationValue;
}
// Properties that are needed for a Configuration creation
export interface CreateConfigurationProps {
identifier: ConfigurationIdentifier;
value: ConfigurationValue;
}
export enum ConfigurationDomain {
CARPOOL = 'CARPOOL',
PAGINATION = 'PAGINATION',
}
export type ConfigurationIdentifier = {
domain: ConfigurationDomain;
key: ConfigurationKey;
};
export type ConfigurationKey = string;
export type ConfigurationValue = string;
export interface CarpoolConfig {
departureTimeMargin: number;
role: string;
seatsProposed: number;
seatsRequested: number;
strictFrequency: boolean;
}
export interface PaginationConfig {
perPage: number;
}

View File

@@ -1,47 +0,0 @@
import { Injectable } from '@nestjs/common';
import { ConfigurationRepositoryPort } from '../core/application/ports/configuration.repository.port';
import { InjectRedis } from '@songkeys/nestjs-redis';
import { Redis } from 'ioredis';
import {
ConfigurationIdentifier,
ConfigurationValue,
} from '../core/domain/configuration.types';
import { ConfigurationEntity } from '../core/domain/configuration.entity';
import { ConfigurationMapper } from '../configuration.mapper';
import { NotFoundException } from '@mobicoop/ddd-library';
export type ConfigurationReadModel = {
key: string;
value: string;
};
export type ConfigurationWriteModel = ConfigurationReadModel;
@Injectable()
export class ConfigurationRepository implements ConfigurationRepositoryPort {
constructor(
@InjectRedis() private readonly redis: Redis,
private readonly mapper: ConfigurationMapper,
) {}
get = async (
identifier: ConfigurationIdentifier,
): Promise<ConfigurationEntity> => {
const key: string = `${identifier.domain}:${identifier.key}`;
const value: ConfigurationValue | null = await this.redis.get(key);
if (!value)
throw new NotFoundException(
`Configuration item not found for key ${key}`,
);
return this.mapper.toDomain({
key,
value,
});
};
set = async (
identifier: ConfigurationIdentifier,
value: ConfigurationValue,
): Promise<void> => {
await this.redis.set(`${identifier.domain}:${identifier.key}`, value);
};
}

View File

@@ -1,6 +1,4 @@
import { ResponseBase } from '@mobicoop/ddd-library';
export class ConfigurationResponseDto extends ResponseBase {
export class ConfigurationResponseDto {
domain: string;
key: string;
value: string;

View File

@@ -1,4 +1,4 @@
import { ConfigurationDomain } from '@modules/configuration/core/domain/configuration.types';
import { ConfigurationDomain } from '@mobicoop/configuration-module';
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
export class GetConfigurationRequestDto {

View File

@@ -1,4 +1,4 @@
import { ConfigurationDomain } from '@modules/configuration/core/domain/configuration.types';
import { ConfigurationDomain } from '@mobicoop/configuration-module';
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
export class SetConfigurationRequestDto {

View File

@@ -1,15 +1,17 @@
import { Controller, UsePipes } from '@nestjs/common';
import { QueryBus } from '@nestjs/cqrs';
import { GrpcMethod, RpcException } from '@nestjs/microservices';
import { NotFoundException } from '@mobicoop/ddd-library';
import { RpcExceptionCode } from '@mobicoop/ddd-library';
import { RpcValidationPipe } from '@mobicoop/ddd-library';
import { GRPC_SERVICE_NAME } from '@src/app.constants';
import { ConfigurationMapper } from '@modules/configuration/configuration.mapper';
import { GetConfigurationRequestDto } from './dtos/get-configuration.request.dto';
import { ConfigurationResponseDto } from '../dtos/configuration.response.dto';
import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
import { GetConfigurationQuery } from '@modules/configuration/core/application/queries/get-configuration/get-configuration.query';
import {
NotFoundException,
RpcExceptionCode,
RpcValidationPipe,
} from '@mobicoop/ddd-library';
import { ConfigurationValue } from '@mobicoop/configuration-module';
@UsePipes(
new RpcValidationPipe({
@@ -29,10 +31,11 @@ export class GetConfigurationGrpcController {
data: GetConfigurationRequestDto,
): Promise<ConfigurationResponseDto> {
try {
const configuration: ConfigurationEntity = await this.queryBus.execute(
new GetConfigurationQuery(data.domain, data.key),
);
return this.mapper.toResponse(configuration);
const configurationValue: ConfigurationValue =
await this.queryBus.execute(
new GetConfigurationQuery(data.domain, data.key),
);
return this.mapper.toResponse(data, configurationValue);
} catch (e) {
if (e instanceof NotFoundException) {
throw new RpcException({

View File

@@ -1,11 +1,11 @@
import { Controller, UsePipes } from '@nestjs/common';
import { CommandBus } from '@nestjs/cqrs';
import { GrpcMethod, RpcException } from '@nestjs/microservices';
import { RpcExceptionCode } from '@mobicoop/ddd-library';
import { RpcValidationPipe } from '@mobicoop/ddd-library';
import { RpcExceptionCode, RpcValidationPipe } from '@mobicoop/ddd-library';
import { GRPC_SERVICE_NAME } from '@src/app.constants';
import { SetConfigurationRequestDto } from './dtos/set-configuration.request.dto';
import { SetConfigurationCommand } from '@modules/configuration/core/application/commands/set-configuration/set-configuration.command';
import { ConfigurationIdentifier } from '@mobicoop/configuration-module';
@UsePipes(
new RpcValidationPipe({
@@ -20,11 +20,13 @@ export class SetConfigurationGrpcController {
@GrpcMethod(GRPC_SERVICE_NAME, 'Set')
async set(
setConfigurationRequestDto: SetConfigurationRequestDto,
): Promise<void> {
): Promise<ConfigurationIdentifier> {
try {
await this.commandBus.execute(
new SetConfigurationCommand(setConfigurationRequestDto),
);
const configurationIdentifier: ConfigurationIdentifier =
await this.commandBus.execute(
new SetConfigurationCommand(setConfigurationRequestDto),
);
return configurationIdentifier;
} catch (error: any) {
throw new RpcException({
code: RpcExceptionCode.UNKNOWN,

View File

@@ -1,31 +1,8 @@
import { ConfigurationDomain } from '@mobicoop/configuration-module';
import { ConfigurationMapper } from '@modules/configuration/configuration.mapper';
import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
import { ConfigurationDomain } from '@modules/configuration/core/domain/configuration.types';
import {
ConfigurationReadModel,
ConfigurationWriteModel,
} from '@modules/configuration/infrastructure/configuration.repository';
import { ConfigurationResponseDto } from '@modules/configuration/interface/dtos/configuration.response.dto';
import { Test } from '@nestjs/testing';
const now = new Date('2023-06-21 06:00:00');
const configurationEntity: ConfigurationEntity = new ConfigurationEntity({
id: 'c160cf8c-f057-4962-841f-3ad68346df44',
props: {
identifier: {
domain: ConfigurationDomain.CARPOOL,
key: 'seatsProposed',
},
value: '3',
},
createdAt: now,
updatedAt: now,
});
const configurationReadModel: ConfigurationReadModel = {
key: 'AD:seatsProposed',
value: '4',
};
describe('Configuration Mapper', () => {
let configurationMapper: ConfigurationMapper;
@@ -40,22 +17,16 @@ describe('Configuration Mapper', () => {
expect(configurationMapper).toBeDefined();
});
it('should map domain entity to persistence data', async () => {
const mapped: ConfigurationWriteModel =
configurationMapper.toPersistence(configurationEntity);
it('should map configuration to response', async () => {
const mapped: ConfigurationResponseDto = configurationMapper.toResponse(
{
domain: ConfigurationDomain.CARPOOL,
key: 'seatsProposed',
},
'3',
);
expect(mapped.domain).toBe('CARPOOL');
expect(mapped.key).toBe('seatsProposed');
expect(mapped.value).toBe('3');
});
it('should map persisted data to domain entity', async () => {
const mapped: ConfigurationEntity = configurationMapper.toDomain(
configurationReadModel,
);
expect(mapped.getProps().value).toBe('4');
});
it('should map domain entity to response', async () => {
const mapped: ConfigurationResponseDto =
configurationMapper.toResponse(configurationEntity);
expect(mapped.id).toBe('c160cf8c-f057-4962-841f-3ad68346df44');
});
});

View File

@@ -1,23 +0,0 @@
import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
import {
CreateConfigurationProps,
ConfigurationDomain,
} from '@modules/configuration/core/domain/configuration.types';
const createConfigurationProps: CreateConfigurationProps = {
identifier: {
domain: ConfigurationDomain.CARPOOL,
key: 'seatsProposed',
},
value: '3',
};
describe('Configuration entity create', () => {
it('should create a new configuration entity', async () => {
const configurationEntity: ConfigurationEntity = ConfigurationEntity.create(
createConfigurationProps,
);
expect(configurationEntity.id.length).toBe(36);
expect(configurationEntity.getProps().value).toBe('3');
});
});

View File

@@ -1,26 +1,16 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
import { ConfigurationDomain } from '@modules/configuration/core/domain/configuration.types';
import { GetConfigurationQueryHandler } from '@modules/configuration/core/application/queries/get-configuration/get-configuration.query-handler';
import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
import { GetConfigurationQuery } from '@modules/configuration/core/application/queries/get-configuration/get-configuration.query';
import {
ConfigurationDomain,
ConfigurationValue,
} from '@mobicoop/configuration-module';
const now = new Date('2023-06-21 06:00:00');
const configuration: ConfigurationEntity = new ConfigurationEntity({
id: 'c160cf8c-f057-4962-841f-3ad68346df44',
props: {
identifier: {
domain: ConfigurationDomain.CARPOOL,
key: 'seatsProposed',
},
value: '3',
},
createdAt: now,
updatedAt: now,
});
const configurationValue: ConfigurationValue = '3';
const mockConfigurationRepository = {
get: jest.fn().mockImplementation(() => configuration),
get: jest.fn().mockImplementation(() => configurationValue),
};
describe('Get Configuration Query Handler', () => {
@@ -47,14 +37,14 @@ describe('Get Configuration Query Handler', () => {
});
describe('execution', () => {
it('should return a configuration item', async () => {
it('should return a configuration value', async () => {
const getConfigurationQuery = new GetConfigurationQuery(
ConfigurationDomain.CARPOOL,
'seatsProposed',
);
const configuration: ConfigurationEntity =
const configurationValue: ConfigurationValue =
await getConfigurationQueryHandler.execute(getConfigurationQuery);
expect(configuration.getProps().value).toBe('3');
expect(configurationValue).toBe('3');
});
});
});

View File

@@ -1,29 +1,13 @@
import { NotFoundException } from '@mobicoop/ddd-library';
import { NotFoundException } from '@mobicoop/configuration-module';
import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
import { PopulateService } from '@modules/configuration/core/application/services/populate.service';
import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
import { ConfigurationDomain } from '@modules/configuration/core/domain/configuration.types';
import { ConfigService } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
const mockConfigurationRepository = {
get: jest
.fn()
.mockImplementationOnce(
() =>
new ConfigurationEntity({
id: '001199d4-7187-4e83-a044-12159cba2e33',
props: {
identifier: {
domain: ConfigurationDomain.CARPOOL,
key: 'someKey',
},
value: 'someValue',
},
createdAt: new Date('2023-10-23'),
updatedAt: new Date('2023-10-23'),
}),
)
.mockImplementationOnce(() => 'someValue')
.mockImplementationOnce(() => {
throw new NotFoundException('Configuration not found');
}),

View File

@@ -1,10 +1,9 @@
import { Test, TestingModule } from '@nestjs/testing';
import { SetConfigurationRequestDto } from '@modules/configuration/interface/grpc-controllers/dtos/set-configuration.request.dto';
import { ConfigurationDomain } from '@modules/configuration/core/domain/configuration.types';
import { SetConfigurationService } from '@modules/configuration/core/application/commands/set-configuration/set-configuration.service';
import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
import { SetConfigurationCommand } from '@modules/configuration/core/application/commands/set-configuration/set-configuration.command';
import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
import { ConfigurationDomain } from '@mobicoop/configuration-module';
const setConfigurationRequest: SetConfigurationRequestDto = {
domain: ConfigurationDomain.CARPOOL,
@@ -50,16 +49,10 @@ describe('Set Configuration Service', () => {
);
it('should set an existing configuration item', async () => {
jest.spyOn(mockConfigurationRepository, 'set');
ConfigurationEntity.create = jest.fn().mockReturnValue({
id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
});
await setConfigurationService.execute(setConfigurationCommand);
expect(mockConfigurationRepository.set).toHaveBeenCalledTimes(1);
});
it('should throw an error if something bad happens on configuration item update', async () => {
ConfigurationEntity.create = jest.fn().mockReturnValue({
id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
});
await expect(
setConfigurationService.execute(setConfigurationCommand),
).rejects.toBeInstanceOf(Error);

View File

@@ -1,93 +0,0 @@
import { NotFoundException } from '@mobicoop/ddd-library';
import { ConfigurationMapper } from '@modules/configuration/configuration.mapper';
import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
import { ConfigurationDomain } from '@modules/configuration/core/domain/configuration.types';
import { ConfigurationRepository } from '@modules/configuration/infrastructure/configuration.repository';
import { Test, TestingModule } from '@nestjs/testing';
import { getRedisToken } from '@songkeys/nestjs-redis';
const mockRedis = {
get: jest
.fn()
.mockImplementationOnce(() => '1')
.mockImplementation(() => null),
set: jest.fn().mockImplementation(),
};
const mockConfigurationMapper = {
toDomain: jest.fn().mockImplementation(
() =>
new ConfigurationEntity({
id: '001199d4-7187-4e83-a044-12159cba2e33',
props: {
identifier: {
domain: ConfigurationDomain.CARPOOL,
key: 'seatsProposed',
},
value: '1',
},
createdAt: new Date('2023-10-23'),
updatedAt: new Date('2023-10-23'),
}),
),
};
describe('Configuration Repository', () => {
let configurationRepository: ConfigurationRepository;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: getRedisToken('default'),
useValue: mockRedis,
},
{
provide: ConfigurationMapper,
useValue: mockConfigurationMapper,
},
ConfigurationRepository,
],
}).compile();
configurationRepository = module.get<ConfigurationRepository>(
ConfigurationRepository,
);
});
it('should be defined', () => {
expect(configurationRepository).toBeDefined();
});
describe('interact', () => {
it('should get a value', async () => {
expect(
(
await configurationRepository.get({
domain: ConfigurationDomain.CARPOOL,
key: 'seatsProposed',
})
).getProps().value,
).toBe('1');
});
it('should throw if configuration is not found', async () => {
await expect(
configurationRepository.get({
domain: ConfigurationDomain.CARPOOL,
key: 'seatsProposed',
}),
).rejects.toBeInstanceOf(NotFoundException);
});
it('should set a value', async () => {
expect(
await configurationRepository.set(
{
domain: ConfigurationDomain.CARPOOL,
key: 'seatsProposed',
},
'3',
),
).toBeUndefined();
});
});
});

View File

@@ -1,7 +1,6 @@
import { NotFoundException } from '@mobicoop/ddd-library';
import { RpcExceptionCode } from '@mobicoop/ddd-library';
import { ConfigurationDomain } from '@mobicoop/configuration-module';
import { NotFoundException, RpcExceptionCode } from '@mobicoop/ddd-library';
import { ConfigurationMapper } from '@modules/configuration/configuration.mapper';
import { ConfigurationDomain } from '@modules/configuration/core/domain/configuration.types';
import { GetConfigurationGrpcController } from '@modules/configuration/interface/grpc-controllers/get-configuration.grpc.controller';
import { QueryBus } from '@nestjs/cqrs';
import { RpcException } from '@nestjs/microservices';

View File

@@ -1,5 +1,8 @@
import {
ConfigurationDomain,
ConfigurationIdentifier,
} from '@mobicoop/configuration-module';
import { RpcExceptionCode } from '@mobicoop/ddd-library';
import { ConfigurationDomain } from '@modules/configuration/core/domain/configuration.types';
import { SetConfigurationRequestDto } from '@modules/configuration/interface/grpc-controllers/dtos/set-configuration.request.dto';
import { SetConfigurationGrpcController } from '@modules/configuration/interface/grpc-controllers/set-configuration.grpc.controller';
import { CommandBus } from '@nestjs/cqrs';
@@ -15,7 +18,10 @@ const setConfigurationRequest: SetConfigurationRequestDto = {
const mockCommandBus = {
execute: jest
.fn()
.mockImplementationOnce(() => '200d61a8-d878-4378-a609-c19ea71633d2')
.mockImplementationOnce(() => ({
domain: ConfigurationDomain.CARPOOL,
key: 'seatsProposed',
}))
.mockImplementationOnce(() => {
throw new Error();
}),
@@ -50,7 +56,9 @@ describe('Set Configuration Grpc Controller', () => {
it('should set a configuration item', async () => {
jest.spyOn(mockCommandBus, 'execute');
await setConfigurationGrpcController.set(setConfigurationRequest);
const configurationIdentifier: ConfigurationIdentifier =
await setConfigurationGrpcController.set(setConfigurationRequest);
expect(configurationIdentifier.key).toBe('seatsProposed');
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
});