Skip to content

Commit

Permalink
Merge pull request #1075 from Vizzuality/feat/MARXAN-1545-command-han…
Browse files Browse the repository at this point in the history
…dler-for-running-a-legacy-project-import

Feat/marxan 1545 command handler for running a legacy project import
  • Loading branch information
angelhigueraacid authored May 20, 2022
2 parents 241eabb + e04f182 commit 52ddeb6
Show file tree
Hide file tree
Showing 3 changed files with 305 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ResourceId } from '@marxan/cloning/domain';
import { Command } from '@nestjs-architects/typed-cqrs';
import { Either } from 'fp-ts/lib/Either';
import { GenerateLegacyProjectImportPiecesErrors } from '../domain/legacy-project-import/legacy-project-import';
import {
LegacyProjectImportRepositoryFindErrors,
LegacyProjectImportRepositorySaveErrors,
} from '../domain/legacy-project-import/legacy-project-import.repository';

export type RunLegacyProjectImportError =
| GenerateLegacyProjectImportPiecesErrors
| LegacyProjectImportRepositorySaveErrors
| LegacyProjectImportRepositoryFindErrors;

export type RunLegacyProjectImportResponse = Either<
RunLegacyProjectImportError,
true
>;

export class RunLegacyProjectImport extends Command<RunLegacyProjectImportResponse> {
constructor(public readonly projectId: ResourceId) {
super();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import { UsersProjectsApiEntity } from '@marxan-api/modules/access-control/projects-acl/entity/users-projects.api.entity';
import { ArchiveLocation, ResourceId } from '@marxan/cloning/domain';
import { UserId } from '@marxan/domain-ids';
import {
LegacyProjectImportFile,
LegacyProjectImportFileType,
} from '@marxan/legacy-project-import';
import { FixtureType } from '@marxan/utils/tests/fixture-type';
import { CqrsModule, EventBus, IEvent } from '@nestjs/cqrs';
import { Test } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { isLeft, isRight } from 'fp-ts/Either';
import { v4 } from 'uuid';
import { LegacyProjectImportPieceRequested } from '../domain/events/legacy-project-import-piece-requested.event';
import { LegacyProjectImportRequested } from '../domain/events/legacy-project-import-requested.event';
import {
LegacyProjectImport,
legacyProjectImportMissingRequiredFile,
} from '../domain/legacy-project-import/legacy-project-import';
import {
legacyProjectImportNotFound,
LegacyProjectImportRepository,
legacyProjectImportSaveError,
} from '../domain/legacy-project-import/legacy-project-import.repository';
import { LegacyProjectImportMemoryRepository } from '../infra/legacy-project-import-memory.repository';
import {
RunLegacyProjectImport,
RunLegacyProjectImportResponse,
} from './run-legacy-project-import.command';
import { RunLegacyProjectImportHandler } from './run-legacy-project-import.handler';

let fixtures: FixtureType<typeof getFixtures>;

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

it(`runs a legacy project import`, async () => {
await fixtures.GivenAnExistingLegacyProjectImport();
await fixtures.GivenAllFilesAreUploaded();
const result = await fixtures.WhenRunningALegacyProjectImport();
await fixtures.ThenStartingLegacyProjectImportIsUpdated(result);
fixtures.ThenLegacyProjectImportRequestedEventIsEmitted();
fixtures.ThenLegacyProjectImportPieceRequestedAreRequested();
});

it(`fails to run when missing uploaded files`, async () => {
fixtures.GivenAnExistingLegacyProjectImport();
fixtures.GivenNotAllFilesAreUploaded();
const result = await fixtures.WhenRunningALegacyProjectImport();
fixtures.ThenNoEventsAreEmitted();
await fixtures.ThenLegacyProjectImportIsNotUpdated();
fixtures.ThenMissingUploadedErrorIsReturned(result);
});

it(`fails to run when there is not an existing legacy project import`, async () => {
fixtures.GivenNoExistingLegacyProjectImport();
const result = await fixtures.WhenRunningALegacyProjectImport();
fixtures.ThenNoEventsAreEmitted();
fixtures.ThenMissingLegacyProjectImportErrorIsReturned(result);
});

it(`fails to run when updating legacy project import fails`, async () => {
await fixtures.GivenAnExistingLegacyProjectImport();
await fixtures.GivenAllFilesAreUploaded();
fixtures.GivenUpdatingALegacyProjectImportFails();
const result = await fixtures.WhenRunningALegacyProjectImport();
fixtures.ThenNoEventsAreEmitted();
fixtures.ThenUpdateErrorIsReturned(result);
});

const getFixtures = async () => {
const sandbox = await Test.createTestingModule({
imports: [CqrsModule],
providers: [
{
provide: LegacyProjectImportRepository,
useClass: LegacyProjectImportMemoryRepository,
},
{
provide: getRepositoryToken(UsersProjectsApiEntity),
useValue: { save: () => {} },
},
RunLegacyProjectImportHandler,
],
}).compile();

await sandbox.init();

const ownerId = UserId.create();
const projectId = v4();
const scenarioId = v4();
const existingLegacyProjectImport = LegacyProjectImport.newOne(
new ResourceId(projectId),
new ResourceId(scenarioId),
ownerId,
);

const sut = sandbox.get(RunLegacyProjectImportHandler);
const repo: LegacyProjectImportMemoryRepository = sandbox.get(
LegacyProjectImportRepository,
);
const events: IEvent[] = [];
sandbox.get(EventBus).subscribe((event) => events.push(event));
const allRequiredFiles = [
LegacyProjectImportFileType.PlanningGridShapefile,
LegacyProjectImportFileType.InputDat,
LegacyProjectImportFileType.PuDat,
LegacyProjectImportFileType.PuvsprDat,
LegacyProjectImportFileType.SpecDat,
].map(
(type) =>
new LegacyProjectImportFile(
type,
new ArchiveLocation(`${type}.location`),
),
);

return {
GivenNoExistingLegacyProjectImport: () => {},
GivenAnExistingLegacyProjectImport: () =>
repo.save(existingLegacyProjectImport),
GivenNotAllFilesAreUploaded: () => {},
GivenAllFilesAreUploaded: () => {
allRequiredFiles.forEach((importFile) =>
existingLegacyProjectImport.addFile(importFile),
);
return repo.save(existingLegacyProjectImport);
},
GivenUpdatingALegacyProjectImportFails: () => {
repo.saveFailure = true;
},
WhenRunningALegacyProjectImport: () => {
return sut.execute(new RunLegacyProjectImport(new ResourceId(projectId)));
},
ThenNoEventsAreEmitted: () => {
expect(events).toHaveLength(0);
},
ThenLegacyProjectImportRequestedEventIsEmitted: () => {
expect(events[0] as LegacyProjectImportRequested).toEqual(
new LegacyProjectImportRequested(
existingLegacyProjectImport.id,
new ResourceId(projectId),
),
);
},
ThenLegacyProjectImportPieceRequestedAreRequested: () => {
expect(
events.slice(1).every((event) => {
return (
event instanceof LegacyProjectImportPieceRequested &&
event.legacyProjectImportId === existingLegacyProjectImport.id
);
}),
);
},
ThenMissingLegacyProjectImportErrorIsReturned: (
result: RunLegacyProjectImportResponse,
) => {
expect(result).toBeDefined();
if (isRight(result)) throw new Error('the handler should have failed');

expect(result.left).toEqual(legacyProjectImportNotFound);
},
ThenMissingUploadedErrorIsReturned: (
result: RunLegacyProjectImportResponse,
) => {
expect(result).toBeDefined();
if (isRight(result)) throw new Error('the handler should have failed');

expect(result.left).toEqual(legacyProjectImportMissingRequiredFile);
},
ThenUpdateErrorIsReturned: (result: RunLegacyProjectImportResponse) => {
expect(result).toBeDefined();
if (isRight(result)) throw new Error('the handler should have failed');

expect(result.left).toEqual(legacyProjectImportSaveError);
},
ThenLegacyProjectImportIsNotUpdated: async () => {
const savedLegacyProjectImport = await repo.find(
new ResourceId(projectId),
);
if (isLeft(savedLegacyProjectImport))
throw new Error('should exist a starting project import');
expect(savedLegacyProjectImport.right).toEqual(
existingLegacyProjectImport,
);
},
ThenStartingLegacyProjectImportIsUpdated: async (
result: RunLegacyProjectImportResponse,
) => {
expect(result).toBeDefined();
if (isLeft(result))
throw new Error('should have created a starting project import');
expect(result.right).toEqual(true);

const savedLegacyProjectImportOrError = await repo.find(
new ResourceId(projectId),
);
if (isLeft(savedLegacyProjectImportOrError))
throw new Error('should exist a starting project import');

const legacyProjectImport = savedLegacyProjectImportOrError.right;
expect(legacyProjectImport.areRequiredFilesUploaded()).toEqual(true);

const legacyProjectImportSnapshot = legacyProjectImport.toSnapshot();
expect(legacyProjectImportSnapshot.isAcceptingFiles).toEqual(false);
expect(legacyProjectImportSnapshot.ownerId).toEqual(ownerId.value);
expect(legacyProjectImportSnapshot.files).toEqual(
allRequiredFiles.map((file) => file.toSnapshot()),
);
expect(legacyProjectImportSnapshot.pieces).not.toHaveLength(0);
expect(legacyProjectImportSnapshot.projectId).toEqual(projectId);
expect(legacyProjectImportSnapshot.scenarioId).toEqual(scenarioId);
},
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
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 { ResourceId } from '@marxan/cloning/domain';
import {
CommandHandler,
EventPublisher,
IInferredCommandHandler,
} from '@nestjs/cqrs';
import { InjectRepository } from '@nestjs/typeorm';
import { isLeft, right } from 'fp-ts/Either';
import { Repository } from 'typeorm';
import { LegacyProjectImportRepository } from '../domain/legacy-project-import/legacy-project-import.repository';
import {
RunLegacyProjectImport,
RunLegacyProjectImportResponse,
} from './run-legacy-project-import.command';

@CommandHandler(RunLegacyProjectImport)
export class RunLegacyProjectImportHandler
implements IInferredCommandHandler<RunLegacyProjectImport> {
constructor(
private readonly legacyProjectImportRepository: LegacyProjectImportRepository,
@InjectRepository(UsersProjectsApiEntity)
private readonly usersRepo: Repository<UsersProjectsApiEntity>,
private readonly eventPublisher: EventPublisher,
) {}

async execute({
projectId,
}: RunLegacyProjectImport): Promise<RunLegacyProjectImportResponse> {
const legacyProjectImportOrError = await this.legacyProjectImportRepository.find(
projectId,
);

if (isLeft(legacyProjectImportOrError)) return legacyProjectImportOrError;

const legacyProjectImport = this.eventPublisher.mergeObjectContext(
legacyProjectImportOrError.right,
);

const { ownerId: userId } = legacyProjectImport.toSnapshot();

const result = legacyProjectImport.start();

if (isLeft(result)) return result;

const legacyProjectImportSaveError = await this.legacyProjectImportRepository.save(
legacyProjectImport,
);

if (isLeft(legacyProjectImportSaveError))
return legacyProjectImportSaveError;

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

legacyProjectImport.commit();

return right(true);
}
}

0 comments on commit 52ddeb6

Please sign in to comment.