diff --git a/api/src/modules/projects/projects.controller.ts b/api/src/modules/projects/projects.controller.ts index 7bdda77..21d5223 100644 --- a/api/src/modules/projects/projects.controller.ts +++ b/api/src/modules/projects/projects.controller.ts @@ -15,6 +15,15 @@ export class ProjectsController { return { body: data, status: HttpStatus.OK }; }); } + + @TsRestHandler(projectsContract.getProjectCountries) + async getProjectCountries(): ControllerResponse { + return tsRestHandler(projectsContract.getProjectCountries, async () => { + const data = await this.projectsService.getProjectCountries(); + return { body: { data }, status: HttpStatus.OK }; + }); + } + @TsRestHandler(projectsContract.getProject) async getProject(): ControllerResponse { return tsRestHandler( @@ -25,12 +34,4 @@ export class ProjectsController { }, ); } - - @TsRestHandler(projectsContract.getProjectCountries) - async getProjectCountries(): ControllerResponse { - return tsRestHandler(projectsContract.getProjectCountries, async () => { - const data = await this.projectsService.getProjectCountries(); - return { body: { data }, status: HttpStatus.OK }; - }); - } } diff --git a/api/src/modules/projects/projects.service.ts b/api/src/modules/projects/projects.service.ts index cc0e653..fd0d577 100644 --- a/api/src/modules/projects/projects.service.ts +++ b/api/src/modules/projects/projects.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { AppBaseService } from '@api/utils/app-base.service'; import { Project } from '@shared/entities/projects.entity'; import { InjectRepository } from '@nestjs/typeorm'; -import { In, Repository } from 'typeorm'; +import { Repository } from 'typeorm'; import { CountryWithNoGeometry } from '@shared/entities/country.entity'; import { CountriesService } from '@api/modules/countries/countries.service'; @@ -23,10 +23,14 @@ export class ProjectsService extends AppBaseService< async getProjectCountries(): Promise { const projects = await this.projectRepository.find(); + + const countryCodes = projects.map((p) => p.countryCode); const [countries] = await this.countryService.findAll({ - filter: { code: In(projects.map((p) => p.countryCode)) }, + filter: { code: countryCodes }, omitFields: ['geometry'], + disablePagination: true, }); + return countries as CountryWithNoGeometry[]; } } diff --git a/api/test/integration/projects/projects.spec.ts b/api/test/integration/projects/projects.spec.ts new file mode 100644 index 0000000..9c782a5 --- /dev/null +++ b/api/test/integration/projects/projects.spec.ts @@ -0,0 +1,80 @@ +import { TestManager } from '../../utils/test-manager'; +import { HttpStatus } from '@nestjs/common'; +import { projectsContract } from '@shared/contracts/projects.contract'; +import { Country } from '@shared/entities/country.entity'; +import { Project } from '@shared/entities/projects.entity'; + +describe('Projects', () => { + let testManager: TestManager; + + beforeAll(async () => { + testManager = await TestManager.createTestManager(); + await testManager.ingestCountries(); + }); + + afterEach(async () => { + await testManager.getDataSource().getRepository(Project).delete({}); + }); + + afterAll(async () => { + await testManager.clearDatabase(); + await testManager.close(); + }); + + describe('Get Projects', () => { + test('Should return a list of Projects', async () => { + const countries = await testManager + .getDataSource() + .getRepository(Country) + .find(); + + const projects: Project[] = []; + for (const country of countries.slice(countries.length / 2)) { + projects.push( + await testManager + .mocks() + .createProject({ countryCode: country.code }), + ); + } + + const response = await testManager + .request() + .get(projectsContract.getProjects.path) + .query({ disablePagination: true }); + + expect(response.status).toBe(HttpStatus.OK); + expect(response.body.data.length).toBe(projects.length); + }); + }); + + describe('Filters for Projects', () => { + test('Should get a list of countries there are projects in', async () => { + const fiveCountriesWithNoGeometry = await testManager + .getDataSource() + .getRepository(Country) + .find() + .then((countries) => + countries.slice(0, 5).map((c) => { + const { geometry, ...rest } = c; + return rest; + }), + ); + + const projects: Project[] = []; + for (const country of fiveCountriesWithNoGeometry) { + projects.push( + await testManager + .mocks() + .createProject({ countryCode: country.code }), + ); + } + const response = await testManager + .request() + .get(projectsContract.getProjectCountries.path); + + expect(fiveCountriesWithNoGeometry).toEqual( + expect.arrayContaining(response.body.data), + ); + }); + }); +}); diff --git a/api/test/utils/test-manager.ts b/api/test/utils/test-manager.ts index 96cf5af..6b86f79 100644 --- a/api/test/utils/test-manager.ts +++ b/api/test/utils/test-manager.ts @@ -12,11 +12,16 @@ import { User } from '@shared/entities/users/user.entity'; import { IEmailServiceToken } from '@api/modules/notifications/email/email-service.interface'; import { MockEmailService } from './mocks/mock-email.service'; import { ROLES } from '@shared/entities/users/roles.enum'; -import { createBaseData, createUser } from '@shared/lib/entity-mocks'; +import { + createBaseData, + createProject, + createUser, +} from '@shared/lib/entity-mocks'; import { clearTestDataFromDatabase } from '@shared/lib/db-helpers'; import * as path from 'path'; import * as fs from 'fs'; import { BaseData } from '@shared/entities/base-data.entity'; +import { Project } from '@shared/entities/projects.entity'; /** * @description: Abstraction for NestJS testing workflow. For now its a basic implementation to create a test app, but can be extended to encapsulate * common testing utilities @@ -105,6 +110,8 @@ export class TestManager { createUser(this.getDataSource(), additionalData), createBaseData: async (additionalData?: Partial) => createBaseData(this.getDataSource(), additionalData), + createProject: async (additionalData?: Partial) => + createProject(this.getDataSource(), additionalData), }; } } diff --git a/shared/lib/entity-mocks.ts b/shared/lib/entity-mocks.ts index fe306ff..2f71fcd 100644 --- a/shared/lib/entity-mocks.ts +++ b/shared/lib/entity-mocks.ts @@ -6,6 +6,12 @@ import { BaseData, ECOSYSTEM, } from "@shared/entities/base-data.entity"; +import { + Project, + PROJECT_PRICE_TYPE, + PROJECT_SIZE_FILTER, +} from "@shared/entities/projects.entity"; +import { Country } from "@shared/entities/country.entity"; export const createUser = async ( dataSource: DataSource, @@ -33,5 +39,37 @@ export const createBaseData = async ( baseData.countryCode = "AND"; baseData.activity = ACTIVITY.CONSERVATION; - return dataSource.getRepository(BaseData).save(baseData); + return dataSource + .getRepository(BaseData) + .save({ ...baseData, ...additionalData }); +}; + +export const createProject = async ( + dataSource: DataSource, + additionalData?: DeepPartial, +): Promise => { + const countries = await dataSource.getRepository(Country).find(); + if (!countries.length) { + throw new Error("No countries in the database"); + } + const defaultProjectData: Partial = { + projectName: "Test Project" + Math.random().toString(36).substring(7), + countryCode: countries[0].code, + activity: ACTIVITY.CONSERVATION, + ecosystem: ECOSYSTEM.MANGROVE, + activitySubtype: "Planting", + projectSize: 100, + projectSizeFilter: PROJECT_SIZE_FILTER.LARGE, + abatementPotential: 100, + totalCostNPV: 100, + totalCost: 100, + costPerTCO2eNPV: 100, + costPerTCO2e: 100, + initialPriceAssumption: "$100", + priceType: PROJECT_PRICE_TYPE.MARKET_PRICE, + }; + + return dataSource + .getRepository(Project) + .save({ ...defaultProjectData, ...additionalData }); };