-
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.
Merge pull request #1075 from Vizzuality/feat/MARXAN-1545-command-han…
…dler-for-running-a-legacy-project-import Feat/marxan 1545 command handler for running a legacy project import
- Loading branch information
Showing
3 changed files
with
305 additions
and
0 deletions.
There are no files selected for viewing
24 changes: 24 additions & 0 deletions
24
...ps/api/src/modules/legacy-project-import/application/run-legacy-project-import.command.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,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(); | ||
} | ||
} |
217 changes: 217 additions & 0 deletions
217
...i/src/modules/legacy-project-import/application/run-legacy-project-import.handler.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,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); | ||
}, | ||
}; | ||
}; |
64 changes: 64 additions & 0 deletions
64
...ps/api/src/modules/legacy-project-import/application/run-legacy-project-import.handler.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,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); | ||
} | ||
} |