From ccdd4b47de7501cb6a23448741f8ed5512572851 Mon Sep 17 00:00:00 2001 From: alexeh Date: Sun, 10 Nov 2024 06:59:01 +0100 Subject: [PATCH] conservation project validations --- .../dto/conservation-project-params.dto.ts | 15 ++ .../dto/project-params.validator.ts | 4 +- .../custom-projects-validations.spec.ts | 155 ++++++++++++------ 3 files changed, 119 insertions(+), 55 deletions(-) diff --git a/api/src/modules/custom-projects/dto/conservation-project-params.dto.ts b/api/src/modules/custom-projects/dto/conservation-project-params.dto.ts index c30410c4..a6d933ec 100644 --- a/api/src/modules/custom-projects/dto/conservation-project-params.dto.ts +++ b/api/src/modules/custom-projects/dto/conservation-project-params.dto.ts @@ -44,6 +44,11 @@ export class ConservationProjectParamDto { o.projectSpecificEmission === PROJECT_SPECIFIC_EMISSION.TWO_EMISSION_FACTORS, ) + @IsNotEmpty({ + message: + 'Emission Factor AGB is required when emissionFactorUsed is Tier 3 and projectSpecificEmission is Two emission factors', + }) + @IsNumber() emissionFactorAGB: number; @ValidateIf( @@ -52,6 +57,11 @@ export class ConservationProjectParamDto { o.projectSpecificEmission === PROJECT_SPECIFIC_EMISSION.TWO_EMISSION_FACTORS, ) + @IsNotEmpty({ + message: + 'Emission Factor SOC is required when emissionFactorUsed is Tier 3 and projectSpecificEmission is Two emission factors', + }) + @IsNumber() emissionFactorSOC: number; @ValidateIf((o) => o.emissionFactorUsed === PROJECT_EMISSION_FACTORS.TIER_3) @@ -64,5 +74,10 @@ export class ConservationProjectParamDto { o.projectSpecificEmission === PROJECT_SPECIFIC_EMISSION.ONE_EMISSION_FACTOR, ) + @IsNotEmpty({ + message: + 'Project Specific Emission Factor must be provided when emissionFactorUsed is Tier 3 and projectSpecificEmission is One emission factor', + }) + @IsNumber() projectSpecificEmissionFactor: number; } diff --git a/api/src/modules/custom-projects/dto/project-params.validator.ts b/api/src/modules/custom-projects/dto/project-params.validator.ts index 60ecbdaf..6e4ef511 100644 --- a/api/src/modules/custom-projects/dto/project-params.validator.ts +++ b/api/src/modules/custom-projects/dto/project-params.validator.ts @@ -45,9 +45,7 @@ export class ProjectParamsValidator implements ValidatorConstraintInterface { const formattedErrors = []; errors.forEach((error) => { Object.values(error.constraints).forEach((constraint) => { - formattedErrors.push({ - message: constraint, - }); + formattedErrors.push(constraint); }); }); return formattedErrors; diff --git a/api/test/integration/custom-projects/custom-projects-validations.spec.ts b/api/test/integration/custom-projects/custom-projects-validations.spec.ts index 1a3461e9..2df70018 100644 --- a/api/test/integration/custom-projects/custom-projects-validations.spec.ts +++ b/api/test/integration/custom-projects/custom-projects-validations.spec.ts @@ -1,5 +1,6 @@ import { TestManager } from '../../utils/test-manager'; import { customProjectContract } from '@shared/contracts/custom-projects.contract'; +import { PROJECT_EMISSION_FACTORS } from '@api/modules/custom-projects/dto/conservation-project-params.dto'; describe('Create Custom Projects - Request Validations', () => { let testManager: TestManager; @@ -20,11 +21,10 @@ describe('Create Custom Projects - Request Validations', () => { .send({}); expect(response.body.errors).toHaveLength(10); - expect(response.body.errors).toMatchObject(GENERAL_VALIDATION_ERRORS); }); }); describe('Conservation Project Validations', () => { - test('If Loss Rate used is National Average, Project Specific Loss Rate should not be provided', async () => { + test('Loss Rate Used should be National Average or Project Specific', async () => { const response = await testManager .request() .post(customProjectContract.createCustomProject.path) @@ -37,61 +37,112 @@ describe('Create Custom Projects - Request Validations', () => { initialCarbonPriceAssumption: 1000, carbonRevenuesToCover: 'Opex', parameters: { - lossRateUsed: 'National average', + lossRateUsed: 'Invalid', emissionFactorUsed: 'Tier 1 - Global emission factor', - projectSpecificLossRate: -0.5, }, }); expect(response.body.errors).toHaveLength(1); + expect(response.body.errors[0].title).toEqual( + 'lossRateUsed must be one of the following values: National average, Project specific', + ); + }); + test('If Loss Rate used is Project Specific, Project Specific Loss Rate should be provided and be negative', async () => { + const response = await testManager + .request() + .post(customProjectContract.createCustomProject.path) + .send({ + countryCode: 'IND', + activity: 'Conservation', + ecosystem: 'Mangrove', + projectName: 'My custom project', + projectSizeHa: 1000, + initialCarbonPriceAssumption: 1000, + carbonRevenuesToCover: 'Opex', + parameters: { + lossRateUsed: 'Project specific', + emissionFactorUsed: 'Tier 1 - Global emission factor', + projectSpecificLossRate: 0.5, + }, + }); + expect(response.body.errors).toHaveLength(1); + expect(response.body.errors[0].title).toEqual( + 'Project Specific Loss Rate must be negative', + ); + }); + test('If Emission Factor Used is Tier 2, only Mangroves is accepted as ecosystem', async () => { + const response = await testManager + .request() + .post(customProjectContract.createCustomProject.path) + .send({ + countryCode: 'IND', + activity: 'Conservation', + ecosystem: 'Seagrass', + projectName: 'My custom project', + projectSizeHa: 1000, + initialCarbonPriceAssumption: 1000, + carbonRevenuesToCover: 'Opex', + parameters: { + lossRateUsed: 'National average', + emissionFactorUsed: PROJECT_EMISSION_FACTORS.TIER_2, + projectSpecificEmission: 'One emission factor', + }, + }); + expect(response.body.errors).toHaveLength(1); + expect(response.body.errors[0].title).toEqual( + 'There is only Tier 2 emission factor for Mangrove ecosystems', + ); + }); + test('If Emission Factor Used is Tier 3 and Project Specific Emission is Two Emission Factors, AGB and SOC should be provided', async () => { + const response = await testManager + .request() + .post(customProjectContract.createCustomProject.path) + .send({ + countryCode: 'IND', + activity: 'Conservation', + ecosystem: 'Mangrove', + projectName: 'My custom project', + projectSizeHa: 1000, + initialCarbonPriceAssumption: 1000, + carbonRevenuesToCover: 'Opex', + parameters: { + lossRateUsed: 'National average', + emissionFactorUsed: PROJECT_EMISSION_FACTORS.TIER_3, + projectSpecificEmission: 'Two emission factors', + }, + }); + const errorTitles = response.body.errors.map((error) => error.title); + expect(errorTitles).toEqual( + expect.arrayContaining([ + 'Emission Factor AGB is required when emissionFactorUsed is Tier 3 and projectSpecificEmission is Two emission factors', + 'Emission Factor SOC is required when emissionFactorUsed is Tier 3 and projectSpecificEmission is Two emission factors', + ]), + ); + }); + test('If Emission Factor Used is Tier 3 and Project Specific Emission is One Emission Factor, then Project Specific Emission Factor should be provided', async () => { + const response = await testManager + .request() + .post(customProjectContract.createCustomProject.path) + .send({ + countryCode: 'IND', + activity: 'Conservation', + ecosystem: 'Mangrove', + projectName: 'My custom project', + projectSizeHa: 1000, + initialCarbonPriceAssumption: 1000, + carbonRevenuesToCover: 'Opex', + parameters: { + lossRateUsed: 'National average', + emissionFactorUsed: PROJECT_EMISSION_FACTORS.TIER_3, + projectSpecificEmission: 'One emission factor', + }, + }); + const errorTitles = response.body.errors.map((error) => error.title); + expect(errorTitles).toEqual( + expect.arrayContaining([ + 'Project Specific Emission Factor must be provided when emissionFactorUsed is Tier 3 and projectSpecificEmission is One emission factor', + ]), + ); }); }); }); - -const GENERAL_VALIDATION_ERRORS = [ - { - status: '400', - title: 'countryCode must be longer than or equal to 3 characters', - }, - { - status: '400', - title: 'countryCode must be a string', - }, - { - status: '400', - title: 'projectName must be a string', - }, - { - status: '400', - title: - 'activity must be one of the following values: Restoration, Conservation', - }, - { - status: '400', - title: - 'ecosystem must be one of the following values: Mangrove, Seagrass, Salt marsh', - }, - { - status: '400', - title: - 'projectSizeHa must be a number conforming to the specified constraints', - }, - { - status: '400', - title: - 'initialCarbonPriceAssumption must be a number conforming to the specified constraints', - }, - { - status: '400', - title: - 'carbonRevenuesToCover must be one of the following values: Opex, Capex and Opex', - }, - { - status: '400', - title: 'Invalid project parameters for the selected activity type.', - }, - { - status: '400', - title: 'parameters should not be empty', - }, -];