From be9ecd5ab6a4cc484bad53c2a298f47431c7a347 Mon Sep 17 00:00:00 2001 From: Yulia Belyakova Date: Mon, 13 Feb 2023 10:10:55 +0100 Subject: [PATCH] Add active material and indicator checks to maps --- api/src/modules/h3-data/h3-data.controller.ts | 24 +++++ api/src/modules/h3-data/h3-data.module.ts | 6 ++ api/src/modules/impact/impact.controller.ts | 14 +++ api/src/modules/impact/impact.module.ts | 2 +- api/test/e2e/h3-data/h3-impact-map.spec.ts | 102 ++++++++++++++++++ api/test/e2e/h3-data/h3-material-map.spec.ts | 21 +++- .../e2e/h3-data/mocks/h3-impact-map.mock.ts | 23 +++- 7 files changed, 188 insertions(+), 4 deletions(-) diff --git a/api/src/modules/h3-data/h3-data.controller.ts b/api/src/modules/h3-data/h3-data.controller.ts index 401377182..478b05bee 100644 --- a/api/src/modules/h3-data/h3-data.controller.ts +++ b/api/src/modules/h3-data/h3-data.controller.ts @@ -21,6 +21,8 @@ import { GetScenarioVsScenarioImpactMapDto, } from 'modules/h3-data/dto/get-impact-map.dto'; import { H3DataMapService } from 'modules/h3-data/h3-data-map.service'; +import { MaterialsService } from 'modules/materials/materials.service'; +import { IndicatorsService } from 'modules/indicators/indicators.service'; @Controller('/api/v1/h3') @ApiTags(H3Data.name) @@ -29,6 +31,8 @@ export class H3DataController { constructor( protected readonly h3DataService: H3DataService, protected readonly h3DataMapService: H3DataMapService, + protected readonly materialsService: MaterialsService, + protected readonly indicatorService: IndicatorsService, ) {} @ApiOperation({ description: 'Retrieve H3 data providing its name' }) @@ -100,6 +104,9 @@ export class H3DataController { queryParams: GetMaterialH3ByResolutionDto, ): Promise { const { materialId, resolution, year } = queryParams; + + await this.materialsService.checkActiveMaterials([materialId]); + return await this.h3DataMapService.getMaterialMapByResolutionAndYear( materialId, resolution, @@ -121,6 +128,13 @@ export class H3DataController { async getImpactMap( @Query(ValidationPipe) getImpactMapDto: GetImpactMapDto, ): Promise { + await this.indicatorService.checkActiveIndicatorsForCalculations([ + getImpactMapDto.indicatorId, + ]); + if (getImpactMapDto.materialIds) + await this.materialsService.checkActiveMaterials( + getImpactMapDto.materialIds, + ); return this.h3DataMapService.getImpactMapByResolution(getImpactMapDto); } @@ -139,6 +153,11 @@ export class H3DataController { async getImpactActualVsScenarioComparisonMap( @Query(ValidationPipe) dto: GetActualVsScenarioImpactMapDto, ): Promise { + await this.indicatorService.checkActiveIndicatorsForCalculations([ + dto.indicatorId, + ]); + if (dto.materialIds) + await this.materialsService.checkActiveMaterials(dto.materialIds); return this.h3DataMapService.getImpactMapByResolution(dto); } @@ -157,6 +176,11 @@ export class H3DataController { async getImpactScenarioVsScenarioComparisonMap( @Query(ValidationPipe) dto: GetScenarioVsScenarioImpactMapDto, ): Promise { + await this.indicatorService.checkActiveIndicatorsForCalculations([ + dto.indicatorId, + ]); + if (dto.materialIds) + await this.materialsService.checkActiveMaterials(dto.materialIds); return this.h3DataMapService.getImpactMapByResolution(dto); } } diff --git a/api/src/modules/h3-data/h3-data.module.ts b/api/src/modules/h3-data/h3-data.module.ts index 2e48cf67c..43e45bc59 100644 --- a/api/src/modules/h3-data/h3-data.module.ts +++ b/api/src/modules/h3-data/h3-data.module.ts @@ -13,6 +13,9 @@ import { SuppliersModule } from 'modules/suppliers/suppliers.module'; import { BusinessUnitsModule } from 'modules/business-units/business-units.module'; import { H3DataMapService } from 'modules/h3-data/h3-data-map.service'; import { H3DataRepository } from 'modules/h3-data/h3-data.repository'; +import { MaterialsService } from 'modules/materials/materials.service'; +import { SourcingLocationsModule } from 'modules/sourcing-locations/sourcing-locations.module'; +import { IndicatorsService } from 'modules/indicators/indicators.service'; @Module({ imports: [ @@ -24,6 +27,7 @@ import { H3DataRepository } from 'modules/h3-data/h3-data.repository'; AdminRegionsModule, SuppliersModule, BusinessUnitsModule, + SourcingLocationsModule, ], controllers: [H3DataController], providers: [ @@ -31,6 +35,8 @@ import { H3DataRepository } from 'modules/h3-data/h3-data.repository'; H3DataMapService, H3DataYearsService, H3DataRepository, + MaterialsService, + IndicatorsService, ], exports: [H3DataService, H3DataYearsService], }) diff --git a/api/src/modules/impact/impact.controller.ts b/api/src/modules/impact/impact.controller.ts index 33dd6ac71..2ea72633c 100644 --- a/api/src/modules/impact/impact.controller.ts +++ b/api/src/modules/impact/impact.controller.ts @@ -31,6 +31,7 @@ import { ActualVsScenarioImpactService } from 'modules/impact/comparison/actual- import { SetScenarioIdsInterceptor } from 'modules/impact/set-scenario-ids.interceptor'; import { ScenarioVsScenarioImpactService } from 'modules/impact/comparison/scenario-vs-scenario.service'; import { ScenarioVsScenarioPaginatedImpactTable } from 'modules/impact/dto/response-scenario-scenario.dto'; +import { IndicatorsService } from 'modules/indicators/indicators.service'; import { MaterialsService } from 'modules/materials/materials.service'; @Controller('/api/v1/impact') @@ -41,6 +42,7 @@ export class ImpactController { private readonly impactService: ImpactService, private readonly actualVsScenarioImpactService: ActualVsScenarioImpactService, private readonly scenarioVsScenarioService: ScenarioVsScenarioImpactService, + private readonly indicatorService: IndicatorsService, private readonly materialsService: MaterialsService, ) {} @@ -57,6 +59,9 @@ export class ImpactController { @ProcessFetchSpecification() fetchSpecification: FetchSpecification, @Query(ValidationPipe) impactTableDto: GetImpactTableDto, ): Promise { + await this.indicatorService.checkActiveIndicatorsForCalculations( + impactTableDto.indicatorIds, + ); /* Here we are validating received materialIds to be active, without validating recursively possible descendants, since the Material Ids come from existing impact data (materials present in Sourcing Locations) and existing descendants will be active per default */ @@ -84,6 +89,9 @@ export class ImpactController { @Query(ValidationPipe) scenarioVsScenarioImpactTableDto: GetScenarioVsScenarioImpactTableDto, ): Promise { + await this.indicatorService.checkActiveIndicatorsForCalculations( + scenarioVsScenarioImpactTableDto.indicatorIds, + ); /* Here we are validating received materialIds to be active, without validating recursively possible descendants, since the Material Ids come from existing impact data (materials present in Sourcing Locations) and existing descendants will be active per default */ @@ -112,6 +120,9 @@ export class ImpactController { @Query(ValidationPipe) actualVsScenarioImpactTableDto: GetActualVsScenarioImpactTableDto, ): Promise { + await this.indicatorService.checkActiveIndicatorsForCalculations( + actualVsScenarioImpactTableDto.indicatorIds, + ); /* Here we are validating received materialIds to be active, without validating recursively possible descendants, since the Material Ids come from existing impact data (materials present in Sourcing Locations) and existing descendants will be active per default */ @@ -139,6 +150,9 @@ export class ImpactController { async getRankedImpactTable( @Query(ValidationPipe) rankedImpactTableDto: GetRankedImpactTableDto, ): Promise { + await this.indicatorService.checkActiveIndicatorsForCalculations( + rankedImpactTableDto.indicatorIds, + ); /* Here we are validating received materialIds to be active, without validating recursively possible descendants, since the Material Ids come from existing impact data (materials present in Sourcing Locations) and existing descendants will be active per default */ diff --git a/api/src/modules/impact/impact.module.ts b/api/src/modules/impact/impact.module.ts index e759599f9..42e232731 100644 --- a/api/src/modules/impact/impact.module.ts +++ b/api/src/modules/impact/impact.module.ts @@ -9,7 +9,7 @@ import { SuppliersModule } from 'modules/suppliers/suppliers.module'; import { MaterialsModule } from 'modules/materials/materials.module'; import { ActualVsScenarioImpactService } from 'modules/impact/comparison/actual-vs-scenario.service'; import { ScenarioVsScenarioImpactService } from 'modules/impact/comparison/scenario-vs-scenario.service'; -import { MaterialsService } from '../materials/materials.service'; +import { MaterialsService } from 'modules/materials/materials.service'; import { SourcingLocationsModule } from 'modules/sourcing-locations/sourcing-locations.module'; import { IndicatorsService } from 'modules/indicators/indicators.service'; diff --git a/api/test/e2e/h3-data/h3-impact-map.spec.ts b/api/test/e2e/h3-data/h3-impact-map.spec.ts index e3efe1004..5a86890c8 100644 --- a/api/test/e2e/h3-data/h3-impact-map.spec.ts +++ b/api/test/e2e/h3-data/h3-impact-map.spec.ts @@ -520,6 +520,108 @@ describe('H3 Data Module (e2e) - Impact map', () => { ).toBeTruthy(); }); }); + + describe('Active materials and indicator validations', () => { + test('When I query impact map for inactive material, then I should get a proper error message', async () => { + const response = await request(testApplication.getHttpServer()) + .get(`/api/v1/h3/map/impact`) + .set('Authorization', `Bearer ${jwtToken}`) + .query({ + indicatorId: impactMapMockData.indicatorId, + 'materialIds[]': [impactMapMockData.inactiveMaterialId], + year: 2020, + resolution: 6, + }); + expect(response.body.errors[0].meta.rawError.response.message).toEqual( + 'Following Requested Materials are not activated: Inactive Material', + ); + }); + + test('When I query impact map for inactive indicator, then I should get a proper error message', async () => { + const response = await request(testApplication.getHttpServer()) + .get(`/api/v1/h3/map/impact`) + .set('Authorization', `Bearer ${jwtToken}`) + .query({ + indicatorId: impactMapMockData.inactiveIndicatorId, + 'materialIds[]': [impactMapMockData.materialOneId], + year: 2020, + resolution: 6, + }); + expect(response.body.errors[0].meta.rawError.response.message).toEqual( + 'Requested Indicators are not activated: Inactive Indicator', + ); + }); + + test('When I query Actual vs Scenario comparison map for inactive material, then I should get a proper error message', async () => { + const response = await request(testApplication.getHttpServer()) + .get(`/api/v1/h3/map/impact/compare/actual/vs/scenario`) + .set('Authorization', `Bearer ${jwtToken}`) + .query({ + indicatorId: impactMapMockData.indicatorId, + 'materialIds[]': [impactMapMockData.inactiveMaterialId], + year: 2020, + resolution: 6, + comparedScenarioId: impactMapMockData.scenarioId, + relative: false, + }); + expect(response.body.errors[0].meta.rawError.response.message).toEqual( + 'Following Requested Materials are not activated: Inactive Material', + ); + }); + + test('When I query Actual vs Scenario comparison map for inactive indicator, then I should get a proper error message', async () => { + const response = await request(testApplication.getHttpServer()) + .get(`/api/v1/h3/map/impact/compare/actual/vs/scenario`) + .set('Authorization', `Bearer ${jwtToken}`) + .query({ + indicatorId: impactMapMockData.inactiveIndicatorId, + 'materialIds[]': [impactMapMockData.materialOneId], + year: 2020, + resolution: 6, + comparedScenarioId: impactMapMockData.scenarioId, + relative: false, + }); + expect(response.body.errors[0].meta.rawError.response.message).toEqual( + 'Requested Indicators are not activated: Inactive Indicator', + ); + }); + + test('When I query Scenario vs Scenario comparison map for inactive material, then I should get a proper error message', async () => { + const response = await request(testApplication.getHttpServer()) + .get(`/api/v1/h3/map/impact/compare/scenario/vs/scenario`) + .set('Authorization', `Bearer ${jwtToken}`) + .query({ + indicatorId: impactMapMockData.indicatorId, + 'materialIds[]': [impactMapMockData.inactiveMaterialId], + year: 2020, + resolution: 6, + baseScenarioId: impactMapMockData.scenarioId, + comparedScenarioId: impactMapMockData.scenarioTwoId, + relative: false, + }); + expect(response.body.errors[0].meta.rawError.response.message).toEqual( + 'Following Requested Materials are not activated: Inactive Material', + ); + }); + + test('When I query Scenario vs Scenario comparison map for inactive indicator, then I should get a proper error message', async () => { + const response = await request(testApplication.getHttpServer()) + .get(`/api/v1/h3/map/impact/compare/scenario/vs/scenario`) + .set('Authorization', `Bearer ${jwtToken}`) + .query({ + indicatorId: impactMapMockData.inactiveIndicatorId, + 'materialIds[]': [impactMapMockData.materialOneId], + year: 2020, + resolution: 6, + baseScenarioId: impactMapMockData.scenarioId, + comparedScenarioId: impactMapMockData.scenarioTwoId, + relative: false, + }); + expect(response.body.errors[0].meta.rawError.response.message).toEqual( + 'Requested Indicators are not activated: Inactive Indicator', + ); + }); + }); }); /** diff --git a/api/test/e2e/h3-data/h3-material-map.spec.ts b/api/test/e2e/h3-data/h3-material-map.spec.ts index 3114f7114..c8d3b34af 100644 --- a/api/test/e2e/h3-data/h3-material-map.spec.ts +++ b/api/test/e2e/h3-data/h3-material-map.spec.ts @@ -1,6 +1,6 @@ import * as request from 'supertest'; import { H3DataRepository } from 'modules/h3-data/h3-data.repository'; -import { h3DataMock, dropH3DataMock } from './mocks/h3-data.mock'; +import { dropH3DataMock, h3DataMock } from './mocks/h3-data.mock'; import { createMaterial, createMaterialToH3 } from '../../entity-mocks'; import { MaterialRepository } from 'modules/materials/material.repository'; import { MATERIAL_TO_H3_TYPE } from 'modules/materials/material-to-h3.entity'; @@ -12,6 +12,10 @@ import ApplicationManager, { } from '../../utils/application-manager'; import { DataSource } from 'typeorm'; import { clearTestDataFromDatabase } from '../../utils/database-test-helper'; +import { + Material, + MATERIALS_STATUS, +} from '../../../src/modules/materials/material.entity'; /** * Tests for the H3DataModule. @@ -75,6 +79,21 @@ describe('H3 Data Module (e2e) - Material map', () => { ); }); + test('When I query inactive material H3 data, then I should get a proper error message', async () => { + const material: Material = await createMaterial({ + status: MATERIALS_STATUS.INACTIVE, + name: 'Inactive Material', + }); + const response = await request(testApplication.getHttpServer()) + .get( + `/api/v1/h3/map/material?materialId=${material.id}&resolution=6&year=2020`, + ) + .set('Authorization', `Bearer ${jwtToken}`); + expect(response.body.errors[0].meta.rawError.response.message).toEqual( + 'Following Requested Materials are not activated: Inactive Material', + ); + }); + test('When I query a material H3 data but it has no year value, then I should get a proper error message', async () => { const material = await createMaterial({ name: 'Material with no H3' }); const response = await request(testApplication.getHttpServer()) diff --git a/api/test/e2e/h3-data/mocks/h3-impact-map.mock.ts b/api/test/e2e/h3-data/mocks/h3-impact-map.mock.ts index 61f18ee88..ef23d30d1 100644 --- a/api/test/e2e/h3-data/mocks/h3-impact-map.mock.ts +++ b/api/test/e2e/h3-data/mocks/h3-impact-map.mock.ts @@ -1,8 +1,11 @@ import { Unit } from 'modules/units/unit.entity'; import { UnitConversion } from 'modules/unit-conversions/unit-conversion.entity'; -import { Indicator } from 'modules/indicators/indicator.entity'; +import { + Indicator, + INDICATOR_STATUS, +} from 'modules/indicators/indicator.entity'; import { h3DataMock } from './h3-data.mock'; -import { Material } from 'modules/materials/material.entity'; +import { Material, MATERIALS_STATUS } from 'modules/materials/material.entity'; import { createAdminRegion, createGeoRegion, @@ -40,6 +43,7 @@ import { DataSource } from 'typeorm'; export interface ImpactMapMockData { indicatorId: string; + inactiveIndicatorId: string; unitId: string; adminRegionOneId: string; adminRegionTwoId: string; @@ -49,6 +53,7 @@ export interface ImpactMapMockData { t1SupplierTwoId: string; producerOneId: string; producerTwoId: string; + inactiveMaterialId: string; materialOneId: string; materialTwoId: string; harvestH3DataOneId: string; @@ -79,6 +84,13 @@ export const createImpactMapMockData = async ( indicator.nameCode = 'UWU_T'; await indicator.save(); + const inactiveIndicator: Indicator = new Indicator(); + inactiveIndicator.name = 'Inactive Indicator'; + inactiveIndicator.unit = unit; + inactiveIndicator.status = INDICATOR_STATUS.INACTIVE; + inactiveIndicator.nameCode = 'INA_IN'; + await inactiveIndicator.save(); + const harvestH3Data = await h3DataMock(dataSource, { h3TableName: 'harvestTable', h3ColumnName: 'harvestColumn', @@ -99,6 +111,11 @@ export const createImpactMapMockData = async ( tablesToDrop.push(productionH3Data.h3tableName); + const inactiveMaterial: Material = await createMaterial({ + name: 'Inactive Material', + status: MATERIALS_STATUS.INACTIVE, + }); + const materialOne: Material = await createMaterial({ name: 'MaterialOne', }); @@ -452,6 +469,7 @@ export const createImpactMapMockData = async ( return { indicatorId: indicator.id, + inactiveIndicatorId: inactiveIndicator.id, unitId: unit.id, geoRegionOneId: geoRegionOne.id, geoRegionTwoId: geoRegionTwo.id, @@ -461,6 +479,7 @@ export const createImpactMapMockData = async ( t1SupplierTwoId: t1SupplierTwo.id, producerOneId: producerSupplierOne.id, producerTwoId: producerSupplierTwo.id, + inactiveMaterialId: inactiveMaterial.id, materialOneId: materialOne.id, materialTwoId: materialTwo.id, harvestH3DataOneId: harvestH3Data.id,