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

forbid changes to legacy project imports [MARXAN-1612] #1136

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
@@ -1,3 +1,5 @@
import { ProjectRoles } from '@marxan-api/modules/access-control/projects-acl/dto/user-role-project.dto';
import { UsersProjectsApiEntity } from '@marxan-api/modules/access-control/projects-acl/entity/users-projects.api.entity';
import { ApiEventsService } from '@marxan-api/modules/api-events';
import { API_EVENT_KINDS } from '@marxan/api-events';
import { ResourceId } from '@marxan/cloning/domain';
Expand All @@ -7,7 +9,9 @@ import {
CommandHandler,
IInferredCommandHandler,
} from '@nestjs/cqrs';
import { InjectRepository } from '@nestjs/typeorm';
import { isLeft } from 'fp-ts/lib/Either';
import { Repository } from 'typeorm';
import { LegacyProjectImportRepository } from '../domain/legacy-project-import/legacy-project-import.repository';
import { MarkLegacyProjectImportAsFailed } from './mark-legacy-project-import-as-failed.command';
import { MarkLegacyProjectImportAsFinished } from './mark-legacy-project-import-as-finished.command';
Expand All @@ -19,6 +23,8 @@ export class MarkLegacyProjectImportAsFinishedHandler
private readonly apiEvents: ApiEventsService,
private readonly legacyProjectImportRepository: LegacyProjectImportRepository,
private readonly commandBus: CommandBus,
@InjectRepository(UsersProjectsApiEntity)
private readonly usersRepo: Repository<UsersProjectsApiEntity>,
private readonly logger: Logger,
) {
this.logger.setContext(MarkLegacyProjectImportAsFinishedHandler.name);
Expand Down Expand Up @@ -61,5 +67,11 @@ export class MarkLegacyProjectImportAsFinishedHandler
ownerId,
},
});

await this.usersRepo.save({
userId: ownerId,
projectId: projectId.value,
roleName: ProjectRoles.project_owner,
});
angelhigueraacid marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { forbiddenError } from '@marxan-api/modules/access-control';
import { ProjectRoles } from '@marxan-api/modules/access-control/projects-acl/dto/user-role-project.dto';
import { UsersProjectsApiEntity } from '@marxan-api/modules/access-control/projects-acl/entity/users-projects.api.entity';
import {
CommandHandler,
Expand Down Expand Up @@ -54,12 +53,6 @@ export class RunLegacyProjectImportHandler
if (isLeft(legacyProjectImportSaveError))
return legacyProjectImportSaveError;

await this.usersRepo.save({
userId: ownerId,
projectId: projectId.value,
roleName: ProjectRoles.project_owner,
});

legacyProjectImport.commit();

return right(true);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Module } from '@nestjs/common';
import { LegacyProjectImportRepositoryModule } from '../../infra/legacy-project-import.repository.module';
import { LegacyProjectImportChecker } from './legacy-project-import-checker.service';
import { MarxanLegacyProjectImportChecker } from './marxan-legacy-project-import-checker.service';

@Module({
imports: [LegacyProjectImportRepositoryModule],
providers: [
{
provide: LegacyProjectImportChecker,
useClass: MarxanLegacyProjectImportChecker,
},
],
exports: [LegacyProjectImportChecker],
})
export class LegacyProjectImportCheckerModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {
LegacyProjectImportChecker,
LegacyProjectImportDoesntExist,
legacyProjectImportDoesntExist,
} from '@marxan-api/modules/legacy-project-import/domain/legacy-project-import-checker/legacy-project-import-checker.service';
import { LegacyProjectImportRepository } from '@marxan-api/modules/legacy-project-import/domain/legacy-project-import/legacy-project-import.repository';
import { ResourceId } from '@marxan/cloning/domain';
import { Injectable } from '@nestjs/common';
import { Either, left, right } from 'fp-ts/lib/Either';

