-
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): projects: shapefile for protected areas (#189)
- Loading branch information
Showing
12 changed files
with
302 additions
and
0 deletions.
There are no files selected for viewing
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
94 changes: 94 additions & 0 deletions
94
api/src/modules/projects/protected-areas/protected-areas.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,94 @@ | ||
import { | ||
ProtectedAreasFacade, | ||
ProtectedAreasJobInput, | ||
} from './protected-areas.facade'; | ||
import { Test } from '@nestjs/testing'; | ||
import { Logger } from '@nestjs/common'; | ||
import { Queue } from 'bullmq'; | ||
|
||
import { QueueService } from '../../queue/queue.service'; | ||
import { FakeLogger } from '../../../utils/__mocks__/fake-logger'; | ||
|
||
let sut: ProtectedAreasFacade; | ||
let logger: FakeLogger; | ||
let addJobMock: jest.SpyInstance; | ||
|
||
const projectId = 'project-id'; | ||
const file: Express.Multer.File = { | ||
filename: 'file-name', | ||
} as Express.Multer.File; | ||
|
||
beforeEach(async () => { | ||
addJobMock = jest.fn(); | ||
const sandbox = await Test.createTestingModule({ | ||
providers: [ | ||
ProtectedAreasFacade, | ||
{ | ||
provide: QueueService, | ||
useValue: ({ | ||
queue: ({ | ||
add: addJobMock, | ||
} as unknown) as Queue, | ||
} as unknown) as QueueService<ProtectedAreasJobInput>, | ||
}, | ||
{ | ||
provide: Logger, | ||
useClass: FakeLogger, | ||
}, | ||
], | ||
}).compile(); | ||
|
||
sut = sandbox.get(ProtectedAreasFacade); | ||
logger = sandbox.get(Logger); | ||
}); | ||
|
||
describe(`when job submits successfully`, () => { | ||
let result: unknown; | ||
beforeEach(() => { | ||
// Asset | ||
addJobMock.mockResolvedValue({ job: { id: 1 } }); | ||
// Act | ||
result = sut.convert(projectId, file); | ||
}); | ||
|
||
it(`should return`, () => { | ||
expect(result).toEqual(undefined); | ||
}); | ||
|
||
it(`should put job to queue`, () => { | ||
expect(addJobMock.mock.calls[0]).toMatchInlineSnapshot(` | ||
Array [ | ||
"protected-areas-for-project-id", | ||
Object { | ||
"file": Object { | ||
"filename": "file-name", | ||
}, | ||
"projectId": "project-id", | ||
}, | ||
] | ||
`); | ||
}); | ||
}); | ||
|
||
describe(`when job submission fails`, () => { | ||
let result: unknown; | ||
beforeEach(() => { | ||
// Asset | ||
addJobMock.mockRejectedValue(new Error('Oups')); | ||
// Act | ||
result = sut.convert(projectId, file); | ||
}); | ||
|
||
it(`should return`, () => { | ||
expect(result).toEqual(undefined); | ||
}); | ||
|
||
it(`should log the error`, () => { | ||
expect(logger.error.mock.calls[0]).toMatchInlineSnapshot(` | ||
Array [ | ||
"Failed submitting job to queue for project-id", | ||
[Error: Oups], | ||
] | ||
`); | ||
}); | ||
}); |
34 changes: 34 additions & 0 deletions
34
api/src/modules/projects/protected-areas/protected-areas.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 |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { Injectable, Logger } from '@nestjs/common'; | ||
import { Express } from 'express'; | ||
import { QueueService } from '../../queue/queue.service'; | ||
|
||
export interface ProtectedAreasJobInput { | ||
projectId: string; | ||
file: Express.Multer.File; | ||
} | ||
|
||
@Injectable() | ||
export class ProtectedAreasFacade { | ||
constructor( | ||
private readonly queueService: QueueService<ProtectedAreasJobInput>, | ||
private readonly logger: Logger = new Logger(ProtectedAreasFacade.name), | ||
) {} | ||
|
||
convert(projectId: string, file: Express.Multer.File): void { | ||
this.queueService.queue | ||
.add(`protected-areas-for-${projectId}`, { | ||
projectId, | ||
file, | ||
}) | ||
.then(() => { | ||
// ok | ||
}) | ||
.catch((error) => { | ||
this.logger.error( | ||
`Failed submitting job to queue for ${projectId}`, | ||
error, | ||
); | ||
throw error; // failed submission | ||
}); | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
api/src/modules/projects/protected-areas/protected-areas.module.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 { Logger, Module } from '@nestjs/common'; | ||
import { ProtectedAreasFacade } from './protected-areas.facade'; | ||
import { QueueModule } from '../../queue/queue.module'; | ||
import { queueName } from './queue-name'; | ||
|
||
@Module({ | ||
imports: [ | ||
QueueModule.register({ | ||
name: queueName, | ||
}), | ||
], | ||
providers: [Logger, ProtectedAreasFacade], | ||
exports: [ProtectedAreasFacade], | ||
}) | ||
export class ProtectedAreasModule {} |
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 @@ | ||
export const queueName = 'project-protected-areas'; |
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 @@ | ||
export class FakeLogger { | ||
debug = jest.fn(); | ||
error = jest.fn(); | ||
log = jest.fn(); | ||
verbose = jest.fn(); | ||
warn = jest.fn(); | ||
} |
38 changes: 38 additions & 0 deletions
38
api/test/project-protected-areas/project-protected-areas-upload-shapefile.e2e-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,38 @@ | ||
import { INestApplication } from '@nestjs/common'; | ||
import { bootstrapApplication } from '../utils/api-application'; | ||
import { createWorld, World } from './steps/world'; | ||
|
||
let app: INestApplication; | ||
let world: World; | ||
|
||
beforeAll(async () => { | ||
app = await bootstrapApplication(); | ||
world = await createWorld(app); | ||
}); | ||
|
||
afterAll(async () => { | ||
await world.cleanup(); | ||
await app.close(); | ||
}); | ||
|
||
describe(`when project is not available`, () => { | ||
it.skip(`should fail`, () => { | ||
// TODO once implemented | ||
}); | ||
}); | ||
|
||
describe(`when project is available`, () => { | ||
it(`submits shapefile to the system`, async () => { | ||
expect( | ||
(await world.WhenSubmittingShapefileFor(world.projectId)).status, | ||
).toEqual(201); | ||
const job = world.GetSubmittedJobs()[0]; | ||
expect(job).toMatchObject({ | ||
name: `protected-areas-for-${world.projectId}`, | ||
data: { | ||
projectId: world.projectId, | ||
file: expect.anything(), | ||
}, | ||
}); | ||
}); | ||
}); |
Binary file not shown.
16 changes: 16 additions & 0 deletions
16
api/test/project-protected-areas/steps/submits-projects-pa-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,16 @@ | ||
import { INestApplication } from '@nestjs/common'; | ||
import * as request from 'supertest'; | ||
|
||
export const SubmitsProjectsPaShapefile = ( | ||
app: INestApplication, | ||
jwt: string, | ||
projectId: string, | ||
/** | ||
* path to file | ||
*/ | ||
shapefile: string, | ||
) => | ||
request(app.getHttpServer()) | ||
.post(`/api/v1/projects/${projectId}/protected-areas/shapefile`) | ||
.set('Authorization', `Bearer ${jwt}`) | ||
.attach(`file`, shapefile); |
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,37 @@ | ||
import { INestApplication } from '@nestjs/common'; | ||
import supertest from 'supertest'; | ||
import { Job } from 'bullmq'; | ||
|
||
import { QueueToken } from '../../../src/modules/queue/queue.tokens'; | ||
import { GivenUserIsLoggedIn } from '../../steps/given-user-is-logged-in'; | ||
import { GivenProjectExists } from '../../steps/given-project'; | ||
|
||
import { SubmitsProjectsPaShapefile } from './submits-projects-pa-shapefile'; | ||
|
||
export interface World { | ||
cleanup: () => Promise<void>; | ||
projectId: string; | ||
organizationId: string; | ||
WhenSubmittingShapefileFor: (projectId: string) => supertest.Test; | ||
GetSubmittedJobs: () => Job[]; | ||
} | ||
|
||
export const createWorld = async (app: INestApplication): Promise<World> => { | ||
const jwtToken = await GivenUserIsLoggedIn(app); | ||
const queue = app.get(QueueToken); | ||
const { | ||
projectId, | ||
cleanup: projectCleanup, | ||
organizationId, | ||
} = await GivenProjectExists(app, jwtToken); | ||
const shapeFilePath = __dirname + '/stations-shapefile.zip'; | ||
|
||
return { | ||
projectId, | ||
organizationId, | ||
WhenSubmittingShapefileFor: (projectId: string) => | ||
SubmitsProjectsPaShapefile(app, jwtToken, projectId, shapeFilePath), | ||
GetSubmittedJobs: () => Object.values(queue.jobs), | ||
cleanup: async () => Promise.all([projectCleanup()]).then(() => undefined), | ||
}; | ||
}; |
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,36 @@ | ||
import { INestApplication } from '@nestjs/common'; | ||
import { ProjectsTestUtils } from '../utils/projects.test.utils'; | ||
import { OrganizationsTestUtils } from '../utils/organizations.test.utils'; | ||
import { E2E_CONFIG } from '../e2e.config'; | ||
|
||
export const GivenProjectExists = async ( | ||
app: INestApplication, | ||
jwt: string, | ||
): Promise<{ | ||
projectId: string; | ||
organizationId: string; | ||
cleanup: () => Promise<void>; | ||
}> => { | ||
const organizationId = ( | ||
await OrganizationsTestUtils.createOrganization( | ||
app, | ||
jwt, | ||
E2E_CONFIG.organizations.valid.minimal(), | ||
) | ||
).data.id; | ||
const projectId = ( | ||
await ProjectsTestUtils.createProject(app, jwt, { | ||
...E2E_CONFIG.projects.valid.minimal(), | ||
organizationId, | ||
}) | ||
).data.id; | ||
return { | ||
projectId, | ||
organizationId, | ||
cleanup: async () => { | ||
// TODO DEBT: no cascade remove? | ||
await ProjectsTestUtils.deleteProject(app, jwt, projectId); | ||
await OrganizationsTestUtils.deleteOrganization(app, jwt, organizationId); | ||
}, | ||
}; | ||
}; |