Skip to content

Commit

Permalink
Merge pull request #196 from Vizzuality/feat/MARXAN-413-cost-surface-…
Browse files Browse the repository at this point in the history
…application

feat(api): cost-surface: application logic
  • Loading branch information
kgajowy authored May 19, 2021
2 parents fb5cf80 + 35fcfd9 commit 7613de4
Show file tree
Hide file tree
Showing 17 changed files with 423 additions and 31 deletions.
21 changes: 21 additions & 0 deletions api/src/migrations/api/1621341638168-CostSurfaceEvents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class CostSurfaceEvents1621341638168 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
INSERT INTO api_event_kinds (id) values
('scenario.costSurface.submitted/v1alpha1'),
('scenario.costSurface.shapeConverted/v1alpha1'),
('scenario.costSurface.shapeConversionFailed/v1alpha1'),
('scenario.costSurface.costUpdateFailed/v1alpha1'),
('scenario.costSurface.finished/v1alpha1');
`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DELETE FROM api_event_kinds WHERE id like 'scenario.costSurface.%';`,
);
}
}
5 changes: 5 additions & 0 deletions api/src/modules/api-events/api-event.api.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ export enum API_EVENT_KINDS {
user__passwordResetTokenGenerated__v1alpha1 = 'user.passwordResetTokenGenerated/v1alpha1',
user__passwordResetSucceeded__v1alpha1 = 'user.passwordResetSucceeded/v1alpha1',
user__passwordResetFailed__v1alpha1 = 'user.passwordResetFailed/v1alpha1',
scenario__costSurface__submitted__v1_alpha1 = 'scenario.costSurface.submitted/v1alpha1',
scenario__costSurface__shapeConverted__v1_alpha1 = 'scenario.costSurface.shapeConverted/v1alpha1',
scenario__costSurface__shapeConversionFailed__v1_alpha1 = 'scenario.costSurface.shapeConversionFailed/v1alpha1',
scenario__costSurface__costUpdateFailed__v1_alpha1 = 'scenario.costSurface.costUpdateFailed/v1alpha1',
scenario__costSurface__finished__v1_alpha1 = 'scenario.costSurface.finished/v1alpha1',
}