@Injectable()
export class LegacyProjectImportCheckerFake
implements LegacyProjectImportChecker {
private legacyProjectImportWithPendingImports: string[] = [];

constructor(
private readonly legacyProjectImportRepo: LegacyProjectImportRepository,
) {}

async isLegacyProjectImportCompletedFor(
projectId: string,
): Promise<Either<LegacyProjectImportDoesntExist, boolean>> {
const legacyProjectImport = await this.legacyProjectImportRepo.find(
new ResourceId(projectId),
);
if (!legacyProjectImport) return left(legacyProjectImportDoesntExist);

return right(
!this.legacyProjectImportWithPendingImports.includes(projectId),
);
}

addPendingLegacyProjecImport(projectId: string) {
this.legacyProjectImportWithPendingImports.push(projectId);
}

clear() {
this.legacyProjectImportWithPendingImports = [];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { FixtureType } from '@marxan/utils/tests/fixture-type';
import { Test } from '@nestjs/testing';
import { Either } from 'fp-ts/lib/Either';
import { v4 } from 'uuid';
import { LegacyProjectImportMemoryRepository } from '../../infra/legacy-project-import-memory.repository';
import { LegacyProjectImport } from '../legacy-project-import/legacy-project-import';
import { LegacyProjectImportStatuses } from '../legacy-project-import/legacy-project-import-status';
import { LegacyProjectImportRepository } from '../legacy-project-import/legacy-project-import.repository';
import {
LegacyProjectImportChecker,
legacyProjectImportDoesntExist,
LegacyProjectImportDoesntExist,
} from './legacy-project-import-checker.service';
import { MarxanLegacyProjectImportChecker } from './marxan-legacy-project-import-checker.service';

let fixtures: FixtureType<typeof getFixtures>;

beforeEach(async () => {
fixtures = await getFixtures();
});

it(`isLegacyProjectImportCompletedFor() should return legacyProjectImportdoesntExist if the project does not have a legacy project import`, async () => {
const res = fixtures.GivenProjectIsNotALegacyProject();

const result = await fixtures.WhenHasImportedLegacyProjectMethodIsCalled(res);

fixtures.ThenLegacyProjectImportDoesntExistIsReturned(result);
});

it.each(
Object.values(LegacyProjectImportStatuses).filter(
(kind) => kind !== LegacyProjectImportStatuses.Completed,
),
)(
`isLegacyProjectImportCompletedFor() should return false if given project has a legacy project import with status %s`,
async (kind) => {
const id = await fixtures.GivenLegacyProjectImport(kind);

const result = await fixtures.WhenHasImportedLegacyProjectMethodIsCalled(
id,
);

fixtures.ThenFalseIsReturned(result);
},
);

it(`isLegacyProjectImportCompletedFor() should return true if given project has an already completed legacy project import`, async () => {
const id = await fixtures.GivenLegacyProjectImport(
LegacyProjectImportStatuses.Completed,
);

const result = await fixtures.WhenHasImportedLegacyProjectMethodIsCalled(id);

fixtures.ThenTrueIsReturned(result);
});

async function getFixtures() {
const testingModule = await Test.createTestingModule({
providers: [
{
provide: LegacyProjectImportChecker,
useClass: MarxanLegacyProjectImportChecker,
},
{
provide: LegacyProjectImportRepository,
useClass: LegacyProjectImportMemoryRepository,
},
],
}).compile();
const sut = testingModule.get(LegacyProjectImportChecker);
const repo = testingModule.get(LegacyProjectImportRepository);
const projectId = v4();

return {
GivenProjectIsNotALegacyProject: () => {
return projectId;
},
GivenLegacyProjectImport: async (status: LegacyProjectImportStatuses) => {
const legacyProjectImport = LegacyProjectImport.fromSnapshot({
files: [],
pieces: [],
id: v4(),
ownerId: v4(),
projectId,
scenarioId: v4(),
status,
toBeRemoved: false,
});
await repo.save(legacyProjectImport);

return projectId;
},
WhenHasImportedLegacyProjectMethodIsCalled: (projectId: string) => {
return sut.isLegacyProjectImportCompletedFor(projectId);
},
ThenLegacyProjectImportDoesntExistIsReturned: (
result: Either<LegacyProjectImportDoesntExist, boolean>,
) => {
expect(result).toEqual({
_tag: 'Left',
left: legacyProjectImportDoesntExist,
});
},
ThenFalseIsReturned: (
result: Either<LegacyProjectImportDoesntExist, boolean>,
) => {
expect(result).toEqual({
_tag: 'Right',
right: false,
});
},
ThenTrueIsReturned: (
result: Either<LegacyProjectImportDoesntExist, boolean>,
) => {
expect(result).toEqual({
_tag: 'Right',
right: true,
});
},
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Either } from 'fp-ts/Either';

export const legacyProjectImportDoesntExist = Symbol(
`doesn't exist legacy project import`,
);
export type LegacyProjectImportDoesntExist = typeof legacyProjectImportDoesntExist;

export abstract class LegacyProjectImportChecker {
abstract isLegacyProjectImportCompletedFor(
projectId: string,
): Promise<Either<LegacyProjectImportDoesntExist, boolean>>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ResourceId } from '@marxan/cloning/domain';
import { Injectable } from '@nestjs/common';
import { Either, isLeft, left, right } from 'fp-ts/Either';
import { LegacyProjectImportRepository } from '../legacy-project-import/legacy-project-import.repository';
import {
legacyProjectImportDoesntExist,
LegacyProjectImportChecker,
LegacyProjectImportDoesntExist,
} from './legacy-project-import-checker.service';

@Injectable()
export class MarxanLegacyProjectImportChecker
implements LegacyProjectImportChecker {
constructor(
private readonly legacyProjectImportRepo: LegacyProjectImportRepository,
) {}
async isLegacyProjectImportCompletedFor(
projectId: string,
): Promise<Either<LegacyProjectImportDoesntExist, boolean>> {
const legacyProjectImport = await this.legacyProjectImportRepo.find(
new ResourceId(projectId),
);

if (isLeft(legacyProjectImport))
return left(legacyProjectImportDoesntExist);

return right(legacyProjectImport.right.hasImportedLegacyProject());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ export class LegacyProjectImport extends AggregateRoot {
return !this.status.isAcceptingFiles();
}

public hasImportedLegacyProject() {
return this.status.hasCompleted();
}

public areRequiredFilesUploaded(): boolean {
const requiredFilesTypes = [
LegacyProjectImportFileType.PlanningGridShapefile,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import { PlanningAreasModule } from '@marxan-api/modules/planning-areas';
import { MarxanBlockGuard } from '@marxan-api/modules/projects/block-guard/marxan-block-guard.service';
import { ScenarioCheckerModule } from '@marxan-api/modules/scenarios/scenario-checker/scenario-checker.module';
import { Scenario } from '@marxan-api/modules/scenarios/scenario.api.entity';
import { LegacyProjectImportCheckerModule } from '@marxan-api/modules/legacy-project-import/domain/legacy-project-import-checker/legacy-project-import-checker.module';

@Module({
imports: [
ProjectCheckerModule,
ScenarioCheckerModule,
LegacyProjectImportCheckerModule,
PlanningAreasModule,
ApiEventsModule,
TypeOrmModule.forFeature([Project, Scenario]),
Expand Down
Loading