Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test/piece exporters integration test #944

Merged
merged 3 commits into from
Apr 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class ExportConfigProjectPieceExporter implements ExportPieceProcessor {
const projectId = input.resourceId;
const [project]: {
name: string;
description: string;
description?: string;
}[] = await this.entityManager
.createQueryBuilder()
.select('name')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type SelectScenarioResult = {
type CustomProtectedArea = {
ewkb: Buffer;
name: string;
projectId: string;
};

type WdpaProtectedArea = {
Expand Down Expand Up @@ -76,6 +77,7 @@ export class ScenarioProtectedAreasPieceExporter
.select('wdpaid')
.addSelect('ST_AsEWKB(the_geom)', 'ewkb')
.addSelect('full_name', 'name')
.addSelect('project_id', 'projectId')
.from('wdpa', 'pa')
.where('pa.id IN (:...paIds)', {
paIds: scenario.protectedAreasIds,
Expand All @@ -91,7 +93,7 @@ export class ScenarioProtectedAreasPieceExporter

const customProtectedAreas = protectedAreas
.filter((pa): pa is CustomProtectedArea =>
isDefined((pa as CustomProtectedArea).name),
isDefined((pa as CustomProtectedArea).projectId),
)
.map((pa) => ({
name: pa.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type PreviousBlmResultsSelectResult = {
boundaryLength: number;
};
type MarxanRunSelectResult = {
includedCount: number;
includedCount: string;
values: boolean[];
puid: number;
};
Expand Down Expand Up @@ -51,7 +51,7 @@ export class ScenarioRunResultsPieceExporter implements ExportPieceProcessor {
.where('scenario_id = :scenarioId', { scenarioId: input.resourceId })
.execute();

const marxanRunResults: MarxanRunSelectResult[] = await this.geoprocessingEntityManager
const marxanRunSelectResult: MarxanRunSelectResult[] = await this.geoprocessingEntityManager
.createQueryBuilder()
.select('included_count', 'includedCount')
.addSelect('value', 'values')
Expand All @@ -74,6 +74,10 @@ export class ScenarioRunResultsPieceExporter implements ExportPieceProcessor {
.innerJoin('projects_pu', 'ppu', 'ppu.id = spd.project_pu_id')
.execute();

const marxanRunResults = marxanRunSelectResult.map((result) => ({
...result,
includedCount: Number(result.includedCount),
}));
const content: ScenarioRunResultsContent = { blmResults, marxanRunResults };

const outputFile = await this.fileRepository.save(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import { geoprocessingConnections } from '@marxan-geoprocessing/ormconfig';
import { ClonePiece, ExportJobInput } from '@marxan/cloning';
import { ResourceKind } from '@marxan/cloning/domain';
import { FileRepository, FileRepositoryModule } from '@marxan/files-repository';
import { FixtureType } from '@marxan/utils/tests/fixture-type';
import { Logger } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { getEntityManagerToken, TypeOrmModule } from '@nestjs/typeorm';
import { isLeft, Right } from 'fp-ts/lib/Either';
import { Readable } from 'stream';
import { EntityManager } from 'typeorm';
import { v4 } from 'uuid';
import {
exportVersion,
ProjectExportConfigContent,
} from '@marxan/cloning/infrastructure/clone-piece-data/export-config';
import { ExportConfigProjectPieceExporter } from '@marxan-geoprocessing/export/pieces-exporters/export-config.project-piece-exporter';
import {
DeleteProjectAndOrganization,
GivenProjectExists,
GivenScenarioExists,
readSavedFile,
} from './fixtures';

let fixtures: FixtureType<typeof getFixtures>;

interface FixtureOptions {
projectWithScenario: boolean;
}

const defaultFixtureOptions: FixtureOptions = {
projectWithScenario: false,
};

describe(ExportConfigProjectPieceExporter, () => {
beforeEach(async () => {
fixtures = await getFixtures();
}, 10_000);

afterEach(async () => {
await fixtures?.cleanUp();
});

it('should throw when project is not found ', async () => {
const input = fixtures.GivenAExportConfigProjectExportJob();
await fixtures
.WhenPieceExporterIsInvoked(input)
.ThenAProjectExistErrorShouldBeThrown();
});
it('should save file succesfully when project is found', async () => {
const input = fixtures.GivenAExportConfigProjectExportJob();
await fixtures.GivenProjectExist();

await fixtures
.WhenPieceExporterIsInvoked(input)
.ThenExportConfigProjectFileIsSaved();
});
it('should save file succesfully when project with a scenario is found', async () => {
const options = { projectWithScenario: true };
const input = fixtures.GivenAExportConfigProjectExportJob(options);
await fixtures.GivenProjectExist(options);
await fixtures
.WhenPieceExporterIsInvoked(input)
.ThenExportConfigProjectFileIsSaved(options);
});
});

const getFixtures = async () => {
const sandbox = await Test.createTestingModule({
imports: [
TypeOrmModule.forRoot({
...geoprocessingConnections.apiDB,
keepConnectionAlive: true,
logging: false,
}),
FileRepositoryModule,
],
providers: [
ExportConfigProjectPieceExporter,
{ provide: Logger, useValue: { error: () => {}, setContext: () => {} } },
],
}).compile();

await sandbox.init();
const projectId = v4();
const scenarioId = v4();
const organizationId = v4();
const sut = sandbox.get(ExportConfigProjectPieceExporter);
const apiEntityManager: EntityManager = sandbox.get(
getEntityManagerToken(geoprocessingConnections.apiDB),
);
const fileRepository = sandbox.get(FileRepository);

const getExpectedContent = (
options: FixtureOptions,
): ProjectExportConfigContent => {
let scenarios: Record<string, ClonePiece[]> = {};
if (options.projectWithScenario)
scenarios[scenarioId] = [ClonePiece.ScenarioMetadata];
return {
name: `test project - ${projectId}`,
version: exportVersion,
resourceKind: ResourceKind.Project,
resourceId: projectId,
scenarios: options.projectWithScenario
? [{ id: scenarioId, name: `test scenario - ${scenarioId}` }]
: [],
pieces: {
project: [ClonePiece.ProjectMetadata, ClonePiece.ExportConfig],
scenarios,
},
};
};

return {
cleanUp: async () => {
return DeleteProjectAndOrganization(
apiEntityManager,
projectId,
organizationId,
);
},
GivenAExportConfigProjectExportJob: (
options = defaultFixtureOptions,
): ExportJobInput => {
const scenarioPiece = options.projectWithScenario
? [{ resourceId: scenarioId, piece: ClonePiece.ScenarioMetadata }]
: [];
return {
allPieces: [
{ resourceId: projectId, piece: ClonePiece.ProjectMetadata },
{ resourceId: projectId, piece: ClonePiece.ExportConfig },
...scenarioPiece,
],
componentId: v4(),
exportId: v4(),
piece: ClonePiece.ExportConfig,
resourceId: projectId,
resourceKind: ResourceKind.Project,
};
},
GivenProjectExist: async (options = defaultFixtureOptions) => {
if (!options.projectWithScenario)
return GivenProjectExists(apiEntityManager, projectId, organizationId);

return GivenScenarioExists(
apiEntityManager,
scenarioId,
projectId,
organizationId,
);
},
WhenPieceExporterIsInvoked: (input: ExportJobInput) => {
return {
ThenAProjectExistErrorShouldBeThrown: async () => {
await expect(sut.run(input)).rejects.toThrow(/Project with ID/gi);
},
ThenExportConfigProjectFileIsSaved: async (
options = defaultFixtureOptions,
) => {
const result = await sut.run(input);
const file = await fileRepository.get(result.uris[0].uri);
expect((file as Right<Readable>).right).toBeDefined();
const expectedContent = getExpectedContent(options);
if (isLeft(file)) throw new Error();
const savedStrem = file.right;
const content = await readSavedFile<ProjectExportConfigContent>(
savedStrem,
);
expect(content).toMatchObject(expectedContent);
},
};
},
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { geoprocessingConnections } from '@marxan-geoprocessing/ormconfig';
import { ClonePiece, ExportJobInput } from '@marxan/cloning';
import { ResourceKind } from '@marxan/cloning/domain';
import { FileRepository, FileRepositoryModule } from '@marxan/files-repository';
import { FixtureType } from '@marxan/utils/tests/fixture-type';
import { Logger } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { getEntityManagerToken, TypeOrmModule } from '@nestjs/typeorm';
import { isLeft, Right } from 'fp-ts/lib/Either';
import { Readable } from 'stream';
import { EntityManager } from 'typeorm';
import { v4 } from 'uuid';
import {
exportVersion,
ScenarioExportConfigContent,
} from '@marxan/cloning/infrastructure/clone-piece-data/export-config';
import { ExportConfigScenarioPieceExporter } from '@marxan-geoprocessing/export/pieces-exporters/export-config.scenario-piece-exporter';
import {
DeleteProjectAndOrganization,
GivenScenarioExists,
readSavedFile,
} from './fixtures';

let fixtures: FixtureType<typeof getFixtures>;

describe(ExportConfigScenarioPieceExporter, () => {
beforeEach(async () => {
fixtures = await getFixtures();
}, 10_000);

afterEach(async () => {
await fixtures?.cleanUp();
});

it('should throw when scenario is not found ', async () => {
const input = fixtures.GivenAExportConfigScenarioExportJob();
await fixtures
.WhenPieceExporterIsInvoked(input)
.ThenASceanarioExistErrorShouldBeThrown();
});
it('should save file succesfully when scenario is found', async () => {
const input = fixtures.GivenAExportConfigScenarioExportJob();
await fixtures.GivenScenarioExist();
await fixtures
.WhenPieceExporterIsInvoked(input)
.ThenExportConfigScenarioFileIsSaved();
});
});

const getFixtures = async () => {
const sandbox = await Test.createTestingModule({
imports: [
TypeOrmModule.forRoot({
...geoprocessingConnections.apiDB,
keepConnectionAlive: true,
logging: false,
}),
FileRepositoryModule,
],
providers: [
ExportConfigScenarioPieceExporter,
{ provide: Logger, useValue: { error: () => {}, setContext: () => {} } },
],
}).compile();

await sandbox.init();
const projectId = v4();
const scenarioId = v4();
const organizationId = v4();
const sut = sandbox.get(ExportConfigScenarioPieceExporter);
const apiEntityManager: EntityManager = sandbox.get(
getEntityManagerToken(geoprocessingConnections.apiDB),
);
const fileRepository = sandbox.get(FileRepository);
const expectedContent: ScenarioExportConfigContent = {
name: `test scenario - ${scenarioId}`,
version: exportVersion,
projectId,
resourceKind: ResourceKind.Scenario,
resourceId: scenarioId,
pieces: [ClonePiece.ScenarioMetadata, ClonePiece.ExportConfig],
};

return {
cleanUp: async () => {
return DeleteProjectAndOrganization(
apiEntityManager,
projectId,
organizationId,
);
},
GivenAExportConfigScenarioExportJob: (): ExportJobInput => {
return {
allPieces: [
{ resourceId: scenarioId, piece: ClonePiece.ScenarioMetadata },
{ resourceId: scenarioId, piece: ClonePiece.ExportConfig },
],
componentId: v4(),
exportId: v4(),
piece: ClonePiece.ExportConfig,
resourceId: scenarioId,
resourceKind: ResourceKind.Scenario,
};
},
GivenScenarioExist: async () => {
return GivenScenarioExists(
apiEntityManager,
scenarioId,
projectId,
organizationId,
);
},
WhenPieceExporterIsInvoked: (input: ExportJobInput) => {
return {
ThenASceanarioExistErrorShouldBeThrown: async () => {
await expect(sut.run(input)).rejects.toThrow(/Scenario with ID/gi);
},
ThenExportConfigScenarioFileIsSaved: async () => {
const result = await sut.run(input);
const file = await fileRepository.get(result.uris[0].uri);
expect((file as Right<Readable>).right).toBeDefined();
if (isLeft(file)) throw new Error();
const savedStrem = file.right;
const content = await readSavedFile<ScenarioExportConfigContent>(
savedStrem,
);
expect(content).toMatchObject(expectedContent);
},
};
},
};
};
Loading