Merge branch 'castValue' into 'main'

Cast value

See merge request v3/service/configuration!27
This commit is contained in:
Sylvain Briat 2023-10-27 09:22:48 +00:00
commit 832fa865b8
23 changed files with 405 additions and 125 deletions

View File

@ -10,10 +10,7 @@ Each item consists in :
## Available domains ## Available domains
- **CARPOOL** : carpool related configuration items (eg. default number of seats proposed as a driver) Domains are set in the [configuration package](https://gitlab.mobicoop.io/v3/packages/configuration).
- **PAGINATION** : pagination related configuration items (eg. default number of results per page)
New domains will be added in the future depending on the needs !
## Requirements ## Requirements

38
package-lock.json generated
View File

@ -1,17 +1,17 @@
{ {
"name": "@mobicoop/configuration", "name": "@mobicoop/configuration",
"version": "2.1.0", "version": "2.2.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@mobicoop/configuration", "name": "@mobicoop/configuration",
"version": "2.1.0", "version": "2.2.0",
"license": "AGPL", "license": "AGPL",
"dependencies": { "dependencies": {
"@grpc/grpc-js": "^1.9.6", "@grpc/grpc-js": "^1.9.7",
"@grpc/proto-loader": "^0.7.10", "@grpc/proto-loader": "^0.7.10",
"@mobicoop/configuration-module": "^4.0.0", "@mobicoop/configuration-module": "^4.1.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",
@ -29,17 +29,17 @@
"rimraf": "^5.0.5" "rimraf": "^5.0.5"
}, },
"devDependencies": { "devDependencies": {
"@nestjs/cli": "^10.1.18", "@nestjs/cli": "^10.2.0",
"@nestjs/schematics": "^10.0.2", "@nestjs/schematics": "^10.0.2",
"@nestjs/testing": "^10.2.7", "@nestjs/testing": "^10.2.7",
"@types/express": "^4.17.20", "@types/express": "^4.17.20",
"@types/jest": "29.5.6", "@types/jest": "29.5.6",
"@types/node": "^20.8.7", "@types/node": "^20.8.9",
"@types/supertest": "^2.0.15", "@types/supertest": "^2.0.15",
"@typescript-eslint/eslint-plugin": "^6.8.0", "@typescript-eslint/eslint-plugin": "^6.9.0",
"@typescript-eslint/parser": "^6.8.0", "@typescript-eslint/parser": "^6.9.0",
"dotenv-cli": "^7.3.0", "dotenv-cli": "^7.3.0",
"eslint": "^8.51.0", "eslint": "^8.52.0",
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.1", "eslint-plugin-prettier": "^5.0.1",
"jest": "29.7.0", "jest": "29.7.0",
@ -1668,9 +1668,9 @@
} }
}, },
"node_modules/@mobicoop/configuration-module": { "node_modules/@mobicoop/configuration-module": {
"version": "4.0.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/@mobicoop/configuration-module/-/configuration-module-4.0.0.tgz", "resolved": "https://registry.npmjs.org/@mobicoop/configuration-module/-/configuration-module-4.1.0.tgz",
"integrity": "sha512-Yd5ONDLwvEcolGYpg6EYLUNWQf8ClWT2UaYISBVWSOn37duFi0F3iuSq4W68JY2Y7ZkF3CalSht2fJVjU8rw0Q==", "integrity": "sha512-j1C6S1S/v5nid9KeWVa97ZCVxwEbSWBP2oHzzRHazmgtO6lbybugnjQKtRHHKlnTMc1zGdD/4Xii/7JlGckflQ==",
"dependencies": { "dependencies": {
"@songkeys/nestjs-redis": "^10.0.0", "@songkeys/nestjs-redis": "^10.0.0",
"ioredis": "^5.3.2" "ioredis": "^5.3.2"
@ -2663,11 +2663,11 @@
"dev": true "dev": true
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "20.8.8", "version": "20.8.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.8.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.9.tgz",
"integrity": "sha512-YRsdVxq6OaLfmR9Hy816IMp33xOBjfyOgUd77ehqg96CFywxAPbDbXvAsuN2KVg2HOT8Eh6uAfU+l4WffwPVrQ==", "integrity": "sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==",
"dependencies": { "dependencies": {
"undici-types": "~5.25.1" "undici-types": "~5.26.4"
} }
}, },
"node_modules/@types/parse-json": { "node_modules/@types/parse-json": {
@ -9383,9 +9383,9 @@
} }
}, },
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "5.25.3", "version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==" "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
}, },
"node_modules/universalify": { "node_modules/universalify": {
"version": "2.0.0", "version": "2.0.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "@mobicoop/configuration", "name": "@mobicoop/configuration",
"version": "2.1.0", "version": "2.2.0",
"description": "Mobicoop V3 Configuration Service", "description": "Mobicoop V3 Configuration Service",
"author": "sbriat", "author": "sbriat",
"private": true, "private": true,
@ -24,9 +24,9 @@
"test:e2e": "jest --config ./test/jest-e2e.json" "test:e2e": "jest --config ./test/jest-e2e.json"
}, },
"dependencies": { "dependencies": {
"@grpc/grpc-js": "^1.9.6", "@grpc/grpc-js": "^1.9.7",
"@grpc/proto-loader": "^0.7.10", "@grpc/proto-loader": "^0.7.10",
"@mobicoop/configuration-module": "^4.0.0", "@mobicoop/configuration-module": "^4.1.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",
@ -44,17 +44,17 @@
"rimraf": "^5.0.5" "rimraf": "^5.0.5"
}, },
"devDependencies": { "devDependencies": {
"@nestjs/cli": "^10.1.18", "@nestjs/cli": "^10.2.0",
"@nestjs/schematics": "^10.0.2", "@nestjs/schematics": "^10.0.2",
"@nestjs/testing": "^10.2.7", "@nestjs/testing": "^10.2.7",
"@types/express": "^4.17.20", "@types/express": "^4.17.20",
"@types/jest": "29.5.6", "@types/jest": "29.5.6",
"@types/node": "^20.8.7", "@types/node": "^20.8.9",
"@types/supertest": "^2.0.15", "@types/supertest": "^2.0.15",
"@typescript-eslint/eslint-plugin": "^6.8.0", "@typescript-eslint/eslint-plugin": "^6.9.0",
"@typescript-eslint/parser": "^6.8.0", "@typescript-eslint/parser": "^6.9.0",
"dotenv-cli": "^7.3.0", "dotenv-cli": "^7.3.0",
"eslint": "^8.51.0", "eslint": "^8.52.0",
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.1", "eslint-plugin-prettier": "^5.0.1",
"jest": "29.7.0", "jest": "29.7.0",

View File

@ -1,6 +1,7 @@
import { registerAs } from '@nestjs/config'; import { registerAs } from '@nestjs/config';
import { Config } from './config';
export interface CarpoolConfig { export interface CarpoolConfig extends Config {
departureTimeMargin: number; departureTimeMargin: number;
role: string; role: string;
seatsProposed: number; seatsProposed: number;

5
src/config/config.ts Normal file
View File

@ -0,0 +1,5 @@
import { ConfigurationDomain } from '@mobicoop/configuration-module';
export interface Config {
domain: ConfigurationDomain;
}

View File

@ -1,6 +1,7 @@
import { registerAs } from '@nestjs/config'; import { registerAs } from '@nestjs/config';
import { Config } from './config';
export interface PaginationConfig { export interface PaginationConfig extends Config {
perPage: number; perPage: number;
} }

View File

@ -4,9 +4,14 @@ import {
ConfigurationIdentifier, ConfigurationIdentifier,
ConfigurationValue, ConfigurationValue,
} from '@mobicoop/configuration-module'; } from '@mobicoop/configuration-module';
import { ConfigurationsManagerService } from './core/application/services/configurations-manager.service';
@Injectable() @Injectable()
export class ConfigurationMapper { export class ConfigurationMapper {
constructor(
private readonly configurationsManager: ConfigurationsManagerService,
) {}
toResponse = ( toResponse = (
configurationIdentifier: ConfigurationIdentifier, configurationIdentifier: ConfigurationIdentifier,
configurationValue: ConfigurationValue, configurationValue: ConfigurationValue,
@ -15,6 +20,8 @@ export class ConfigurationMapper {
response.domain = configurationIdentifier.domain; response.domain = configurationIdentifier.domain;
response.key = configurationIdentifier.key; response.key = configurationIdentifier.key;
response.value = configurationValue; response.value = configurationValue;
response.type =
this.configurationsManager.configurationType(configurationValue);
return response; return response;
}; };
} }

View File

@ -8,6 +8,7 @@ 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 '@mobicoop/configuration-module'; import { ConfigurationRepository } from '@mobicoop/configuration-module';
import { ConfigurationsManagerService } from './core/application/services/configurations-manager.service';
const grpcControllers = [ const grpcControllers = [
GetConfigurationGrpcController, GetConfigurationGrpcController,
@ -20,7 +21,7 @@ const queryHandlers: Provider[] = [GetConfigurationQueryHandler];
const mappers: Provider[] = [ConfigurationMapper]; const mappers: Provider[] = [ConfigurationMapper];
const providers: Provider[] = [PopulateService]; const providers: Provider[] = [PopulateService, ConfigurationsManagerService];
const repositories: Provider[] = [ const repositories: Provider[] = [
{ {

View File

@ -1,14 +1,13 @@
import { ConfigurationIdentifier } from '@mobicoop/configuration-module';
import { Command, CommandProps } from '@mobicoop/ddd-library'; import { Command, CommandProps } from '@mobicoop/ddd-library';
export class SetConfigurationCommand extends Command { export class SetConfigurationCommand extends Command {
readonly domain: string; readonly configurationIdentifier: ConfigurationIdentifier;
readonly key: string; readonly value: string | boolean | number;
readonly value: string;
constructor(props: CommandProps<SetConfigurationCommand>) { constructor(props: CommandProps<SetConfigurationCommand>) {
super(props); super(props);
this.domain = props.domain; this.configurationIdentifier = props.configurationIdentifier;
this.key = props.key;
this.value = props.value; this.value = props.value;
} }
} }

View File

@ -3,27 +3,36 @@ 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 { import {
ConfigurationDomain,
ConfigurationIdentifier, ConfigurationIdentifier,
ConfigurationType,
SetConfigurationRepositoryPort, SetConfigurationRepositoryPort,
} from '@mobicoop/configuration-module'; } from '@mobicoop/configuration-module';
import { ConfigurationsManagerService } from '../../services/configurations-manager.service';
import { ArgumentInvalidException } from '@mobicoop/ddd-library';
@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: SetConfigurationRepositoryPort, private readonly configurationRepository: SetConfigurationRepositoryPort,
private readonly configurationsManager: ConfigurationsManagerService,
) {} ) {}
async execute( async execute(
command: SetConfigurationCommand, command: SetConfigurationCommand,
): Promise<ConfigurationIdentifier> { ): Promise<ConfigurationIdentifier> {
const configurationType: ConfigurationType =
this.configurationsManager.identifierType(
command.configurationIdentifier,
);
const value: any = this.configurationsManager.cast(
`${command.value}`,
configurationType,
);
if (isNaN(value)) throw new ArgumentInvalidException('Bad value');
return await this.configurationRepository.set( return await this.configurationRepository.set(
{ command.configurationIdentifier,
domain: command.domain as ConfigurationDomain, value,
key: command.key,
},
command.value,
); );
} }
} }

View File

@ -3,21 +3,22 @@ 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 { import {
ConfigurationDomain,
ConfigurationValue, ConfigurationValue,
GetConfigurationRepositoryPort, GetConfigurationRepositoryPort,
} from '@mobicoop/configuration-module'; } from '@mobicoop/configuration-module';
import { ConfigurationsManagerService } from '../../services/configurations-manager.service';
@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: GetConfigurationRepositoryPort, private readonly configurationRepository: GetConfigurationRepositoryPort,
private readonly configurationsManager: ConfigurationsManagerService,
) {} ) {}
async execute(query: GetConfigurationQuery): Promise<ConfigurationValue> { async execute(query: GetConfigurationQuery): Promise<ConfigurationValue> {
return await this.configurationRepository.get({ return await this.configurationRepository.get(
domain: query.domain as ConfigurationDomain, query.configurationIdentifier,
key: query.key, this.configurationsManager.identifierType(query.configurationIdentifier),
}); );
} }
} }

View File

@ -1,12 +1,17 @@
import {
ConfigurationDomain,
ConfigurationIdentifier,
} from '@mobicoop/configuration-module';
import { QueryBase } from '@mobicoop/ddd-library'; import { QueryBase } from '@mobicoop/ddd-library';
export class GetConfigurationQuery extends QueryBase { export class GetConfigurationQuery extends QueryBase {
readonly domain: string; readonly configurationIdentifier: ConfigurationIdentifier;
readonly key: string;
constructor(domain: string, key: string) { constructor(domain: ConfigurationDomain, key: string) {
super(); super();
this.domain = domain; this.configurationIdentifier = {
this.key = key; domain,
key,
};
} }
} }

View File

@ -0,0 +1,93 @@
import {
ConfigurationDomain,
ConfigurationIdentifier,
ConfigurationType,
ConfigurationValue,
} from '@mobicoop/configuration-module';
import { NotFoundException } from '@mobicoop/ddd-library';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { CarpoolConfig } from '@src/config/carpool.config';
import { Config } from '@src/config/config';
import { PaginationConfig } from '@src/config/pagination.config';
@Injectable()
export class ConfigurationsManagerService {
constructor(private readonly configService: ConfigService) {}
list = (): Config[] => {
return [
{
...(this.configService.get<CarpoolConfig>('carpool') as CarpoolConfig),
domain: ConfigurationDomain.CARPOOL,
},
{
...(this.configService.get<PaginationConfig>(
'pagination',
) as PaginationConfig),
domain: ConfigurationDomain.PAGINATION,
},
];
};
identifierType = (
configurationIdentifier: ConfigurationIdentifier,
): ConfigurationType => {
const configs: Config[] = this.list();
const configuration: Config | undefined = configs.find(
(config: Config) =>
config.domain === configurationIdentifier.domain &&
this._hasProperty(configurationIdentifier.key, config),
);
if (!configuration)
throw new NotFoundException('Configuration item not found');
return this.configurationType(
this._getValue(configurationIdentifier.key, configuration),
);
};
configurationType = (value: any): ConfigurationType => {
switch (typeof value) {
case 'number':
if (this._isInt(value)) return ConfigurationType.INT;
return ConfigurationType.FLOAT;
case 'boolean':
return ConfigurationType.BOOLEAN;
default:
return ConfigurationType.STRING;
}
};
cast = (
value: string,
configurationType: ConfigurationType,
): ConfigurationValue => {
switch (configurationType) {
case ConfigurationType.BOOLEAN:
return value === 'true';
case ConfigurationType.INT:
return parseInt(value);
case ConfigurationType.FLOAT:
return parseFloat(value);
default:
return value;
}
};
private _hasProperty = <T>(property: string, configuration: T): boolean => {
let key: keyof typeof configuration;
for (key in configuration) {
if (key !== 'domain' && key === property) return true;
}
return false;
};
private _getValue = <T>(property: string, configuration: T): any => {
let key: keyof typeof configuration;
for (key in configuration) {
if (key !== 'domain' && key === property) return configuration[key];
}
};
private _isInt = (number: number): boolean => number % 1 === 0;
}

View File

@ -1,15 +1,12 @@
import { import {
ConfigurationDomain,
GetConfigurationRepositoryPort, GetConfigurationRepositoryPort,
NotFoundException, NotFoundException,
SetConfigurationRepositoryPort, SetConfigurationRepositoryPort,
} from '@mobicoop/configuration-module'; } 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 { ConfigurationsManagerService } from './configurations-manager.service';
import { Config } from '@src/config/config';
import { CarpoolConfig } from '@src/config/carpool.config';
import { PaginationConfig } from '@src/config/pagination.config';
@Injectable() @Injectable()
export class PopulateService implements OnApplicationBootstrap { export class PopulateService implements OnApplicationBootstrap {
@ -18,46 +15,41 @@ export class PopulateService implements OnApplicationBootstrap {
private readonly getConfigurationRepository: GetConfigurationRepositoryPort, private readonly getConfigurationRepository: GetConfigurationRepositoryPort,
@Inject(CONFIGURATION_REPOSITORY) @Inject(CONFIGURATION_REPOSITORY)
private readonly setConfigurationRepository: SetConfigurationRepositoryPort, private readonly setConfigurationRepository: SetConfigurationRepositoryPort,
private readonly configService: ConfigService, private readonly configurationsManager: ConfigurationsManagerService,
) {} ) {}
onApplicationBootstrap() { async onApplicationBootstrap() {
this._populate(); await this._populate();
} }
private _populate = async (): Promise<void> => { private _populate = async (): Promise<void> => {
const carpoolConfig: CarpoolConfig = this.configService.get<CarpoolConfig>( const configs: Config[] = this.configurationsManager.list();
'carpool', await Promise.all(
) as CarpoolConfig; configs.map((config: Config) => this._populateConfig(config)),
const paginationConfig: PaginationConfig = );
this.configService.get<PaginationConfig>(
'pagination',
) as PaginationConfig;
await Promise.all([
this._populateConfig(ConfigurationDomain.CARPOOL, carpoolConfig),
this._populateConfig(ConfigurationDomain.PAGINATION, paginationConfig),
]);
}; };
private _populateConfig = async <T>( private _populateConfig = async <T>(configuration: T): Promise<void> => {
domain: ConfigurationDomain, let key: keyof typeof configuration;
config: T, const config: Config = configuration as Config;
): Promise<void> => { for (key in configuration) {
let key: keyof typeof config;
for (key in config) {
try { try {
await this.getConfigurationRepository.get({ if (key !== 'domain')
domain, await this.getConfigurationRepository.get(
key, {
}); domain: config.domain,
key,
},
this.configurationsManager.configurationType(configuration[key]),
);
} catch (error: any) { } catch (error: any) {
if (error instanceof NotFoundException) { if (error instanceof NotFoundException) {
this.setConfigurationRepository.set( this.setConfigurationRepository.set(
{ {
domain, domain: config.domain,
key, key,
}, },
`${config[key]}`, `${configuration[key]}`,
); );
} }
} }

View File

@ -1,5 +1,8 @@
import { ConfigurationType } from '@mobicoop/configuration-module';
export class ConfigurationResponseDto { export class ConfigurationResponseDto {
domain: string; domain: string;
key: string; key: string;
value: string; value: string | boolean | number;
type: ConfigurationType;
} }

View File

@ -16,6 +16,7 @@ message Configuration {
string domain = 1; string domain = 1;
string key = 2; string key = 2;
string value = 3; string value = 3;
string type = 4;
} }
message Empty {} message Empty {}

View File

@ -10,7 +10,6 @@ export class SetConfigurationRequestDto {
@IsNotEmpty() @IsNotEmpty()
key: string; key: string;
@IsString()
@IsNotEmpty() @IsNotEmpty()
value: string; value: string | boolean | number;
} }

View File

@ -24,7 +24,13 @@ export class SetConfigurationGrpcController {
try { try {
const configurationIdentifier: ConfigurationIdentifier = const configurationIdentifier: ConfigurationIdentifier =
await this.commandBus.execute( await this.commandBus.execute(
new SetConfigurationCommand(setConfigurationRequestDto), new SetConfigurationCommand({
configurationIdentifier: {
domain: setConfigurationRequestDto.domain,
key: setConfigurationRequestDto.key,
},
value: setConfigurationRequestDto.value,
}),
); );
return configurationIdentifier; return configurationIdentifier;
} catch (error: any) { } catch (error: any) {

View File

@ -1,14 +1,28 @@
import { ConfigurationDomain } from '@mobicoop/configuration-module'; import {
ConfigurationDomain,
ConfigurationType,
} from '@mobicoop/configuration-module';
import { ConfigurationMapper } from '@modules/configuration/configuration.mapper'; import { ConfigurationMapper } from '@modules/configuration/configuration.mapper';
import { ConfigurationsManagerService } from '@modules/configuration/core/application/services/configurations-manager.service';
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 mockConfigurationsManagerService = {
configurationType: jest.fn().mockImplementation(() => ConfigurationType.INT),
};
describe('Configuration Mapper', () => { describe('Configuration Mapper', () => {
let configurationMapper: ConfigurationMapper; let configurationMapper: ConfigurationMapper;
beforeAll(async () => { beforeAll(async () => {
const module = await Test.createTestingModule({ const module = await Test.createTestingModule({
providers: [ConfigurationMapper], providers: [
ConfigurationMapper,
{
provide: ConfigurationsManagerService,
useValue: mockConfigurationsManagerService,
},
],
}).compile(); }).compile();
configurationMapper = module.get<ConfigurationMapper>(ConfigurationMapper); configurationMapper = module.get<ConfigurationMapper>(ConfigurationMapper);
}); });

View File

@ -0,0 +1,125 @@
import {
ConfigurationDomain,
ConfigurationIdentifier,
ConfigurationType,
} from '@mobicoop/configuration-module';
import { NotFoundException } from '@mobicoop/ddd-library';
import { ConfigurationsManagerService } from '@modules/configuration/core/application/services/configurations-manager.service';
import { ConfigService } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import { Config } from '@src/config/config';
const mockConfigService = {
get: jest.fn().mockImplementation((domain: string) => {
switch (domain) {
case 'carpool':
return {
departureTimeMargin: 900,
role: 'passenger',
seatsProposed: 3,
seatsRequested: 1,
strictFrequency: false,
};
case 'pagination':
return {
perPage: 10,
};
}
}),
};
describe('Configurations Manager Service', () => {
let configurationsManagerService: ConfigurationsManagerService;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: ConfigService,
useValue: mockConfigService,
},
ConfigurationsManagerService,
],
}).compile();
configurationsManagerService = module.get<ConfigurationsManagerService>(
ConfigurationsManagerService,
);
});
it('should be defined', () => {
expect(configurationsManagerService).toBeDefined();
});
it('should return the list of configuration elements', () => {
const list: Config[] = configurationsManagerService.list();
expect(list).toHaveLength(2);
});
describe('identifierType', () => {
it('should return the type of a configuration item for a given identifier', () => {
const configurationIdentifier: ConfigurationIdentifier = {
domain: ConfigurationDomain.CARPOOL,
key: 'seatsProposed',
};
const configurationType: ConfigurationType =
configurationsManagerService.identifierType(configurationIdentifier);
expect(configurationType).toBe(ConfigurationType.INT);
});
it('should throw if configuration item is not found', () => {
const configurationIdentifier: ConfigurationIdentifier = {
domain: ConfigurationDomain.CARPOOL,
key: 'minAge',
};
expect(() => {
configurationsManagerService.identifierType(configurationIdentifier);
}).toThrow(NotFoundException);
});
});
describe('configurationType', () => {
it('should return the configuration type of an int', () => {
expect(configurationsManagerService.configurationType(3)).toBe(
ConfigurationType.INT,
);
});
it('should return the configuration type of a float', () => {
expect(configurationsManagerService.configurationType(3.5)).toBe(
ConfigurationType.FLOAT,
);
});
it('should return the configuration type of a boolean', () => {
expect(configurationsManagerService.configurationType(true)).toBe(
ConfigurationType.BOOLEAN,
);
});
it('should return the configuration type of a string', () => {
expect(configurationsManagerService.configurationType('role')).toBe(
ConfigurationType.STRING,
);
});
});
describe('cast', () => {
it('should cast a string to int', () => {
expect(
configurationsManagerService.cast('1', ConfigurationType.INT),
).toBe(1);
});
it('should cast a string to float', () => {
expect(
configurationsManagerService.cast('1.5', ConfigurationType.FLOAT),
).toBe(1.5);
});
it('should cast a string to boolean', () => {
expect(
configurationsManagerService.cast('true', ConfigurationType.BOOLEAN),
).toBeTruthy();
});
it('should not cast a string and return it as is', () => {
expect(
configurationsManagerService.cast('role', ConfigurationType.STRING),
).toBe('role');
});
});
});

View File

@ -4,8 +4,10 @@ import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.d
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 { import {
ConfigurationDomain, ConfigurationDomain,
ConfigurationType,
ConfigurationValue, ConfigurationValue,
} from '@mobicoop/configuration-module'; } from '@mobicoop/configuration-module';
import { ConfigurationsManagerService } from '@modules/configuration/core/application/services/configurations-manager.service';
const configurationValue: ConfigurationValue = '3'; const configurationValue: ConfigurationValue = '3';
@ -13,6 +15,10 @@ const mockConfigurationRepository = {
get: jest.fn().mockImplementation(() => configurationValue), get: jest.fn().mockImplementation(() => configurationValue),
}; };
const mockConfigurationsManagerService = {
identifierType: jest.fn().mockImplementation(() => ConfigurationType.INT),
};
describe('Get Configuration Query Handler', () => { describe('Get Configuration Query Handler', () => {
let getConfigurationQueryHandler: GetConfigurationQueryHandler; let getConfigurationQueryHandler: GetConfigurationQueryHandler;
@ -23,6 +29,10 @@ describe('Get Configuration Query Handler', () => {
provide: CONFIGURATION_REPOSITORY, provide: CONFIGURATION_REPOSITORY,
useValue: mockConfigurationRepository, useValue: mockConfigurationRepository,
}, },
{
provide: ConfigurationsManagerService,
useValue: mockConfigurationsManagerService,
},
GetConfigurationQueryHandler, GetConfigurationQueryHandler,
], ],
}).compile(); }).compile();

View File

@ -1,7 +1,10 @@
import { NotFoundException } from '@mobicoop/configuration-module'; import {
ConfigurationDomain,
NotFoundException,
} from '@mobicoop/configuration-module';
import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens'; import { CONFIGURATION_REPOSITORY } from '@modules/configuration/configuration.di-tokens';
import { ConfigurationsManagerService } from '@modules/configuration/core/application/services/configurations-manager.service';
import { PopulateService } from '@modules/configuration/core/application/services/populate.service'; import { PopulateService } from '@modules/configuration/core/application/services/populate.service';
import { ConfigService } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
const mockConfigurationRepository = { const mockConfigurationRepository = {
@ -14,23 +17,21 @@ const mockConfigurationRepository = {
set: jest.fn(), set: jest.fn(),
}; };
const mockConfigService = { const mockConfigurationsManagerService = {
get: jest.fn().mockImplementation((domain: string) => { list: jest.fn().mockImplementation(() => [
switch (domain) { {
case 'carpool': domain: ConfigurationDomain.CARPOOL,
return { departureTimeMargin: 900,
departureTimeMargin: 900, role: 'passenger',
role: 'passenger', seatsProposed: 3,
seatsProposed: 3, seatsRequested: 1,
seatsRequested: 1, strictFrequency: false,
strictFrequency: false, },
}; {
case 'pagination': domain: ConfigurationDomain.PAGINATION,
return { perPage: 10,
perPage: 10, },
}; ]),
}
}),
}; };
describe('Populate Service', () => { describe('Populate Service', () => {
@ -44,25 +45,18 @@ describe('Populate Service', () => {
useValue: mockConfigurationRepository, useValue: mockConfigurationRepository,
}, },
{ {
provide: ConfigService, provide: ConfigurationsManagerService,
useValue: mockConfigService, useValue: mockConfigurationsManagerService,
}, },
PopulateService, PopulateService,
], ],
}).compile(); }).compile();
populateService = module.get<PopulateService>(PopulateService); populateService = module.get<PopulateService>(PopulateService);
populateService.onApplicationBootstrap();
}); });
it('should be defined', () => { it('should be defined', () => {
expect(populateService).toBeDefined(); expect(populateService).toBeDefined();
}); });
it('should populate database with default values', () => {
jest.spyOn(mockConfigurationRepository, 'get');
jest.spyOn(mockConfigurationRepository, 'set');
populateService.onApplicationBootstrap();
expect(mockConfigurationRepository.get).toHaveBeenCalled();
expect(mockConfigurationRepository.set).toHaveBeenCalled();
});
}); });

