Skip to content

Commit

Permalink
feat(CostSurface): Adds GET endpoint to retrieve one and all Cost Sur…
Browse files Browse the repository at this point in the history
…faces for a Project
  • Loading branch information
KevSanchez committed Oct 2, 2023
1 parent 4a9316a commit a3d5461
Show file tree
Hide file tree
Showing 6 changed files with 332 additions and 2 deletions.
13 changes: 12 additions & 1 deletion api/apps/api/src/modules/cost-surface/cost-surface.api.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
UpdateDateColumn,
OneToMany,
} from 'typeorm';
import { ApiProperty } from '@nestjs/swagger';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Project } from '@marxan-api/modules/projects/project.api.entity';
import { BaseServiceResource } from '@marxan-api/types/resource.interface';
import { Scenario } from '@marxan-api/modules/scenarios/scenario.api.entity';
Expand Down Expand Up @@ -73,6 +73,9 @@ export class CostSurface {
@ApiProperty()
@OneToMany(() => Scenario, (scenario) => scenario.costSurface, {})
scenarios!: Scenario[];

@ApiPropertyOptional()
scenarioUsageCount?: number;
}

export class JSONAPICostSurface {
Expand All @@ -90,3 +93,11 @@ export class CostSurfaceResult {
@ApiProperty()
data!: JSONAPICostSurface;
}

export class CostSurfaceResultPlural {
@ApiProperty({
isArray: true,
type: JSONAPICostSurface,
})
data!: JSONAPICostSurface[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,14 @@ export class CostSurfaceSerializer {
'isDefault',
'createdAt',
'lastModifiedAt',
'scenarios',
'scenarioUsageCount',
],
keyForAttribute: 'camelCase',
scenarios: {
ref: 'id',
attributes: ['id', 'name'],
},
project: {
ref: 'id',
attributes: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,14 @@ import {
import { ensureShapefileHasRequiredFiles } from '@marxan-api/utils/file-uploads.utils';
import { UpdateCostSurfaceDto } from '@marxan-api/modules/cost-surface/dto/update-cost-surface.dto';
import { CostSurfaceSerializer } from '@marxan-api/modules/cost-surface/dto/cost-surface.serializer';
import { JSONAPICostSurface } from '@marxan-api/modules/cost-surface/cost-surface.api.entity';
import {
costSurfaceResource,
CostSurfaceResult,
CostSurfaceResultPlural,
JSONAPICostSurface,
} from '@marxan-api/modules/cost-surface/cost-surface.api.entity';

//@Todo refactor all endpoints to use cost-surfaces instead of singular cost-surface
@ApiTags(projectResource.className)
@Controller(`${apiGlobalPrefixes.v1}/projects`)
export class ProjectCostSurfaceController {
Expand All @@ -58,6 +64,56 @@ export class ProjectCostSurfaceController {
public readonly costSurfaceSeralizer: CostSurfaceSerializer,
) {}

@ImplementsAcl()
@UseGuards(JwtAuthGuard)
@Get(`:projectId/cost-surface/:costSurfaceId`)
@ApiOkResponse({ type: CostSurfaceResult })
async getCostSurfaces(
@Param('projectId') projectId: string,
@Param('costSurfaceId') costSurfaceId: string,
@Req() req: RequestWithAuthenticatedUser,
): Promise<void> {
const result = await this.costSurfaceService.getCostSurface(
req.user.id,
projectId,
costSurfaceId,
);

if (isLeft(result)) {
throw mapAclDomainToHttpError(result.left, {
projectId,
userId: req.user.id,
resourceType: costSurfaceResource.name.plural,
});
}

return this.costSurfaceSeralizer.serialize(result.right);
}

@ImplementsAcl()
@UseGuards(JwtAuthGuard)
@Get(`:projectId/cost-surface/`)
@ApiOkResponse({ type: CostSurfaceResultPlural })
async getCostSurface(
@Param('projectId') projectId: string,
@Req() req: RequestWithAuthenticatedUser,
): Promise<void> {
const result = await this.costSurfaceService.getCostSurfaces(
req.user.id,
projectId,
);

if (isLeft(result)) {
throw mapAclDomainToHttpError(result.left, {
projectId,
userId: req.user.id,
resourceType: costSurfaceResource.name.plural,
});
}

return this.costSurfaceSeralizer.serialize(result.right);
}

@ImplementsAcl()
@UseGuards(JwtAuthGuard)
@ApiOperation({ description: 'To be implemented' })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,56 @@ import { getProjectCostSurfaceFixtures } from './project-cost-surface.fixtures';

let fixtures: FixtureType<typeof getProjectCostSurfaceFixtures>;

describe('Get Project Cost Surface', () => {
beforeEach(async () => {
fixtures = await getProjectCostSurfaceFixtures();
});

it(`should not return the CostSurface if the user doesn't have permissions to view the project`, async () => {
// ARRANGE
const projectId = await fixtures.GivenProject('someProject', []);
const costSurface = await fixtures.GivenCostSurfaceMetadataForProject(
projectId,
'costSurface',
);
await fixtures.GivenScenario(projectId, costSurface.id);
await fixtures.GivenScenario(projectId, costSurface.id);
// ACT
const response = await fixtures.WhenGettingCostSurfaceForProject(
projectId,
costSurface.id,
);

// ASSERT
await fixtures.ThenProjectNotViewableErrorWasReturned(response);
});

it(`should not return list of CostSurface if the user doesn't have permissions to view the project`, async () => {
// ARRANGE
const projectId = await fixtures.GivenProject('someProject', []);
const costSurface1 = await fixtures.GivenCostSurfaceMetadataForProject(
projectId,
'costSurface 1',
);
const costSurface2 = await fixtures.GivenCostSurfaceMetadataForProject(
projectId,
'costSurface 2',
);
await fixtures.GivenScenario(projectId, costSurface1.id);
await fixtures.GivenScenario(projectId, costSurface1.id);
await fixtures.GivenScenario(projectId, costSurface2.id);
await fixtures.GivenScenario(projectId, costSurface2.id);
await fixtures.GivenScenario(projectId, costSurface2.id);
// ACT
const response = await fixtures.WhenGettingCostSurfacesForProject(
projectId,
);

// ASSERT
await fixtures.ThenProjectNotViewableErrorWasReturned(response);
});
});

describe('Upload Cost Surface Shapefile', () => {
beforeEach(async () => {
fixtures = await getProjectCostSurfaceFixtures();
Expand Down
143 changes: 143 additions & 0 deletions api/apps/api/test/project/project-cost-surface.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,149 @@ describe('Cost Surface', () => {
await fixtures.ThenADefaultCostSurfaceWasCreated(projectId);
});
});
describe('Getting Cost Surfaces for Project', () => {
beforeEach(async () => {
fixtures = await getProjectCostSurfaceFixtures();
});

it(`should return the costSurface for the given id, along with its scenarioUsageCount`, async () => {
// ARRANGE
const projectId1 = await fixtures.GivenProject('someProject 1');
const projectId2 = await fixtures.GivenProject('the REAL project');
const default2 = await fixtures.GivenDefaultCostSurfaceForProject(
projectId1,
);
const costSurface11 = await fixtures.GivenCostSurfaceMetadataForProject(
projectId1,
'costSurface 1 1',
);
const costSurface21 = await fixtures.GivenCostSurfaceMetadataForProject(
projectId2,
'costSurface 2 1',
);
const costSurface22 = await fixtures.GivenCostSurfaceMetadataForProject(
projectId2,
'costSurface 2 2',
);
await fixtures.GivenScenario(projectId1, costSurface11.id);
await fixtures.GivenScenario(projectId1, costSurface11.id);
await fixtures.GivenScenario(projectId2, costSurface21.id);
const scenario22 = await fixtures.GivenScenario(
projectId2,
costSurface22.id,
);
const scenario23 = await fixtures.GivenScenario(
projectId2,
costSurface22.id,
);
const scenario24 = await fixtures.GivenScenario(
projectId2,
costSurface22.id,
);

// ACT
const response = await fixtures.WhenGettingCostSurfaceForProject(
projectId2,
costSurface22.id,
);

// ASSERT
await fixtures.ThenResponseHasCostSurface(response, {
...costSurface22,
scenarioUsageCount: 3,
scenarios: [scenario22, scenario23, scenario24],
});
});

it(`should return all the Project's CostSurfaces along their corresponding scenarioUsageCount`, async () => {
// ARRANGE
const projectId1 = await fixtures.GivenProject('someProject 1');
const projectId2 = await fixtures.GivenProject('the REAL project');
const default1 = await fixtures.GivenDefaultCostSurfaceForProject(
projectId1,
);
const default2 = await fixtures.GivenDefaultCostSurfaceForProject(
projectId2,
);
const costSurface11 = await fixtures.GivenCostSurfaceMetadataForProject(
projectId1,
'costSurface 1 1',
);
const costSurface21 = await fixtures.GivenCostSurfaceMetadataForProject(
projectId2,
'costSurface 2 1',
);
const costSurface22 = await fixtures.GivenCostSurfaceMetadataForProject(
projectId2,
'costSurface 2 2',
);
const scenario11 = await fixtures.GivenScenario(
projectId1,
costSurface11.id,
);
const scenario12 = await fixtures.GivenScenario(
projectId1,
costSurface11.id,
);
const scenario21 = await fixtures.GivenScenario(
projectId2,
costSurface21.id,
);
const scenario22 = await fixtures.GivenScenario(
projectId2,
costSurface22.id,
);
const scenario23 = await fixtures.GivenScenario(
projectId2,
costSurface22.id,
);
const scenario24 = await fixtures.GivenScenario(
projectId2,
costSurface22.id,
);
const scenario25 = await fixtures.GivenScenario(
projectId2,
costSurface22.id,
);

const expectedResponse1 = [
{ ...default1, scenarioUsageCount: 0, scenarios: [] },
{
...costSurface11,
scenarioUsageCount: 2,
scenarios: [scenario11, scenario12],
},
];

const expectedResponse2 = [
{ ...default2, scenarioUsageCount: 0, scenarios: [] },
{ ...costSurface21, scenarioUsageCount: 1, scenarios: [scenario21] },
{
...costSurface22,
scenarioUsageCount: 4,
scenarios: [scenario22, scenario23, scenario24, scenario25],
},
];

// ACT
const response1 = await fixtures.WhenGettingCostSurfacesForProject(
projectId1,
);
const response2 = await fixtures.WhenGettingCostSurfacesForProject(
projectId2,
);

// ASSERT
await fixtures.ThenReponseHasCostSurfaceList(
response1,
expectedResponse1,
);
await fixtures.ThenReponseHasCostSurfaceList(
response2,
expectedResponse2,
);
});
});
describe('Upload Cost Surface Shapefile', () => {
it(`should create CostSurface API entity with the provided name`, async () => {
// ARRANGE
Expand Down
Loading

0 comments on commit a3d5461

Please sign in to comment.