diff --git a/src/modules/proposal/controller/__tests__/proposal-contracting.controller.spec.ts b/src/modules/proposal/controller/__tests__/proposal-contracting.controller.spec.ts index d3dd48f..c0af5ea 100644 --- a/src/modules/proposal/controller/__tests__/proposal-contracting.controller.spec.ts +++ b/src/modules/proposal/controller/__tests__/proposal-contracting.controller.spec.ts @@ -5,7 +5,7 @@ import { Role } from 'src/shared/enums/role.enum'; import { FdpgRequest } from 'src/shared/types/request-user.interface'; import { SetDizApprovalDto } from '../../dto/set-diz-approval.dto'; import { SetUacApprovalDto } from '../../dto/set-uac-approval.dto'; -import { RevertLocationDecisionDto } from '../../dto/revert-location-decision.dto'; +import { RevertLocationVoteDto } from '../../dto/revert-location-vote.dto'; import { SignContractDto } from '../../dto/sign-contract.dto'; import { ProposalContractingController } from '../proposal-contracting.controller'; import { ProposalContractingService } from '../../services/proposal-contracting.service'; @@ -76,16 +76,16 @@ describe('ProposalContractingController', () => { }); }); - describe('revertLocationDecision', () => { - it('should revert the location decision', async () => { + describe('revertLocationVote', () => { + it('should revert the location vote', async () => { const params = { id: 'mongoId', }; - const input = new RevertLocationDecisionDto(); - jest.spyOn(proposalContractingService, 'revertLocationDecision'); + const input = new RevertLocationVoteDto(); + jest.spyOn(proposalContractingService, 'handleLocationVote'); - await proposalContractingController.revertLocationDecision(params, input, request); - expect(proposalContractingService.revertLocationDecision).toHaveBeenCalledWith( + await proposalContractingController.revertLocationVote(params, input, request); + expect(proposalContractingService.handleLocationVote).toHaveBeenCalledWith( params.id, input.location, request.user, diff --git a/src/modules/proposal/controller/proposal-contracting.controller.ts b/src/modules/proposal/controller/proposal-contracting.controller.ts index d8c1a08..32cedf7 100644 --- a/src/modules/proposal/controller/proposal-contracting.controller.ts +++ b/src/modules/proposal/controller/proposal-contracting.controller.ts @@ -1,4 +1,3 @@ -import { ProposalContractingService } from './../services/proposal-contracting.service'; import { Body, HttpCode, @@ -25,9 +24,10 @@ import { ContractingUploadDto } from '../dto/contracting-upload.dto'; import { ProposalMarkConditionAcceptedReturnDto } from '../dto/proposal/proposal.dto'; import { SetDizApprovalDto } from '../dto/set-diz-approval.dto'; import { SetUacApprovalDto, SetUacApprovalWithFileDto } from '../dto/set-uac-approval.dto'; -import { RevertLocationDecisionDto } from '../dto/revert-location-decision.dto'; +import { RevertLocationVoteDto } from '../dto/revert-location-vote.dto'; import { SignContractDto, SignContractWithFileDto } from '../dto/sign-contract.dto'; import { InitContractingDto } from '../dto/proposal/init-contracting.dto'; +import { ProposalContractingService } from '../services/proposal-contracting.service'; @ApiController('proposals', undefined, 'contracting') export class ProposalContractingController { @@ -68,19 +68,19 @@ export class ProposalContractingController { } @Auth(Role.FdpgMember) - @Put(':id/revertLocationDecision') + @Put(':id/revertLocationVote') @UsePipes(ValidationPipe) @ApiNotFoundResponse({ description: 'Proposal could not be found' }) - @ApiOperation({ summary: 'Reverts the Location Decision' }) - @ApiNoContentResponse({ description: 'Location Decision reverted. No content returns.' }) + @ApiOperation({ summary: 'FDPG member reverts locations vote ' }) + @ApiNoContentResponse({ description: 'Locations vote reverted. No content returns.' }) @HttpCode(204) - @ApiBody({ type: RevertLocationDecisionDto }) - async revertLocationDecision( + @ApiBody({ type: RevertLocationVoteDto }) + async revertLocationVote( @Param() { id }: MongoIdParamDto, - @Body() { location }: RevertLocationDecisionDto, + @Body() { location }: RevertLocationVoteDto, @Request() { user }: FdpgRequest, ): Promise { - return await this.proposalContractingService.revertLocationDecision(id, location, user); + return await this.proposalContractingService.handleLocationVote(id, location, user); } @Auth(Role.FdpgMember) diff --git a/src/modules/proposal/dto/revert-location-decision.dto.ts b/src/modules/proposal/dto/revert-location-vote.dto.ts similarity index 85% rename from src/modules/proposal/dto/revert-location-decision.dto.ts rename to src/modules/proposal/dto/revert-location-vote.dto.ts index e113541..3c90227 100644 --- a/src/modules/proposal/dto/revert-location-decision.dto.ts +++ b/src/modules/proposal/dto/revert-location-vote.dto.ts @@ -3,7 +3,7 @@ import { IsEnum } from 'class-validator'; import { MiiLocation } from 'src/shared/constants/mii-locations'; @Exclude() -export class RevertLocationDecisionDto { +export class RevertLocationVoteDto { @Expose() @IsEnum(MiiLocation) location: MiiLocation; diff --git a/src/modules/proposal/enums/history-event.enum.ts b/src/modules/proposal/enums/history-event.enum.ts index f1d2763..9631bc6 100644 --- a/src/modules/proposal/enums/history-event.enum.ts +++ b/src/modules/proposal/enums/history-event.enum.ts @@ -29,8 +29,8 @@ export enum HistoryEventType { /** FDPG Veto */ FdpgApprovedLocationRemoved = 'FDPG_APPROVED_LOCATION_REMOVED', - /** FDPG reverts DIZ and UAC decision */ - FdpgRevertedLocationDecision = 'FDPG_REVERTED_LOCATION_DECISION', + /** FDPG reverts DIZ and UAC vote */ + FdpgRevertedLocationVote = 'FDPG_REVERTED_LOCATION_VOTE', /** Data Delivery */ diff --git a/src/modules/proposal/services/__tests__/proposal-contracting.service.spec.ts b/src/modules/proposal/services/__tests__/proposal-contracting.service.spec.ts index 0bba9bf..2d477a7 100644 --- a/src/modules/proposal/services/__tests__/proposal-contracting.service.spec.ts +++ b/src/modules/proposal/services/__tests__/proposal-contracting.service.spec.ts @@ -1,39 +1,35 @@ -import { handleLocationDecision } from './../../utils/handle-location-vote.util'; +import { ForbiddenException, NotFoundException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { StorageService } from 'src/modules/storage/storage.service'; import { EventEngineService } from 'src/modules/event-engine/event-engine.service'; +import { StorageService } from 'src/modules/storage/storage.service'; import { MiiLocation } from 'src/shared/constants/mii-locations'; import { Role } from 'src/shared/enums/role.enum'; import { FdpgRequest } from 'src/shared/types/request-user.interface'; +import { NoErrorThrownError, getError } from 'test/get-error'; +import { InitContractingDto } from '../../dto/proposal/init-contracting.dto'; import { SetDizApprovalDto } from '../../dto/set-diz-approval.dto'; import { SetUacApprovalDto } from '../../dto/set-uac-approval.dto'; import { SignContractDto } from '../../dto/sign-contract.dto'; import { ProposalStatus } from '../../enums/proposal-status.enum'; import { ProposalDocument } from '../../schema/proposal.schema'; import { addContractSign } from '../../utils/add-contract-sign.util'; -import { addDizApproval, addUacApproval, addUacApprovalWithCondition } from '../../utils/handle-location-vote.util'; +import { addDizApproval, addUacApproval, addUacApprovalWithCondition } from '../../utils/add-location-vote.util'; import { addHistoryItemForContractSign, addHistoryItemForDizApproval, + addHistoryItemForRevertLocationVote, addHistoryItemForStatus, addHistoryItemForUacApproval, - addHistoryItemForRevertLocationDecision, } from '../../utils/proposal-history.util'; import { addUpload } from '../../utils/proposal.utils'; +import { revertLocationVote } from '../../utils/revert-location-vote.util'; import { validateContractSign } from '../../utils/validate-contract-sign.util'; import { validateStatusChange } from '../../utils/validate-status-change.util'; -import { - validateDizApproval, - validateRevertLocationDecision, - validateUacApproval, -} from '../../utils/validate-vote.util'; +import { validateDizApproval, validateRevertLocationVote, validateUacApproval } from '../../utils/validate-vote.util'; import { ProposalContractingService } from '../proposal-contracting.service'; import { ProposalCrudService } from '../proposal-crud.service'; -import { StatusChangeService } from '../status-change.service'; -import { ForbiddenException, NotFoundException } from '@nestjs/common'; -import { NoErrorThrownError, getError } from 'test/get-error'; -import { InitContractingDto } from '../../dto/proposal/init-contracting.dto'; import { ProposalUploadService } from '../proposal-upload.service'; +import { StatusChangeService } from '../status-change.service'; jest.mock('class-transformer', () => { const original = jest.requireActual('class-transformer'); @@ -46,15 +42,17 @@ jest.mock('class-transformer', () => { jest.mock('../../utils/validate-vote.util', () => ({ validateDizApproval: jest.fn(), validateUacApproval: jest.fn(), - validateRevertLocationDecision: jest.fn(), + validateRevertLocationVote: jest.fn(), })); -jest.mock('../../utils/handle-location-vote.util', () => ({ +jest.mock('../../utils/add-location-vote.util', () => ({ addDizApproval: jest.fn(), addUacApproval: jest.fn(), addUacApprovalWithCondition: jest.fn(), addUacConditionReview: jest.fn(), - handleLocationDecision: jest.fn(), +})); +jest.mock('../../utils/revert-location-vote.util', () => ({ + revertLocationVote: jest.fn(), })); jest.mock('../../utils/proposal-history.util', () => ({ @@ -63,7 +61,7 @@ jest.mock('../../utils/proposal-history.util', () => ({ addHistoryItemForStatus: jest.fn(), addHistoryItemForUacApproval: jest.fn(), addHistoryItemForUacCondition: jest.fn(), - addHistoryItemForRevertLocationDecision: jest.fn(), + addHistoryItemForRevertLocationVote: jest.fn(), })); jest.mock('../../utils/proposal.utils', () => ({ @@ -148,7 +146,7 @@ describe('ProposalContractingService', () => { handleProposalUacApproval: jest.fn(), handleProposalStatusChange: jest.fn(), handleProposalContractSign: jest.fn(), - handleLocationDecision: jest.fn(), + handleLocationVote: jest.fn(), }, }, { @@ -247,21 +245,21 @@ describe('ProposalContractingService', () => { }); }); - describe('revertLocationDecision', () => { - it('should revert the location decision', async () => { + describe('revertLocationVote', () => { + it('should revert the location vote', async () => { const proposalDocument = getProposalDocument(); jest.spyOn(proposalCrudService, 'findDocument').mockResolvedValueOnce(proposalDocument); - await proposalContractingService.revertLocationDecision(proposalId, request.user.miiLocation, request.user); + await proposalContractingService.handleLocationVote(proposalId, request.user.miiLocation, request.user); - expect(validateRevertLocationDecision).toHaveBeenCalledWith(proposalDocument); - expect(handleLocationDecision).toHaveBeenCalledWith( + expect(validateRevertLocationVote).toHaveBeenCalledWith(proposalDocument); + expect(revertLocationVote).toHaveBeenCalledWith( proposalDocument, request.user.miiLocation, request.user, proposalUploadService, ); - expect(addHistoryItemForRevertLocationDecision).toHaveBeenCalledWith( + expect(addHistoryItemForRevertLocationVote).toHaveBeenCalledWith( proposalDocument, request.user, request.user.miiLocation, diff --git a/src/modules/proposal/services/proposal-contracting.service.ts b/src/modules/proposal/services/proposal-contracting.service.ts index d6b69b3..ec33833 100644 --- a/src/modules/proposal/services/proposal-contracting.service.ts +++ b/src/modules/proposal/services/proposal-contracting.service.ts @@ -1,46 +1,46 @@ -import { ProposalUploadService } from './proposal-upload.service'; import { ForbiddenException, Injectable, NotFoundException } from '@nestjs/common'; import { plainToClass } from 'class-transformer'; +import { ValidationException } from 'src/exceptions/validation/validation.exception'; +import { MiiLocation } from 'src/shared/constants/mii-locations'; +import { ValidationErrorInfo } from 'src/shared/dto/validation/validation-error-info.dto'; +import { BadRequestError } from 'src/shared/enums/bad-request-error.enum'; import { Role } from 'src/shared/enums/role.enum'; import { IRequestUser } from 'src/shared/types/request-user.interface'; import { convertUserToGroups } from 'src/shared/utils/user-group.utils'; -import { StorageService } from '../../storage/storage.service'; import { EventEngineService } from '../../event-engine/event-engine.service'; +import { StorageService } from '../../storage/storage.service'; +import { InitContractingDto } from '../dto/proposal/init-contracting.dto'; import { ProposalMarkConditionAcceptedReturnDto } from '../dto/proposal/proposal.dto'; +import { SetDizApprovalDto } from '../dto/set-diz-approval.dto'; import { SetUacApprovalDto } from '../dto/set-uac-approval.dto'; import { SignContractDto } from '../dto/sign-contract.dto'; import { UploadDto } from '../dto/upload.dto'; import { ProposalValidation } from '../enums/porposal-validation.enum'; import { ProposalStatus } from '../enums/proposal-status.enum'; import { UseCaseUpload } from '../enums/upload-type.enum'; -import { ProposalCrudService } from './proposal-crud.service'; -import { StatusChangeService } from './status-change.service'; import { addContractSign } from '../utils/add-contract-sign.util'; import { addDizApproval, addUacApproval, addUacApprovalWithCondition, addUacConditionReview, - handleLocationDecision, -} from '../utils/handle-location-vote.util'; +} from '../utils/add-location-vote.util'; import { addHistoryItemForContractSign, addHistoryItemForDizApproval, + addHistoryItemForRevertLocationVote, addHistoryItemForStatus, addHistoryItemForUacApproval, addHistoryItemForUacCondition, - addHistoryItemForRevertLocationDecision, } from '../utils/proposal-history.util'; import { addUpload, getBlobName } from '../utils/proposal.utils'; +import { revertLocationVote } from '../utils/revert-location-vote.util'; import { validateContractSign } from '../utils/validate-contract-sign.util'; import { validateStatusChange } from '../utils/validate-status-change.util'; -import { validateDizApproval, validateUacApproval, validateRevertLocationDecision } from '../utils/validate-vote.util'; -import { SetDizApprovalDto } from '../dto/set-diz-approval.dto'; -import { InitContractingDto } from '../dto/proposal/init-contracting.dto'; -import { ValidationErrorInfo } from 'src/shared/dto/validation/validation-error-info.dto'; -import { ValidationException } from 'src/exceptions/validation/validation.exception'; -import { BadRequestError } from 'src/shared/enums/bad-request-error.enum'; -import { MiiLocation } from 'src/shared/constants/mii-locations'; +import { validateDizApproval, validateRevertLocationVote, validateUacApproval } from '../utils/validate-vote.util'; +import { ProposalCrudService } from './proposal-crud.service'; +import { ProposalUploadService } from './proposal-upload.service'; +import { StatusChangeService } from './status-change.service'; @Injectable() export class ProposalContractingService { @@ -90,12 +90,12 @@ export class ProposalContractingService { await this.eventEngineService.handleProposalUacApproval(saveResult, vote.value, user.miiLocation); } - async revertLocationDecision(proposalId: string, location: MiiLocation, user: IRequestUser): Promise { + async handleLocationVote(proposalId: string, location: MiiLocation, user: IRequestUser): Promise { const toBeUpdated = await this.proposalCrudService.findDocument(proposalId, user, undefined, true); - validateRevertLocationDecision(toBeUpdated); + validateRevertLocationVote(toBeUpdated); - await handleLocationDecision(toBeUpdated, location, user, this.proposalUploadService); - addHistoryItemForRevertLocationDecision(toBeUpdated, user, location); + await revertLocationVote(toBeUpdated, location, user, this.proposalUploadService); + addHistoryItemForRevertLocationVote(toBeUpdated, user, location); await toBeUpdated.save(); } diff --git a/src/modules/proposal/utils/__tests__/handle-location-vote.util.spec.ts b/src/modules/proposal/utils/__tests__/add-location-vote.util.spec.ts similarity index 53% rename from src/modules/proposal/utils/__tests__/handle-location-vote.util.spec.ts rename to src/modules/proposal/utils/__tests__/add-location-vote.util.spec.ts index 6551e8a..7c61a57 100644 --- a/src/modules/proposal/utils/__tests__/handle-location-vote.util.spec.ts +++ b/src/modules/proposal/utils/__tests__/add-location-vote.util.spec.ts @@ -1,4 +1,3 @@ -import { getLocationState } from './../validate-access.util'; import { MiiLocation } from 'src/shared/constants/mii-locations'; import { Role } from 'src/shared/enums/role.enum'; import { FdpgRequest } from 'src/shared/types/request-user.interface'; @@ -9,18 +8,14 @@ import { FdpgTaskType } from '../../enums/fdpg-task-type.enum'; import { ProposalStatus } from '../../enums/proposal-status.enum'; import { ProposalDocument } from '../../schema/proposal.schema'; import { ConditionalApproval } from '../../schema/sub-schema/conditional-approval.schema'; -import { DeclineReason } from '../../schema/sub-schema/decline-reason.schema'; -import { UacApproval } from '../../schema/sub-schema/uac-approval.schema'; import { addFdpgTaskAndReturnId, removeFdpgTask } from '../add-fdpg-task.util'; import { addDizApproval, addUacApproval, addUacApprovalWithCondition, addUacConditionReview, - handleLocationDecision, -} from '../handle-location-vote.util'; +} from '../add-location-vote.util'; import { clearLocationsVotes } from '../location-flow.util'; -import { ProposalUploadService } from '../../services/proposal-upload.service'; jest.mock('../location-flow.util', () => ({ clearLocationsVotes: jest.fn(), @@ -32,14 +27,6 @@ jest.mock('../add-fdpg-task.util', () => ({ removeFdpgTask: jest.fn(), })); -jest.mock('../validate-access.util', () => ({ - getLocationState: jest.fn(), -})); - -const proposalUploadServiceMock = { - deleteUpload: jest.fn(), -} as any as ProposalUploadService; - describe('addLocationVoteUtil', () => { beforeEach(() => { jest.clearAllMocks(); @@ -243,171 +230,4 @@ describe('addLocationVoteUtil', () => { expect(proposal.requestedButExcludedLocations).toEqual([condition.location]); }); }); - - const locationStateDefault: ReturnType = { - isDizCheck: false, - dizApproved: false, - uacApproved: false, - isConditionalApproval: false, - conditionalApprovalAccepted: false, - conditionalApprovalDeclined: false, - contractAcceptedByResearcher: false, - contractRejectedByResearcher: false, - signedContract: false, - signedContractAndContractingDone: false, - requestedButExcluded: false, - }; - - describe('handleLocationDecision', () => { - const getLocationStateMock = jest.mocked(getLocationState); - - it('should delete the declineReason object', async () => { - const declineReason = new DeclineReason(); - declineReason.location = MiiLocation.UKRUB; - const proposal = getProposalDocument(); - proposal.declineReasons = [declineReason]; - proposal.openDizChecks = []; - const request = getRequest(); - - getLocationStateMock.mockReturnValueOnce({ - ...locationStateDefault, - requestedButExcluded: true, - }); - - expect(proposal.declineReasons).toEqual([declineReason]); - await handleLocationDecision(proposal, declineReason.location, request.user, proposalUploadServiceMock); - - expect(getLocationStateMock).toBeCalledWith(proposal, request.user); - expect(proposal.declineReasons).not.toEqual([declineReason]); - expect(proposal.declineReasons.length).toEqual(0); - expect(clearLocationsVotes).toBeCalledWith(proposal, declineReason.location); - expect(proposal.requestedButExcludedLocations).not.toEqual([request.user.miiLocation]); - expect(proposal.openDizChecks).toEqual([declineReason.location]); - }); - - it('should delete the uac approval object', async () => { - const uacApproval = new UacApproval(); - uacApproval.location = MiiLocation.UKRUB; - const proposal = getProposalDocument(); - proposal.uacApprovals = [uacApproval]; - proposal.openDizChecks = []; - - const request = getRequest(); - - getLocationStateMock.mockReturnValueOnce({ - ...locationStateDefault, - uacApproved: true, - }); - - expect(proposal.uacApprovals).toEqual([uacApproval]); - await handleLocationDecision(proposal, uacApproval.location, request.user, proposalUploadServiceMock); - expect(getLocationStateMock).toBeCalledWith(proposal, request.user); - expect(proposal.uacApprovals.length).toEqual(0); - expect(proposal.uacApprovals).not.toEqual([uacApproval]); - expect(clearLocationsVotes).toBeCalledWith(proposal, uacApproval.location); - expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); - expect(proposal.openDizChecks).toEqual([uacApproval.location]); - }); - - describe('should remove Fdpg Task in case of expected data amount reached', () => { - const testCases = [ - { - name: 'expected data amount is reached even withouth the location data amount', - dataAmount: 10, - expectedDataAmountReached: true, - }, - { - name: 'expected data amount is not reached withouth the location data amount', - dataAmount: 12, - expectedDataAmountReached: false, - }, - ]; - - test.each(testCases)('%s', async ({ dataAmount, expectedDataAmountReached }) => { - const approval = new UacApproval(); - approval.location = MiiLocation.UKRUB; - approval.dataAmount = dataAmount; - const condition = new ConditionalApproval(); - condition.location = MiiLocation.UKRUB; - condition.fdpgTaskId = FdpgTaskType.ConditionApproval; - condition.dataAmount = 10; - const proposal = getProposalDocument(); - proposal.conditionalApprovals = [condition]; - proposal.uacApprovals = [approval]; - proposal.totalPromisedDataAmount = 21; - proposal.requestedData.desiredDataAmount = 11; - proposal.openDizChecks = []; - - const request = getRequest(); - - getLocationStateMock.mockReturnValueOnce({ - ...locationStateDefault, - conditionalApprovalAccepted: true, - }); - - expect(proposal.uacApprovals).toEqual([approval]); - await handleLocationDecision(proposal, approval.location, request.user, proposalUploadServiceMock); - expect(getLocationStateMock).toBeCalledWith(proposal, request.user); - if (expectedDataAmountReached) { - expect(removeFdpgTask).not.toBeCalledWith(proposal, FdpgTaskType.DataAmountReached); - } else { - expect(removeFdpgTask).toBeCalledWith(proposal, FdpgTaskType.DataAmountReached); - } - expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); - expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); - expect(proposal.openDizChecks).toEqual([condition.location]); - }); - }); - - it('should delete conditional approval object and fdpg task', async () => { - const condition = new ConditionalApproval(); - condition.location = MiiLocation.UKRUB; - condition.fdpgTaskId = FdpgTaskType.ConditionApproval; - condition.dataAmount = 10; - const proposal = getProposalDocument(); - proposal.conditionalApprovals = [condition]; - proposal.totalPromisedDataAmount = condition.dataAmount + 1; - proposal.requestedData.desiredDataAmount = 11; - proposal.openDizChecks = []; - - const request = getRequest(); - - getLocationStateMock.mockReturnValueOnce({ - ...locationStateDefault, - conditionalApprovalAccepted: true, - }); - - expect(proposal.conditionalApprovals).toEqual([condition]); - await handleLocationDecision(proposal, condition.location, request.user, proposalUploadServiceMock); - expect(getLocationStateMock).toBeCalledWith(proposal, request.user); - expect(removeFdpgTask).toBeCalledWith(proposal, condition.fdpgTaskId); - expect(proposal.conditionalApprovals).not.toEqual([condition]); - expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); - expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); - expect(proposal.openDizChecks).toEqual([condition.location]); - }); - - it('should delete the upload blob', async () => { - const condition = new ConditionalApproval(); - condition.location = MiiLocation.UKRUB; - condition.uploadId = 'uploadId'; - const proposal = getProposalDocument(); - proposal.conditionalApprovals = [condition]; - proposal.openDizChecks = []; - const request = getRequest(); - - getLocationStateMock.mockReturnValueOnce({ - ...locationStateDefault, - isConditionalApproval: true, - }); - - await handleLocationDecision(proposal, condition.location, request.user, proposalUploadServiceMock); - expect(getLocationStateMock).toBeCalledWith(proposal, request.user); - expect(proposalUploadServiceMock.deleteUpload).toBeCalledTimes(1); - expect(proposalUploadServiceMock.deleteUpload).toBeCalledWith(proposal._id, condition.uploadId, request.user); - expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); - expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); - expect(proposal.openDizChecks).toEqual([condition.location]); - }); - }); }); diff --git a/src/modules/proposal/utils/__tests__/proposal-history.util.spec.ts b/src/modules/proposal/utils/__tests__/proposal-history.util.spec.ts index 64a8c98..3525393 100644 --- a/src/modules/proposal/utils/__tests__/proposal-history.util.spec.ts +++ b/src/modules/proposal/utils/__tests__/proposal-history.util.spec.ts @@ -13,7 +13,7 @@ import { addHistoryItemForContractSystemReject, addHistoryItemForProposalLock, addHistoryItemForUnselectedLocation, - addHistoryItemForRevertLocationDecision, + addHistoryItemForRevertLocationVote, } from '../proposal-history.util'; const proposalId = 'proposalId'; @@ -192,16 +192,16 @@ describe('ProposalHistoryUtil', () => { }); }); - describe('addHistoryItemForRevertLocationDecision', () => { - it('should add history item for reverted location decision', () => { + describe('addHistoryItemForRevertLocationVote', () => { + it('should add history item for reverted location vote', () => { const request = getRequest(); const proposal = getProposalDocument(); const location = MiiLocation.UKRUB; - addHistoryItemForRevertLocationDecision(proposal, request.user, location); + addHistoryItemForRevertLocationVote(proposal, request.user, location); expect(proposal.history.length).toBe(1); - const expectedType = HistoryEventType.FdpgRevertedLocationDecision; + const expectedType = HistoryEventType.FdpgRevertedLocationVote; expect(proposal.history[0].type).toBe(expectedType); }); }); diff --git a/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts b/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts new file mode 100644 index 0000000..e5ddac7 --- /dev/null +++ b/src/modules/proposal/utils/__tests__/revert-location-vote.util.spec.ts @@ -0,0 +1,240 @@ +import { getLocationState } from '../validate-access.util'; +import { DeclineReason } from '../../schema/sub-schema/decline-reason.schema'; +import { UacApproval } from '../../schema/sub-schema/uac-approval.schema'; +import { revertLocationVote } from '../revert-location-vote.util'; +import { ProposalUploadService } from '../../services/proposal-upload.service'; +import { MiiLocation } from 'src/shared/constants/mii-locations'; +import { Role } from 'src/shared/enums/role.enum'; +import { FdpgRequest } from 'src/shared/types/request-user.interface'; +import { ProposalStatus } from '../../enums/proposal-status.enum'; +import { ProposalDocument } from '../../schema/proposal.schema'; +import { clearLocationsVotes } from '../location-flow.util'; +import { ConditionalApproval } from '../../schema/sub-schema/conditional-approval.schema'; +import { FdpgTaskType } from '../../enums/fdpg-task-type.enum'; +import { removeFdpgTask } from '../add-fdpg-task.util'; + +jest.mock('../location-flow.util', () => ({ + clearLocationsVotes: jest.fn(), +})); + +jest.mock('../add-fdpg-task.util', () => ({ + removeFdpgTask: jest.fn(), +})); +jest.mock('../validate-access.util', () => ({ + getLocationState: jest.fn(), +})); + +const proposalUploadServiceMock = { + deleteUpload: jest.fn(), +} as any as ProposalUploadService; + +describe('revertLocationVoteUtil', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + const requestContent = { + user: { + userId: 'userId', + firstName: 'firstName', + lastName: 'lastName', + fullName: 'fullName', + email: 'info@appsfactory.de', + username: 'username', + email_verified: true, + roles: [Role.Researcher], + singleKnownRole: Role.Researcher, + miiLocation: MiiLocation.UKL, + isFromLocation: false, + isKnownLocation: true, + }, + } as FdpgRequest; + + const getRequest = () => JSON.parse(JSON.stringify(requestContent)) as FdpgRequest; + + const proposalId = 'proposalId'; + + const proposalContent = { + _id: proposalId, + projectAbbreviation: 'projectAbbreviation', + status: ProposalStatus.FdpgCheck, + requestedData: {}, + dizApprovedLocations: [], + requestedButExcludedLocations: [], + uacApprovedLocations: [], + uacApprovals: [], + declineReasons: [], + }; + const getProposalDocument = () => { + const proposalDocument = { + ...JSON.parse(JSON.stringify(proposalContent)), + }; + return proposalDocument as any as ProposalDocument; + }; + + const locationStateDefault: ReturnType = { + isDizCheck: false, + dizApproved: false, + uacApproved: false, + isConditionalApproval: false, + conditionalApprovalAccepted: false, + conditionalApprovalDeclined: false, + contractAcceptedByResearcher: false, + contractRejectedByResearcher: false, + signedContract: false, + signedContractAndContractingDone: false, + requestedButExcluded: false, + }; + + describe('handleLocationVote', () => { + const getLocationStateMock = jest.mocked(getLocationState); + + it('should delete the declineReason object', async () => { + const declineReason = new DeclineReason(); + declineReason.location = MiiLocation.UKRUB; + const proposal = getProposalDocument(); + proposal.declineReasons = [declineReason]; + proposal.openDizChecks = []; + const request = getRequest(); + + getLocationStateMock.mockReturnValueOnce({ + ...locationStateDefault, + requestedButExcluded: true, + }); + + expect(proposal.declineReasons).toEqual([declineReason]); + await revertLocationVote(proposal, declineReason.location, request.user, proposalUploadServiceMock); + + expect(getLocationStateMock).toBeCalledWith(proposal, request.user); + expect(proposal.declineReasons).not.toEqual([declineReason]); + expect(proposal.declineReasons.length).toEqual(0); + expect(clearLocationsVotes).toBeCalledWith(proposal, declineReason.location); + expect(proposal.requestedButExcludedLocations).not.toEqual([request.user.miiLocation]); + expect(proposal.openDizChecks).toEqual([declineReason.location]); + }); + + it('should delete the uac approval object', async () => { + const uacApproval = new UacApproval(); + uacApproval.location = MiiLocation.UKRUB; + const proposal = getProposalDocument(); + proposal.uacApprovals = [uacApproval]; + proposal.openDizChecks = []; + + const request = getRequest(); + + getLocationStateMock.mockReturnValueOnce({ + ...locationStateDefault, + uacApproved: true, + }); + + expect(proposal.uacApprovals).toEqual([uacApproval]); + await revertLocationVote(proposal, uacApproval.location, request.user, proposalUploadServiceMock); + expect(getLocationStateMock).toBeCalledWith(proposal, request.user); + expect(proposal.uacApprovals.length).toEqual(0); + expect(proposal.uacApprovals).not.toEqual([uacApproval]); + expect(clearLocationsVotes).toBeCalledWith(proposal, uacApproval.location); + expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); + expect(proposal.openDizChecks).toEqual([uacApproval.location]); + }); + + describe('should remove Fdpg Task in case of expected data amount reached', () => { + const testCases = [ + { + name: 'expected data amount is reached even withouth the location data amount', + dataAmount: 10, + expectedDataAmountReached: true, + }, + { + name: 'expected data amount is not reached withouth the location data amount', + dataAmount: 12, + expectedDataAmountReached: false, + }, + ]; + + test.each(testCases)('%s', async ({ dataAmount, expectedDataAmountReached }) => { + const approval = new UacApproval(); + approval.location = MiiLocation.UKRUB; + approval.dataAmount = dataAmount; + const condition = new ConditionalApproval(); + condition.location = MiiLocation.UKRUB; + condition.fdpgTaskId = FdpgTaskType.ConditionApproval; + condition.dataAmount = 10; + const proposal = getProposalDocument(); + proposal.conditionalApprovals = [condition]; + proposal.uacApprovals = [approval]; + proposal.totalPromisedDataAmount = 21; + proposal.requestedData.desiredDataAmount = 11; + proposal.openDizChecks = []; + + const request = getRequest(); + + getLocationStateMock.mockReturnValueOnce({ + ...locationStateDefault, + conditionalApprovalAccepted: true, + }); + + expect(proposal.uacApprovals).toEqual([approval]); + await revertLocationVote(proposal, approval.location, request.user, proposalUploadServiceMock); + expect(getLocationStateMock).toBeCalledWith(proposal, request.user); + if (expectedDataAmountReached) { + expect(removeFdpgTask).not.toBeCalledWith(proposal, FdpgTaskType.DataAmountReached); + } else { + expect(removeFdpgTask).toBeCalledWith(proposal, FdpgTaskType.DataAmountReached); + } + expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); + expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); + expect(proposal.openDizChecks).toEqual([condition.location]); + }); + }); + + it('should delete conditional approval object and fdpg task', async () => { + const condition = new ConditionalApproval(); + condition.location = MiiLocation.UKRUB; + condition.fdpgTaskId = FdpgTaskType.ConditionApproval; + condition.dataAmount = 10; + const proposal = getProposalDocument(); + proposal.conditionalApprovals = [condition]; + proposal.totalPromisedDataAmount = condition.dataAmount + 1; + proposal.requestedData.desiredDataAmount = 11; + proposal.openDizChecks = []; + + const request = getRequest(); + + getLocationStateMock.mockReturnValueOnce({ + ...locationStateDefault, + conditionalApprovalAccepted: true, + }); + + expect(proposal.conditionalApprovals).toEqual([condition]); + await revertLocationVote(proposal, condition.location, request.user, proposalUploadServiceMock); + expect(getLocationStateMock).toBeCalledWith(proposal, request.user); + expect(removeFdpgTask).toBeCalledWith(proposal, condition.fdpgTaskId); + expect(proposal.conditionalApprovals).not.toEqual([condition]); + expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); + expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); + expect(proposal.openDizChecks).toEqual([condition.location]); + }); + + it('should delete the upload blob', async () => { + const condition = new ConditionalApproval(); + condition.location = MiiLocation.UKRUB; + condition.uploadId = 'uploadId'; + const proposal = getProposalDocument(); + proposal.conditionalApprovals = [condition]; + proposal.openDizChecks = []; + const request = getRequest(); + + getLocationStateMock.mockReturnValueOnce({ + ...locationStateDefault, + isConditionalApproval: true, + }); + + await revertLocationVote(proposal, condition.location, request.user, proposalUploadServiceMock); + expect(getLocationStateMock).toBeCalledWith(proposal, request.user); + expect(proposalUploadServiceMock.deleteUpload).toBeCalledTimes(1); + expect(proposalUploadServiceMock.deleteUpload).toBeCalledWith(proposal._id, condition.uploadId, request.user); + expect(clearLocationsVotes).toBeCalledWith(proposal, condition.location); + expect(proposal.uacApprovedLocations).not.toEqual([request.user.miiLocation]); + expect(proposal.openDizChecks).toEqual([condition.location]); + }); + }); +}); diff --git a/src/modules/proposal/utils/handle-location-vote.util.ts b/src/modules/proposal/utils/add-location-vote.util.ts similarity index 75% rename from src/modules/proposal/utils/handle-location-vote.util.ts rename to src/modules/proposal/utils/add-location-vote.util.ts index 76572c4..74e539f 100644 --- a/src/modules/proposal/utils/handle-location-vote.util.ts +++ b/src/modules/proposal/utils/add-location-vote.util.ts @@ -11,8 +11,6 @@ import { ConditionalApproval } from '../schema/sub-schema/conditional-approval.s import { UacApproval } from '../schema/sub-schema/uac-approval.schema'; import { addFdpgTaskAndReturnId, removeFdpgTask } from './add-fdpg-task.util'; import { clearLocationsVotes } from './location-flow.util'; -import { getLocationState } from './validate-access.util'; -import { ProposalUploadService } from '../services/proposal-upload.service'; export const addDizApproval = (proposal: Proposal, user: IRequestUser, vote: SetDizApprovalDto) => { clearLocationsVotes(proposal, user.miiLocation); @@ -148,46 +146,3 @@ export const addUacConditionReview = ( proposal.requestedButExcludedLocations.push(condition.location); } }; - -export const handleLocationDecision = async ( - proposal: Proposal, - location: MiiLocation, - user: IRequestUser, - proposalUploadService: ProposalUploadService, -) => { - const locationState = getLocationState(proposal, user); - - if (locationState.requestedButExcluded) { - proposal.declineReasons = proposal.declineReasons.filter((reason) => reason.location !== location); - } - - if (locationState.uacApproved) { - proposal.uacApprovals = proposal.uacApprovals.filter((approval) => approval.location !== location); - } - - if (locationState.conditionalApprovalAccepted) { - removeFdpgTask(proposal, FdpgTaskType.ConditionApproval); - - proposal.conditionalApprovals = proposal.conditionalApprovals.filter( - (condition) => condition.location !== location, - ); - - const locationDataAmount = proposal.uacApprovals.find((approval) => approval.location === location)?.dataAmount; - - proposal.totalPromisedDataAmount = proposal.totalPromisedDataAmount - locationDataAmount; - - const isDataAmountReached = proposal.totalPromisedDataAmount >= (proposal.requestedData.desiredDataAmount ?? 0); - if (!isDataAmountReached) { - removeFdpgTask(proposal, FdpgTaskType.DataAmountReached); - } - } - if (locationState.isConditionalApproval) { - const uploadId = proposal.conditionalApprovals.find((approval) => approval.location === location)?.uploadId; - - if (uploadId) { - await proposalUploadService.deleteUpload(proposal._id, uploadId, user); - } - } - clearLocationsVotes(proposal, location); - proposal.openDizChecks.push(location); -}; diff --git a/src/modules/proposal/utils/proposal-history.util.ts b/src/modules/proposal/utils/proposal-history.util.ts index ecf00a5..8241d81 100644 --- a/src/modules/proposal/utils/proposal-history.util.ts +++ b/src/modules/proposal/utils/proposal-history.util.ts @@ -131,12 +131,12 @@ export const addHistoryItemForUnselectedLocation = ( pushHistoryItem(proposalAfterChanges, user, type, location); }; -export const addHistoryItemForRevertLocationDecision = ( +export const addHistoryItemForRevertLocationVote = ( proposalAfterChanges: Proposal, user: IRequestUser, location: MiiLocation, ): void => { - const type = HistoryEventType.FdpgRevertedLocationDecision; + const type = HistoryEventType.FdpgRevertedLocationVote; pushHistoryItem(proposalAfterChanges, user, type, location); }; diff --git a/src/modules/proposal/utils/revert-location-vote.util.ts b/src/modules/proposal/utils/revert-location-vote.util.ts new file mode 100644 index 0000000..8519185 --- /dev/null +++ b/src/modules/proposal/utils/revert-location-vote.util.ts @@ -0,0 +1,51 @@ +import { getLocationState } from './validate-access.util'; +import { ProposalUploadService } from '../services/proposal-upload.service'; +import { Proposal } from '../schema/proposal.schema'; +import { MiiLocation } from 'src/shared/constants/mii-locations'; +import { IRequestUser } from 'src/shared/types/request-user.interface'; +import { removeFdpgTask } from './add-fdpg-task.util'; +import { FdpgTaskType } from '../enums/fdpg-task-type.enum'; +import { clearLocationsVotes } from './location-flow.util'; + +export const revertLocationVote = async ( + proposal: Proposal, + location: MiiLocation, + user: IRequestUser, + proposalUploadService: ProposalUploadService, +) => { + const locationState = getLocationState(proposal, user); + + if (locationState.requestedButExcluded) { + proposal.declineReasons = proposal.declineReasons.filter((reason) => reason.location !== location); + } + + if (locationState.uacApproved) { + proposal.uacApprovals = proposal.uacApprovals.filter((approval) => approval.location !== location); + } + + if (locationState.conditionalApprovalAccepted) { + removeFdpgTask(proposal, FdpgTaskType.ConditionApproval); + + proposal.conditionalApprovals = proposal.conditionalApprovals.filter( + (condition) => condition.location !== location, + ); + + const locationDataAmount = proposal.uacApprovals.find((approval) => approval.location === location)?.dataAmount; + + proposal.totalPromisedDataAmount = proposal.totalPromisedDataAmount - locationDataAmount; + + const isDataAmountReached = proposal.totalPromisedDataAmount >= (proposal.requestedData.desiredDataAmount ?? 0); + if (!isDataAmountReached) { + removeFdpgTask(proposal, FdpgTaskType.DataAmountReached); + } + } + if (locationState.isConditionalApproval) { + const uploadId = proposal.conditionalApprovals.find((approval) => approval.location === location)?.uploadId; + + if (uploadId) { + await proposalUploadService.deleteUpload(proposal._id, uploadId, user); + } + } + clearLocationsVotes(proposal, location); + proposal.openDizChecks.push(location); +}; diff --git a/src/modules/proposal/utils/validate-vote.util.ts b/src/modules/proposal/utils/validate-vote.util.ts index 37d328e..ad59ebc 100644 --- a/src/modules/proposal/utils/validate-vote.util.ts +++ b/src/modules/proposal/utils/validate-vote.util.ts @@ -23,8 +23,8 @@ export const validateUacApproval = (proposal: Proposal, user: IRequestUser) => { } }; -export const validateRevertLocationDecision = (proposal: Proposal) => { +export const validateRevertLocationVote = (proposal: Proposal) => { if (proposal.status !== ProposalStatus.LocationCheck) { - throw new ForbiddenException('The current status does not allow to revert the location decision'); + throw new ForbiddenException('The current status does not allow to revert the location vote'); } };