View File

@ -3,7 +3,11 @@ import { SetConfigurationRequestDto } from '@modules/configuration/interface/grp
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 { ConfigurationDomain } from '@mobicoop/configuration-module'; import {
ConfigurationDomain,
ConfigurationType,
} from '@mobicoop/configuration-module';
import { ConfigurationsManagerService } from '@modules/configuration/core/application/services/configurations-manager.service';
const setConfigurationRequest: SetConfigurationRequestDto = { const setConfigurationRequest: SetConfigurationRequestDto = {
domain: ConfigurationDomain.CARPOOL, domain: ConfigurationDomain.CARPOOL,
@ -20,6 +24,11 @@ const mockConfigurationRepository = {
}), }),
}; };
const mockConfigurationsManagerService = {
identifierType: jest.fn().mockImplementation(() => ConfigurationType.INT),
cast: jest.fn().mockImplementation(() => 3),
};
describe('Set Configuration Service', () => { describe('Set Configuration Service', () => {
let setConfigurationService: SetConfigurationService; let setConfigurationService: SetConfigurationService;
@ -30,6 +39,10 @@ describe('Set Configuration Service', () => {
provide: CONFIGURATION_REPOSITORY, provide: CONFIGURATION_REPOSITORY,
useValue: mockConfigurationRepository, useValue: mockConfigurationRepository,
}, },
{
provide: ConfigurationsManagerService,
useValue: mockConfigurationsManagerService,
},
SetConfigurationService, SetConfigurationService,
], ],
}).compile(); }).compile();
@ -44,9 +57,13 @@ describe('Set Configuration Service', () => {
}); });
describe('execution', () => { describe('execution', () => {
const setConfigurationCommand = new SetConfigurationCommand( const setConfigurationCommand = new SetConfigurationCommand({
setConfigurationRequest, configurationIdentifier: {
); domain: setConfigurationRequest.domain,
key: setConfigurationRequest.key,
},
value: setConfigurationRequest.value,
});
it('should set an existing configuration item', async () => { it('should set an existing configuration item', async () => {
jest.spyOn(mockConfigurationRepository, 'set'); jest.spyOn(mockConfigurationRepository, 'set');
await setConfigurationService.execute(setConfigurationCommand); await setConfigurationService.execute(setConfigurationCommand);