/**
Expand Down
2 changes: 1 addition & 1 deletion api/src/modules/api-events/api-events.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ export const logger = new Logger('ApiEvents');
],
providers: [ApiEventsService],
controllers: [ApiEventsController],
exports: [ApiEventsService],
exports: [ApiEventsService, TypeOrmModule],
})
export class ApiEventsModule {}
6 changes: 3 additions & 3 deletions api/src/modules/api-events/api-events.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ import { isNil } from 'lodash';
import {
AppBaseService,
JSONAPISerializerConfig,
} from 'utils/app-base.service';
} from '../../utils/app-base.service';
import { CreateApiEventDTO } from './dto/create.api-event.dto';
import { UpdateApiEventDTO } from './dto/update.api-event.dto';
import { AppInfoDTO } from 'dto/info.dto';
import { AppConfig } from 'utils/config.utils';
import { AppInfoDTO } from '../../dto/info.dto';
import { AppConfig } from '../../utils/config.utils';

@Injectable()
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Injectable } from '@nestjs/common';
import { AdjustCostSurface } from '../../../analysis/entry-points/adjust-cost-surface';
import { CostSurfaceInputDto } from '../../../analysis/entry-points/adjust-cost-surface-input';

@Injectable()
export class AdjustCostServiceFake implements AdjustCostSurface {
mock = jest.fn();

async update(
scenarioId: string,
constraints: CostSurfaceInputDto,
): Promise<true> {
return this.mock(scenarioId, constraints);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Injectable, Scope } from '@nestjs/common';
import {
CostSurfaceEventsPort,
CostSurfaceState,
} from '../cost-surface-events.port';

@Injectable()
export class CostSurfaceEventsFake implements CostSurfaceEventsPort {
mock = jest.fn();
events: [string, CostSurfaceState][] = [];

async event(
scenarioId: string,
state: CostSurfaceState,
context?: Record<string, unknown> | Error,
): Promise<void> {
this.events.push([scenarioId, state]);
return this.mock(scenarioId, state, context);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Injectable } from '@nestjs/common';
import { ResolvePuWithCost } from '../resolve-pu-with-cost';
import { CostSurfaceInputDto } from '../../../analysis/entry-points/adjust-cost-surface-input';

@Injectable()
export class ShapefileConverterFake implements ResolvePuWithCost {
mock = jest.fn();

async fromShapefile(file: Express.Multer.File): Promise<CostSurfaceInputDto> {
return this.mock(file);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { CostSurfaceInputDto } from '../../../analysis/entry-points/adjust-cost-surface-input';

export const getValidSurfaceCost = (): CostSurfaceInputDto => ({
planningUnits: [
{
id: 'pu-id-1',
cost: 300,
},
{
id: 'pu-id-2',
cost: 2000,
},
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { getRepositoryToken } from '@nestjs/typeorm';
import { Test } from '@nestjs/testing';
import { Repository } from 'typeorm';

import {
API_EVENT_KINDS,
ApiEvent,
} from '../../../api-events/api-event.api.entity';

import { CostSurfaceApiEvents } from './cost-surface-api-events';
import { CostSurfaceState } from '../cost-surface-events.port';

import { fakeQueryBuilder } from '../../../../utils/__mocks__/fake-query-builder';
import { LatestApiEventByTopicAndKind } from '../../../api-events/api-event.topic+kind.api.entity';

const scenarioId = 'scenario-uuid';
const cases: [CostSurfaceState, API_EVENT_KINDS][] = [
[
CostSurfaceState.Submitted,
API_EVENT_KINDS.scenario__costSurface__submitted__v1_alpha1,
],
[
CostSurfaceState.ShapefileConverted,
API_EVENT_KINDS.scenario__costSurface__shapeConverted__v1_alpha1,
],
[
CostSurfaceState.ShapefileConversionFailed,
API_EVENT_KINDS.scenario__costSurface__shapeConversionFailed__v1_alpha1,
],
[
CostSurfaceState.CostUpdateFailed,
API_EVENT_KINDS.scenario__costSurface__costUpdateFailed__v1_alpha1,
],
[
CostSurfaceState.Finished,
API_EVENT_KINDS.scenario__costSurface__finished__v1_alpha1,
],
];

let sut: CostSurfaceApiEvents;
let repoMock: jest.Mocked<Repository<ApiEvent>>;

beforeEach(async () => {
const apiEventsToken = getRepositoryToken(ApiEvent);
const sandbox = await Test.createTestingModule({
providers: [
CostSurfaceApiEvents,
{
provide: apiEventsToken,
useValue: {
metadata: {
name: 'required-by-base-service-for-logging',
},
createQueryBuilder: () => fakeQueryBuilder(jest.fn()),
save: jest.fn().mockResolvedValue({}),
},
},
{
provide: getRepositoryToken(LatestApiEventByTopicAndKind),
useValue: jest.fn(),
},
],
}).compile();

sut = sandbox.get(CostSurfaceApiEvents);
repoMock = sandbox.get(apiEventsToken);
});

test.each(cases)(`emits %p as %p`, async (event, kind) => {
await sut.event(scenarioId, event);
expect(repoMock.save.mock.calls[0][0]).toEqual({
data: {},
topic: scenarioId,
kind,
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Injectable } from '@nestjs/common';
import {
CostSurfaceEventsPort,
CostSurfaceState,
} from '../cost-surface-events.port';
import { ApiEventsService } from '../../../api-events/api-events.service';
import { API_EVENT_KINDS } from '../../../api-events/api-event.api.entity';

@Injectable()
export class CostSurfaceApiEvents
extends ApiEventsService
implements CostSurfaceEventsPort {
private readonly eventsMap: Record<CostSurfaceState, API_EVENT_KINDS> = {
[CostSurfaceState.Submitted]:
API_EVENT_KINDS.scenario__costSurface__submitted__v1_alpha1,
[CostSurfaceState.ShapefileConverted]:
API_EVENT_KINDS.scenario__costSurface__shapeConverted__v1_alpha1,
[CostSurfaceState.ShapefileConversionFailed]:
API_EVENT_KINDS.scenario__costSurface__shapeConversionFailed__v1_alpha1,
[CostSurfaceState.CostUpdateFailed]:
API_EVENT_KINDS.scenario__costSurface__costUpdateFailed__v1_alpha1,
[CostSurfaceState.Finished]:
API_EVENT_KINDS.scenario__costSurface__finished__v1_alpha1,
};

async event(scenarioId: string, state: CostSurfaceState): Promise<void> {
await this.create({
data: {},
topic: scenarioId,
kind: this.eventsMap[state],
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Injectable } from '@nestjs/common';
import { ResolvePuWithCost } from '../resolve-pu-with-cost';
import { CostSurfaceInputDto } from '../../../analysis/entry-points/adjust-cost-surface-input';

@Injectable()
export class GeoprocessingCostFromShapefile implements ResolvePuWithCost {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async fromShapefile(file: Express.Multer.File): Promise<CostSurfaceInputDto> {
return {
planningUnits: [],
};
}
}
15 changes: 15 additions & 0 deletions api/src/modules/scenarios/cost-surface/cost-surface-events.port.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export enum CostSurfaceState {
Submitted = 'submitted',
ShapefileConverted = 'shapefile-converted',
ShapefileConversionFailed = 'shapefile-conversion-failed',
CostUpdateFailed = 'cost-update-failed',
Finished = 'finished',
}

export abstract class CostSurfaceEventsPort {
abstract event(
scenarioId: string,
state: CostSurfaceState,
context?: Record<string, unknown> | Error,
): Promise<void>;
}
Loading

0 comments on commit 7613de4

Please sign in to comment.