-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(api): cost-surface: application logic
- Loading branch information
Showing
12 changed files
with
292 additions
and
25 deletions.
There are no files selected for viewing
15 changes: 15 additions & 0 deletions
15
api/src/modules/scenarios/cost-surface/__mocks__/adjust-cost-service-fake.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
20 changes: 20 additions & 0 deletions
20
api/src/modules/scenarios/cost-surface/__mocks__/cost-surface-events-fake.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
api/src/modules/scenarios/cost-surface/__mocks__/shapefile-converter-fake.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
api/src/modules/scenarios/cost-surface/__mocks__/surface-cost.data.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}, | ||
], | ||
}); |
13 changes: 13 additions & 0 deletions
13
api/src/modules/scenarios/cost-surface/adapters/cost-surface-api-events.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
import { | ||
CostSurfaceEventsPort, | ||
CostSurfaceState, | ||
} from '../cost-surface-events.port'; | ||
|
||
@Injectable() | ||
export class CostSurfaceApiEvents implements CostSurfaceEventsPort { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
async event(scenarioId: string, state: CostSurfaceState): Promise<void> { | ||
// | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
api/src/modules/scenarios/cost-surface/adapters/geoprocessing-cost-from-shapefile.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
15
api/src/modules/scenarios/cost-surface/cost-surface-events.port.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>; | ||
} |
141 changes: 141 additions & 0 deletions
141
api/src/modules/scenarios/cost-surface/cost-surface.facade.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import { CostSurfaceFacade } from './cost-surface.facade'; | ||
import { Test } from '@nestjs/testing'; | ||
import { Request } from 'express'; | ||
import { AdjustCostSurface } from '../../analysis/entry-points/adjust-cost-surface'; | ||
import { AdjustCostServiceFake } from './__mocks__/adjust-cost-service-fake'; | ||
import { ResolvePuWithCost } from './resolve-pu-with-cost'; | ||
import { ShapefileConverterFake } from './__mocks__/shapefile-converter-fake'; | ||
import { CostSurfaceEventsPort } from './cost-surface-events.port'; | ||
import { CostSurfaceEventsFake } from './__mocks__/cost-surface-events-fake'; | ||
import { getValidSurfaceCost } from './__mocks__/surface-cost.data'; | ||
|
||
let sut: CostSurfaceFacade; | ||
|
||
let costService: AdjustCostServiceFake; | ||
let fileConverter: ShapefileConverterFake; | ||
let events: CostSurfaceEventsFake; | ||
|
||
beforeEach(async () => { | ||
const sandbox = await Test.createTestingModule({ | ||
providers: [ | ||
{ | ||
provide: AdjustCostSurface, | ||
useClass: AdjustCostServiceFake, | ||
}, | ||
{ | ||
provide: ResolvePuWithCost, | ||
useClass: ShapefileConverterFake, | ||
}, | ||
{ | ||
provide: CostSurfaceEventsPort, | ||
useClass: CostSurfaceEventsFake, | ||
}, | ||
CostSurfaceFacade, | ||
], | ||
}).compile(); | ||
|
||
sut = sandbox.get(CostSurfaceFacade); | ||
costService = sandbox.get(AdjustCostSurface); | ||
fileConverter = sandbox.get(ResolvePuWithCost); | ||
events = sandbox.get(CostSurfaceEventsPort); | ||
}); | ||
|
||
const scenarioId = 'scenarioId'; | ||
const request: Request = Object.freeze({ | ||
file: ({ fakeFile: 1 } as unknown) as Express.Multer.File, | ||
} as unknown) as Request; | ||
|
||
describe(`when file couldn't be converted`, () => { | ||
beforeEach(async () => { | ||
fileConverter.mock.mockRejectedValue(new Error('Not a shapefile.')); | ||
|
||
// Act | ||
await sut.convert(scenarioId, request); | ||
}); | ||
|
||
it(`should emit relevant events`, () => { | ||
expect(events.events).toMatchInlineSnapshot(` | ||
Array [ | ||
Array [ | ||
"scenarioId", | ||
"submitted", | ||
], | ||
Array [ | ||
"scenarioId", | ||
"shapefile-conversion-failed", | ||
], | ||
] | ||
`); | ||
}); | ||
}); | ||
|
||
describe(`when file can be converted`, () => { | ||
beforeEach(() => { | ||
fileConverter.mock.mockResolvedValue(getValidSurfaceCost()); | ||
}); | ||
|
||
describe(`when cost couldn't be adjusted`, () => { | ||
beforeEach(async () => { | ||
costService.mock.mockRejectedValue(new Error('SQL Error')); | ||
|
||
// Act | ||
await sut.convert(scenarioId, request); | ||
}); | ||
|
||
it(`should emit relevant events`, () => { | ||
expect(events.events).toMatchInlineSnapshot(` | ||
Array [ | ||
Array [ | ||
"scenarioId", | ||
"submitted", | ||
], | ||
Array [ | ||
"scenarioId", | ||
"shapefile-converted", | ||
], | ||
Array [ | ||
"scenarioId", | ||
"cost-update-failed", | ||
], | ||
] | ||
`); | ||
}); | ||
}); | ||
|
||
describe(`when cost can be adjusted`, () => { | ||
beforeEach(async () => { | ||
costService.mock.mockResolvedValue(undefined); | ||
|
||
// Act | ||
await sut.convert(scenarioId, request); | ||
}); | ||
|
||
it(`proxies file to port`, () => { | ||
expect(fileConverter.mock.mock.calls[0][0]).toEqual(request.file); | ||
}); | ||
|
||
it(`proxies port output to update service`, () => { | ||
expect(costService.mock.mock.calls[0][0]).toEqual(scenarioId); | ||
expect(costService.mock.mock.calls[0][1]).toEqual(getValidSurfaceCost()); | ||
}); | ||
|
||
it(`emits valid events chain`, () => { | ||
expect(events.events).toMatchInlineSnapshot(` | ||
Array [ | ||
Array [ | ||
"scenarioId", | ||
"submitted", | ||
], | ||
Array [ | ||
"scenarioId", | ||
"shapefile-converted", | ||
], | ||
Array [ | ||
"scenarioId", | ||
"finished", | ||
], | ||
] | ||
`); | ||
}); | ||
}); | ||
}); |
50 changes: 26 additions & 24 deletions
50
api/src/modules/scenarios/cost-surface/cost-surface.facade.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,42 +1,44 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
import { Request, Response } from 'express'; | ||
import { Request } from 'express'; | ||
import { AdjustCostSurface } from '../../analysis/entry-points/adjust-cost-surface'; | ||
import { ProxyService } from '../../proxy/proxy.service'; | ||
import { ResolvePuWithCost } from './resolve-pu-with-cost'; | ||
import { | ||
CostSurfaceEventsPort, | ||
CostSurfaceState, | ||
} from './cost-surface-events.port'; | ||
import { CostSurfaceInputDto } from '../../analysis/entry-points/adjust-cost-surface-input'; | ||
|
||
@Injectable() | ||
export class CostSurfaceFacade { | ||
constructor( | ||
private readonly adjustCostSurfaceService: AdjustCostSurface, | ||
private readonly proxyService: ProxyService, | ||
private readonly shapefileConverter: ResolvePuWithCost, | ||
private readonly events: CostSurfaceEventsPort, | ||
) {} | ||
|
||
/** | ||
* non blocking - will do job in "background" | ||
* | ||
* @param scenarioId | ||
* @param request | ||
*/ | ||
convert(scenarioId: string, request: Request) { | ||
// TODO #0 Generate & Dispatch Api Event (some wrapping service for /dummy/"terminating" if already running) | ||
async convert(scenarioId: string, request: Request) { | ||
await this.events.event(scenarioId, CostSurfaceState.Submitted); | ||
let costSurface: CostSurfaceInputDto; | ||
|
||
// TODO #1 Call Proxy Service to get Planning Units and their surface cost | ||
// this.proxyService.... - modify this to send back data, not act on Response | ||
try { | ||
costSurface = await this.shapefileConverter.fromShapefile(request.file); | ||
} catch (error) { | ||
await this.events.event( | ||
scenarioId, | ||
CostSurfaceState.ShapefileConversionFailed, | ||
); | ||
return; | ||
} | ||
|
||
// TODO #2 Call Analysis-module with scenario id & output from the above | ||
await this.events.event(scenarioId, CostSurfaceState.ShapefileConverted); | ||
this.adjustCostSurfaceService | ||
.update(scenarioId, { | ||
planningUnits: [ | ||
{ | ||
id: '0', | ||
cost: 100, | ||
}, | ||
], | ||
}) | ||
.then(() => { | ||
// dispatch ApiEvent - Done | ||
}) | ||
.catch(() => { | ||
// dispatch ApiEvent - Failed | ||
.update(scenarioId, costSurface) | ||
.then(() => this.events.event(scenarioId, CostSurfaceState.Finished)) | ||
.catch(async () => { | ||
await this.events.event(scenarioId, CostSurfaceState.CostUpdateFailed); | ||
}); | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
api/src/modules/scenarios/cost-surface/resolve-pu-with-cost.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { CostSurfaceInputDto } from '../../analysis/entry-points/adjust-cost-surface-input'; | ||
|
||
export abstract class ResolvePuWithCost { | ||
abstract fromShapefile( | ||
file: Express.Multer.File, | ||
): Promise<CostSurfaceInputDto>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters