Merge branch 'useLibrary' into 'main'
Use configuration package See merge request v3/service/configuration!26
This commit is contained in:
commit
09f0142325
|
@ -1,6 +1,6 @@
|
||||||
# Mobicoop V3 - Configuration Service
|
# Mobicoop V3 - Configuration Service
|
||||||
|
|
||||||
Configuration items management. Used to configure and store all services using redis as database.
|
Configuration items management. Used to configure and store configuration items for all Mobicoop V3 services using redis as database.
|
||||||
|
|
||||||
Each item consists in :
|
Each item consists in :
|
||||||
|
|
||||||
|
@ -8,10 +8,6 @@ Each item consists in :
|
||||||
- a **key** : the key of the configuration item (a string)
|
- a **key** : the key of the configuration item (a string)
|
||||||
- a **value** : the value of the configuration item (always a string, each service must cast the value if needed)
|
- a **value** : the value of the configuration item (always a string, each service must cast the value if needed)
|
||||||
|
|
||||||
This service centralizes the configuration items, but theoratically each service should "push" its items toward the configuration service.
|
|
||||||
|
|
||||||
Practically, it's the other way round as it's easier to use this configuration service as the single source of truth. This is why configuration items key and domain are immutable : services may use hardcoded domain-key pairs. Therefore, only values can be updated.
|
|
||||||
|
|
||||||
## Available domains
|
## Available domains
|
||||||
|
|
||||||
- **CARPOOL** : carpool related configuration items (eg. default number of seats proposed as a driver)
|
- **CARPOOL** : carpool related configuration items (eg. default number of seats proposed as a driver)
|
||||||
|
@ -27,8 +23,6 @@ You also need NodeJS installed locally : we **strongly** advise to install [Node
|
||||||
|
|
||||||
The API will run inside a docker container, **but** the install itself is made outside the container, because during development we need tools that need to be available locally (eg. ESLint, Prettier...).
|
The API will run inside a docker container, **but** the install itself is made outside the container, because during development we need tools that need to be available locally (eg. ESLint, Prettier...).
|
||||||
|
|
||||||
A RabbitMQ instance is also required to send / receive messages when data has been inserted/updated/deleted.
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
- copy `.env.dist` to `.env` :
|
- copy `.env.dist` to `.env` :
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@mobicoop/configuration",
|
"name": "@mobicoop/configuration",
|
||||||
"version": "2.0.0",
|
"version": "2.1.0",
|
||||||
"description": "Mobicoop V3 Configuration Service",
|
"description": "Mobicoop V3 Configuration Service",
|
||||||
"author": "sbriat",
|
"author": "sbriat",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
@ -26,6 +26,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@grpc/grpc-js": "^1.9.6",
|
"@grpc/grpc-js": "^1.9.6",
|
||||||
"@grpc/proto-loader": "^0.7.10",
|
"@grpc/proto-loader": "^0.7.10",
|
||||||
|
"@mobicoop/configuration-module": "^4.0.0",
|
||||||
"@mobicoop/ddd-library": "^2.1.1",
|
"@mobicoop/ddd-library": "^2.1.1",
|
||||||
"@mobicoop/health-module": "^2.3.1",
|
"@mobicoop/health-module": "^2.3.1",
|
||||||
"@mobicoop/message-broker-module": "^2.1.1",
|
"@mobicoop/message-broker-module": "^2.1.1",
|
||||||
|
@ -37,14 +38,10 @@
|
||||||
"@nestjs/microservices": "^10.2.7",
|
"@nestjs/microservices": "^10.2.7",
|
||||||
"@nestjs/platform-express": "^10.2.7",
|
"@nestjs/platform-express": "^10.2.7",
|
||||||
"@nestjs/terminus": "^10.1.1",
|
"@nestjs/terminus": "^10.1.1",
|
||||||
"@songkeys/nestjs-redis": "^10.0.0",
|
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.0",
|
"class-validator": "^0.14.0",
|
||||||
"ioredis": "^5.3.2",
|
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5"
|
||||||
"rxjs": "^7.8.1",
|
|
||||||
"uuid": "^9.0.1"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/cli": "^10.1.18",
|
"@nestjs/cli": "^10.1.18",
|
||||||
|
@ -54,7 +51,6 @@
|
||||||
"@types/jest": "29.5.6",
|
"@types/jest": "29.5.6",
|
||||||
"@types/node": "^20.8.7",
|
"@types/node": "^20.8.7",
|
||||||
"@types/supertest": "^2.0.15",
|
"@types/supertest": "^2.0.15",
|
||||||
"@types/uuid": "^9.0.6",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^6.8.0",
|
"@typescript-eslint/eslint-plugin": "^6.8.0",
|
||||||
"@typescript-eslint/parser": "^6.8.0",
|
"@typescript-eslint/parser": "^6.8.0",
|
||||||
"dotenv-cli": "^7.3.0",
|
"dotenv-cli": "^7.3.0",
|
||||||
|
|
|
@ -4,6 +4,10 @@ import { Module } from '@nestjs/common';
|
||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
import { HEALTH_CRITICAL_LOGGING_KEY, SERVICE_NAME } from './app.constants';
|
import { HEALTH_CRITICAL_LOGGING_KEY, SERVICE_NAME } from './app.constants';
|
||||||
import { MessagePublisherPort } from '@mobicoop/ddd-library';
|
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 { MESSAGE_PUBLISHER } from '@modules/messager/messager.di-tokens';
|
||||||
import { ConfigurationModule } from '@modules/configuration/configuration.module';
|
import { ConfigurationModule } from '@modules/configuration/configuration.module';
|
||||||
import { EventEmitterModule } from '@nestjs/event-emitter';
|
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||||
|
@ -11,7 +15,6 @@ import brokerConfig from './config/broker.config';
|
||||||
import carpoolConfig from './config/carpool.config';
|
import carpoolConfig from './config/carpool.config';
|
||||||
import paginationConfig from './config/pagination.config';
|
import paginationConfig from './config/pagination.config';
|
||||||
import serviceConfig from './config/service.config';
|
import serviceConfig from './config/service.config';
|
||||||
import { RedisModule, RedisModuleOptions } from '@songkeys/nestjs-redis';
|
|
||||||
import redisConfig from './config/redis.config';
|
import redisConfig from './config/redis.config';
|
||||||
import { Transport } from '@nestjs/microservices';
|
import { Transport } from '@nestjs/microservices';
|
||||||
|
|
||||||
|
@ -49,18 +52,16 @@ import { Transport } from '@nestjs/microservices';
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
RedisModule.forRootAsync({
|
ConfigurationModulePackage.forRootAsync({
|
||||||
imports: [ConfigModule],
|
imports: [ConfigModule],
|
||||||
inject: [ConfigService],
|
inject: [ConfigService],
|
||||||
useFactory: async (
|
useFactory: async (
|
||||||
configService: ConfigService,
|
configService: ConfigService,
|
||||||
): Promise<RedisModuleOptions> => {
|
): Promise<ConfigurationModuleOptions> => {
|
||||||
return {
|
return {
|
||||||
config: {
|
|
||||||
host: configService.get<string>('redis.host') as string,
|
host: configService.get<string>('redis.host') as string,
|
||||||
port: configService.get<number>('redis.port') as number,
|
port: configService.get<number>('redis.port') as number,
|
||||||
password: configService.get<string>('redis.password'),
|
password: configService.get<string>('redis.password'),
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
import { registerAs } from '@nestjs/config';
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export interface CarpoolConfig {
|
||||||
|
departureTimeMargin: number;
|
||||||
|
role: string;
|
||||||
|
seatsProposed: number;
|
||||||
|
seatsRequested: number;
|
||||||
|
strictFrequency: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export default registerAs('carpool', () => ({
|
export default registerAs('carpool', () => ({
|
||||||
departureTimeMargin: process.env.DEPARTURE_TIME_MARGIN
|
departureTimeMargin: process.env.DEPARTURE_TIME_MARGIN
|
||||||
? parseInt(process.env.DEPARTURE_TIME_MARGIN, 10)
|
? parseInt(process.env.DEPARTURE_TIME_MARGIN, 10)
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import { registerAs } from '@nestjs/config';
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export interface PaginationConfig {
|
||||||
|
perPage: number;
|
||||||
|
}
|
||||||
|
|
||||||
export default registerAs('pagination', () => ({
|
export default registerAs('pagination', () => ({
|
||||||
perPage: process.env.PER_PAGE ? parseInt(process.env.PER_PAGE, 10) : 10,
|
perPage: process.env.PER_PAGE ? parseInt(process.env.PER_PAGE, 10) : 10,
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -1,55 +1,20 @@
|
||||||
import { Mapper } from '@mobicoop/ddd-library';
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { ConfigurationEntity } from './core/domain/configuration.entity';
|
|
||||||
import { ConfigurationResponseDto } from './interface/dtos/configuration.response.dto';
|
import { ConfigurationResponseDto } from './interface/dtos/configuration.response.dto';
|
||||||
import { ConfigurationDomain } from './core/domain/configuration.types';
|
|
||||||
import {
|
import {
|
||||||
ConfigurationReadModel,
|
ConfigurationIdentifier,
|
||||||
ConfigurationWriteModel,
|
ConfigurationValue,
|
||||||
} from './infrastructure/configuration.repository';
|
} from '@mobicoop/configuration-module';
|
||||||
import { v4 } from 'uuid';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ConfigurationMapper
|
export class ConfigurationMapper {
|
||||||
implements
|
toResponse = (
|
||||||
Mapper<
|
configurationIdentifier: ConfigurationIdentifier,
|
||||||
ConfigurationEntity,
|
configurationValue: ConfigurationValue,
|
||||||
ConfigurationReadModel,
|
): ConfigurationResponseDto => {
|
||||||
ConfigurationWriteModel,
|
const response = new ConfigurationResponseDto();
|
||||||
ConfigurationResponseDto
|
response.domain = configurationIdentifier.domain;
|
||||||
>
|
response.key = configurationIdentifier.key;
|
||||||
{
|
response.value = configurationValue;
|
||||||
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;
|
|
||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { GetConfigurationQueryHandler } from './core/application/queries/get-con
|
||||||
import { ConfigurationMapper } from './configuration.mapper';
|
import { ConfigurationMapper } from './configuration.mapper';
|
||||||
import { CONFIGURATION_REPOSITORY } from './configuration.di-tokens';
|
import { CONFIGURATION_REPOSITORY } from './configuration.di-tokens';
|
||||||
import { PopulateService } from './core/application/services/populate.service';
|
import { PopulateService } from './core/application/services/populate.service';
|
||||||
import { ConfigurationRepository } from './infrastructure/configuration.repository';
|
import { ConfigurationRepository } from '@mobicoop/configuration-module';
|
||||||
|
|
||||||
const grpcControllers = [
|
const grpcControllers = [
|
||||||
GetConfigurationGrpcController,
|
GetConfigurationGrpcController,
|
||||||
|
|
|
@ -2,18 +2,23 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import { SetConfigurationCommand } from './set-configuration.command';
|
import { SetConfigurationCommand } from './set-configuration.command';
|
||||||
import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
|
import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
|
||||||
import { ConfigurationDomain } from '@modules/configuration/core/domain/configuration.types';
|
import {
|
||||||
import { ConfigurationRepositoryPort } from '../../ports/configuration.repository.port';
|
ConfigurationDomain,
|
||||||
|
ConfigurationIdentifier,
|
||||||
|
SetConfigurationRepositoryPort,
|
||||||
|
} from '@mobicoop/configuration-module';
|
||||||
|
|
||||||
@CommandHandler(SetConfigurationCommand)
|
@CommandHandler(SetConfigurationCommand)
|
||||||
export class SetConfigurationService implements ICommandHandler {
|
export class SetConfigurationService implements ICommandHandler {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(CONFIGURATION_REPOSITORY)
|
@Inject(CONFIGURATION_REPOSITORY)
|
||||||
private readonly configurationRepository: ConfigurationRepositoryPort,
|
private readonly configurationRepository: SetConfigurationRepositoryPort,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async execute(command: SetConfigurationCommand): Promise<void> {
|
async execute(
|
||||||
await this.configurationRepository.set(
|
command: SetConfigurationCommand,
|
||||||
|
): Promise<ConfigurationIdentifier> {
|
||||||
|
return await this.configurationRepository.set(
|
||||||
{
|
{
|
||||||
domain: command.domain as ConfigurationDomain,
|
domain: command.domain as ConfigurationDomain,
|
||||||
key: command.key,
|
key: command.key,
|
||||||
|
|
|
@ -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>;
|
|
||||||
}
|
|
|
@ -2,17 +2,19 @@ import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
|
||||||
import { GetConfigurationQuery } from './get-configuration.query';
|
import { GetConfigurationQuery } from './get-configuration.query';
|
||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
|
import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
|
||||||
import { ConfigurationEntity } from '@modules/configuration/core/domain/configuration.entity';
|
import {
|
||||||
import { ConfigurationRepositoryPort } from '../../ports/configuration.repository.port';
|
ConfigurationDomain,
|
||||||
import { ConfigurationDomain } from '@modules/configuration/core/domain/configuration.types';
|
ConfigurationValue,
|
||||||
|
GetConfigurationRepositoryPort,
|
||||||
|
} from '@mobicoop/configuration-module';
|
||||||
|
|
||||||
@QueryHandler(GetConfigurationQuery)
|
@QueryHandler(GetConfigurationQuery)
|
||||||
export class GetConfigurationQueryHandler implements IQueryHandler {
|
export class GetConfigurationQueryHandler implements IQueryHandler {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(CONFIGURATION_REPOSITORY)
|
@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({
|
return await this.configurationRepository.get({
|
||||||
domain: query.domain as ConfigurationDomain,
|
domain: query.domain as ConfigurationDomain,
|
||||||
key: query.key,
|
key: query.key,
|
||||||
|
|
|
@ -1,19 +1,23 @@
|
||||||
|
import {
|
||||||
|
ConfigurationDomain,
|
||||||
|
GetConfigurationRepositoryPort,
|
||||||
|
NotFoundException,
|
||||||
|
SetConfigurationRepositoryPort,
|
||||||
|
} from '@mobicoop/configuration-module';
|
||||||
import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
|
import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
|
||||||
import { Inject, Injectable, OnApplicationBootstrap } from '@nestjs/common';
|
import { Inject, Injectable, OnApplicationBootstrap } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { ConfigurationRepositoryPort } from '../ports/configuration.repository.port';
|
|
||||||
import {
|
import { CarpoolConfig } from '@src/config/carpool.config';
|
||||||
CarpoolConfig,
|
import { PaginationConfig } from '@src/config/pagination.config';
|
||||||
ConfigurationDomain,
|
|
||||||
PaginationConfig,
|
|
||||||
} from '../../domain/configuration.types';
|
|
||||||
import { NotFoundException } from '@mobicoop/ddd-library';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PopulateService implements OnApplicationBootstrap {
|
export class PopulateService implements OnApplicationBootstrap {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(CONFIGURATION_REPOSITORY)
|
@Inject(CONFIGURATION_REPOSITORY)
|
||||||
private readonly configurationRepository: ConfigurationRepositoryPort,
|
private readonly getConfigurationRepository: GetConfigurationRepositoryPort,
|
||||||
|
@Inject(CONFIGURATION_REPOSITORY)
|
||||||
|
private readonly setConfigurationRepository: SetConfigurationRepositoryPort,
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
@ -42,13 +46,13 @@ export class PopulateService implements OnApplicationBootstrap {
|
||||||
let key: keyof typeof config;
|
let key: keyof typeof config;
|
||||||
for (key in config) {
|
for (key in config) {
|
||||||
try {
|
try {
|
||||||
await this.configurationRepository.get({
|
await this.getConfigurationRepository.get({
|
||||||
domain,
|
domain,
|
||||||
key,
|
key,
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error instanceof NotFoundException) {
|
if (error instanceof NotFoundException) {
|
||||||
this.configurationRepository.set(
|
this.setConfigurationRepository.set(
|
||||||
{
|
{
|
||||||
domain,
|
domain,
|
||||||
key,
|
key,
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,6 +1,4 @@
|
||||||
import { ResponseBase } from '@mobicoop/ddd-library';
|
export class ConfigurationResponseDto {
|
||||||
|
|
||||||
export class ConfigurationResponseDto extends ResponseBase {
|
|
||||||
domain: string;
|
domain: string;
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
|
|
@ -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';
|
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
export class GetConfigurationRequestDto {
|
export class GetConfigurationRequestDto {
|
||||||
|
|
|
@ -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';
|
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
export class SetConfigurationRequestDto {
|
export class SetConfigurationRequestDto {
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
import { Controller, UsePipes } from '@nestjs/common';
|
import { Controller, UsePipes } from '@nestjs/common';
|
||||||
import { QueryBus } from '@nestjs/cqrs';
|
import { QueryBus } from '@nestjs/cqrs';
|
||||||
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
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 { GRPC_SERVICE_NAME } from '@src/app.constants';
|
||||||
import { ConfigurationMapper } from '@modules/configuration/configuration.mapper';
|
import { ConfigurationMapper } from '@modules/configuration/configuration.mapper';
|
||||||
import { GetConfigurationRequestDto } from './dtos/get-configuration.request.dto';
|
import { GetConfigurationRequestDto } from './dtos/get-configuration.request.dto';
|
||||||
import { ConfigurationResponseDto } from '../dtos/configuration.response.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 { 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(
|
@UsePipes(
|
||||||
new RpcValidationPipe({
|
new RpcValidationPipe({
|
||||||
|
@ -29,10 +31,11 @@ export class GetConfigurationGrpcController {
|
||||||
data: GetConfigurationRequestDto,
|
data: GetConfigurationRequestDto,
|
||||||
): Promise<ConfigurationResponseDto> {
|
): Promise<ConfigurationResponseDto> {
|
||||||
try {
|
try {
|
||||||
const configuration: ConfigurationEntity = await this.queryBus.execute(
|
const configurationValue: ConfigurationValue =
|
||||||
|
await this.queryBus.execute(
|
||||||
new GetConfigurationQuery(data.domain, data.key),
|
new GetConfigurationQuery(data.domain, data.key),
|
||||||
);
|
);
|
||||||
return this.mapper.toResponse(configuration);
|
return this.mapper.toResponse(data, configurationValue);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof NotFoundException) {
|
if (e instanceof NotFoundException) {
|
||||||
throw new RpcException({
|
throw new RpcException({
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { Controller, UsePipes } from '@nestjs/common';
|
import { Controller, UsePipes } from '@nestjs/common';
|
||||||
import { CommandBus } from '@nestjs/cqrs';
|
import { CommandBus } from '@nestjs/cqrs';
|
||||||
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
import { GrpcMethod, RpcException } from '@nestjs/microservices';
|
||||||
import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
import { RpcExceptionCode, RpcValidationPipe } from '@mobicoop/ddd-library';
|
||||||
import { RpcValidationPipe } from '@mobicoop/ddd-library';
|
|
||||||
import { GRPC_SERVICE_NAME } from '@src/app.constants';
|
import { GRPC_SERVICE_NAME } from '@src/app.constants';
|
||||||
import { SetConfigurationRequestDto } from './dtos/set-configuration.request.dto';
|
import { SetConfigurationRequestDto } from './dtos/set-configuration.request.dto';
|
||||||
import { SetConfigurationCommand } from '@modules/configuration/core/application/commands/set-configuration/set-configuration.command';
|
import { SetConfigurationCommand } from '@modules/configuration/core/application/commands/set-configuration/set-configuration.command';
|
||||||
|
import { ConfigurationIdentifier } from '@mobicoop/configuration-module';
|
||||||
|
|
||||||
@UsePipes(
|
@UsePipes(
|
||||||
new RpcValidationPipe({
|
new RpcValidationPipe({
|
||||||
|
@ -20,11 +20,13 @@ export class SetConfigurationGrpcController {
|
||||||
@GrpcMethod(GRPC_SERVICE_NAME, 'Set')
|
@GrpcMethod(GRPC_SERVICE_NAME, 'Set')
|
||||||
async set(
|
async set(
|
||||||
setConfigurationRequestDto: SetConfigurationRequestDto,
|
setConfigurationRequestDto: SetConfigurationRequestDto,
|
||||||
): Promise<void> {
|
): Promise<ConfigurationIdentifier> {
|
||||||
try {
|
try {
|
||||||
|
const configurationIdentifier: ConfigurationIdentifier =
|
||||||
await this.commandBus.execute(
|
await this.commandBus.execute(
|
||||||
new SetConfigurationCommand(setConfigurationRequestDto),
|
new SetConfigurationCommand(setConfigurationRequestDto),
|
||||||
);
|
);
|
||||||
|
return configurationIdentifier;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
throw new RpcException({
|
throw new RpcException({
|
||||||
code: RpcExceptionCode.UNKNOWN,
|
code: RpcExceptionCode.UNKNOWN,
|
||||||
|
|
|
@ -1,31 +1,8 @@
|
||||||
|
import { ConfigurationDomain } from '@mobicoop/configuration-module';
|
||||||
import { ConfigurationMapper } from '@modules/configuration/configuration.mapper';
|
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 { ConfigurationResponseDto } from '@modules/configuration/interface/dtos/configuration.response.dto';
|
||||||
import { Test } from '@nestjs/testing';
|
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', () => {
|
describe('Configuration Mapper', () => {
|
||||||
let configurationMapper: ConfigurationMapper;
|
let configurationMapper: ConfigurationMapper;
|
||||||
|
|
||||||
|
@ -40,22 +17,16 @@ describe('Configuration Mapper', () => {
|
||||||
expect(configurationMapper).toBeDefined();
|
expect(configurationMapper).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should map domain entity to persistence data', async () => {
|
it('should map configuration to response', async () => {
|
||||||
const mapped: ConfigurationWriteModel =
|
const mapped: ConfigurationResponseDto = configurationMapper.toResponse(
|
||||||
configurationMapper.toPersistence(configurationEntity);
|
{
|
||||||
|
domain: ConfigurationDomain.CARPOOL,
|
||||||
|
key: 'seatsProposed',
|
||||||
|
},
|
||||||
|
'3',
|
||||||
|
);
|
||||||
|
expect(mapped.domain).toBe('CARPOOL');
|
||||||
|
expect(mapped.key).toBe('seatsProposed');
|
||||||
expect(mapped.value).toBe('3');
|
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');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -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');
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,26 +1,16 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
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 { GetConfigurationQueryHandler } from '@modules/configuration/core/application/queries/get-configuration/get-configuration.query-handler';
|
||||||
import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
|
import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
|
||||||
import { GetConfigurationQuery } from '@modules/configuration/core/application/queries/get-configuration/get-configuration.query';
|
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 configurationValue: ConfigurationValue = '3';
|
||||||
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 mockConfigurationRepository = {
|
const mockConfigurationRepository = {
|
||||||
get: jest.fn().mockImplementation(() => configuration),
|
get: jest.fn().mockImplementation(() => configurationValue),
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Get Configuration Query Handler', () => {
|
describe('Get Configuration Query Handler', () => {
|
||||||
|
@ -47,14 +37,14 @@ describe('Get Configuration Query Handler', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('execution', () => {
|
describe('execution', () => {
|
||||||
it('should return a configuration item', async () => {
|
it('should return a configuration value', async () => {
|
||||||
const getConfigurationQuery = new GetConfigurationQuery(
|
const getConfigurationQuery = new GetConfigurationQuery(
|
||||||
ConfigurationDomain.CARPOOL,
|
ConfigurationDomain.CARPOOL,
|
||||||
'seatsProposed',
|
'seatsProposed',
|
||||||
);
|
);
|
||||||
const configuration: ConfigurationEntity =
|
const configurationValue: ConfigurationValue =
|
||||||
await getConfigurationQueryHandler.execute(getConfigurationQuery);
|
await getConfigurationQueryHandler.execute(getConfigurationQuery);
|
||||||
expect(configuration.getProps().value).toBe('3');
|
expect(configurationValue).toBe('3');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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 { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
|
||||||
import { PopulateService } from '@modules/configuration/core/application/services/populate.service';
|
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 { ConfigService } from '@nestjs/config';
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
|
||||||
const mockConfigurationRepository = {
|
const mockConfigurationRepository = {
|
||||||
get: jest
|
get: jest
|
||||||
.fn()
|
.fn()
|
||||||
.mockImplementationOnce(
|
.mockImplementationOnce(() => 'someValue')
|
||||||
() =>
|
|
||||||
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(() => {
|
.mockImplementationOnce(() => {
|
||||||
throw new NotFoundException('Configuration not found');
|
throw new NotFoundException('Configuration not found');
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { SetConfigurationRequestDto } from '@modules/configuration/interface/grpc-controllers/dtos/set-configuration.request.dto';
|
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 { SetConfigurationService } from '@modules/configuration/core/application/commands/set-configuration/set-configuration.service';
|
||||||
import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
|
import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
|
||||||
import { SetConfigurationCommand } from '@modules/configuration/core/application/commands/set-configuration/set-configuration.command';
|
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 = {
|
const setConfigurationRequest: SetConfigurationRequestDto = {
|
||||||
domain: ConfigurationDomain.CARPOOL,
|
domain: ConfigurationDomain.CARPOOL,
|
||||||
|
@ -50,16 +49,10 @@ describe('Set Configuration Service', () => {
|
||||||
);
|
);
|
||||||
it('should set an existing configuration item', async () => {
|
it('should set an existing configuration item', async () => {
|
||||||
jest.spyOn(mockConfigurationRepository, 'set');
|
jest.spyOn(mockConfigurationRepository, 'set');
|
||||||
ConfigurationEntity.create = jest.fn().mockReturnValue({
|
|
||||||
id: '047a6ecf-23d4-4d3e-877c-3225d560a8da',
|
|
||||||
});
|
|
||||||
await setConfigurationService.execute(setConfigurationCommand);
|
await setConfigurationService.execute(setConfigurationCommand);
|
||||||
expect(mockConfigurationRepository.set).toHaveBeenCalledTimes(1);
|
expect(mockConfigurationRepository.set).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
it('should throw an error if something bad happens on configuration item update', async () => {
|
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(
|
await expect(
|
||||||
setConfigurationService.execute(setConfigurationCommand),
|
setConfigurationService.execute(setConfigurationCommand),
|
||||||
).rejects.toBeInstanceOf(Error);
|
).rejects.toBeInstanceOf(Error);
|
||||||
|
|
|
@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { NotFoundException } from '@mobicoop/ddd-library';
|
import { ConfigurationDomain } from '@mobicoop/configuration-module';
|
||||||
import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
import { NotFoundException, RpcExceptionCode } from '@mobicoop/ddd-library';
|
||||||
import { ConfigurationMapper } from '@modules/configuration/configuration.mapper';
|
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 { GetConfigurationGrpcController } from '@modules/configuration/interface/grpc-controllers/get-configuration.grpc.controller';
|
||||||
import { QueryBus } from '@nestjs/cqrs';
|
import { QueryBus } from '@nestjs/cqrs';
|
||||||
import { RpcException } from '@nestjs/microservices';
|
import { RpcException } from '@nestjs/microservices';
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
|
import {
|
||||||
|
ConfigurationDomain,
|
||||||
|
ConfigurationIdentifier,
|
||||||
|
} from '@mobicoop/configuration-module';
|
||||||
import { RpcExceptionCode } from '@mobicoop/ddd-library';
|
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 { 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 { SetConfigurationGrpcController } from '@modules/configuration/interface/grpc-controllers/set-configuration.grpc.controller';
|
||||||
import { CommandBus } from '@nestjs/cqrs';
|
import { CommandBus } from '@nestjs/cqrs';
|
||||||
|
@ -15,7 +18,10 @@ const setConfigurationRequest: SetConfigurationRequestDto = {
|
||||||
const mockCommandBus = {
|
const mockCommandBus = {
|
||||||
execute: jest
|
execute: jest
|
||||||
.fn()
|
.fn()
|
||||||
.mockImplementationOnce(() => '200d61a8-d878-4378-a609-c19ea71633d2')
|
.mockImplementationOnce(() => ({
|
||||||
|
domain: ConfigurationDomain.CARPOOL,
|
||||||
|
key: 'seatsProposed',
|
||||||
|
}))
|
||||||
.mockImplementationOnce(() => {
|
.mockImplementationOnce(() => {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}),
|
}),
|
||||||
|
@ -50,7 +56,9 @@ describe('Set Configuration Grpc Controller', () => {
|
||||||
|
|
||||||
it('should set a configuration item', async () => {
|
it('should set a configuration item', async () => {
|
||||||
jest.spyOn(mockCommandBus, 'execute');
|
jest.spyOn(mockCommandBus, 'execute');
|
||||||
|
const configurationIdentifier: ConfigurationIdentifier =
|
||||||
await setConfigurationGrpcController.set(setConfigurationRequest);
|
await setConfigurationGrpcController.set(setConfigurationRequest);
|
||||||
|
expect(configurationIdentifier.key).toBe('seatsProposed');
|
||||||
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue