use configuration package as middleware
This commit is contained in:
		
							parent
							
								
									efb35d6d2b
								
							
						
					
					
						commit
						5094e27ad1
					
				| 
						 | 
					@ -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
											
										
									
								
							| 
						 | 
					@ -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 =
 | 
				
			||||||
        new GetConfigurationQuery(data.domain, data.key),
 | 
					        await this.queryBus.execute(
 | 
				
			||||||
      );
 | 
					          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 {
 | 
				
			||||||
      await this.commandBus.execute(
 | 
					      const configurationIdentifier: ConfigurationIdentifier =
 | 
				
			||||||
        new SetConfigurationCommand(setConfigurationRequestDto),
 | 
					        await this.commandBus.execute(
 | 
				
			||||||
      );
 | 
					          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');
 | 
				
			||||||
    await setConfigurationGrpcController.set(setConfigurationRequest);
 | 
					    const configurationIdentifier: ConfigurationIdentifier =
 | 
				
			||||||
 | 
					      await setConfigurationGrpcController.set(setConfigurationRequest);
 | 
				
			||||||
 | 
					    expect(configurationIdentifier.key).toBe('seatsProposed');
 | 
				
			||||||
    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
					    expect(mockCommandBus.execute).toHaveBeenCalledTimes(1);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue