From 9a6fd0125bd8812d571dbfa8720a2ecf110b5700 Mon Sep 17 00:00:00 2001 From: siva kumar Date: Mon, 22 Jun 2020 16:29:07 +0530 Subject: [PATCH] [ACA-3448] Candidate user is able to complete a task without a form attached before claiming it (#5780) * [ACA-3448] Candidate user is able to complete a task without a form attached before claiming it * * Added unit test to the recent changes * * Updated unit test * Fixed comment * * Updated doc * * Revered claim changes * * Fixed comments * * Added unit tests to the recent changes * * Removed errorModel --- .../src/lib/mock/task/task-details.mock.ts | 46 +++++- .../task-form/task-form.component.html | 21 ++- .../task-form/task-form.component.spec.ts | 154 +++++++++++++++--- .../task-form/task-form.component.ts | 25 ++- 4 files changed, 212 insertions(+), 34 deletions(-) diff --git a/lib/process-services/src/lib/mock/task/task-details.mock.ts b/lib/process-services/src/lib/mock/task/task-details.mock.ts index bf04da9c1f..55e42d040d 100644 --- a/lib/process-services/src/lib/mock/task/task-details.mock.ts +++ b/lib/process-services/src/lib/mock/task/task-details.mock.ts @@ -206,6 +206,41 @@ export let initiatorCanCompleteTaskDetailsMock = new TaskDetailsModel({ taskDefinitionKey: 'sid-DDECD9E4-0299-433F-9193-C3D905C3EEBE', executionId: '86', involvedGroups: [], + involvedPeople: [], + memberOfCandidateUsers: false, + managerOfCandidateGroup: false, + memberOfCandidateGroup: false +}); + +export let initiatorWithCandidatesTaskDetailsMock = new TaskDetailsModel({ + id: '91', + name: 'Request translation', + description: null, + category: null, + assignee: null, + created: '2016-11-03T15:25:42.749+0000', + dueDate: null, + endDate: null, + duration: null, + priority: 50, + parentTaskId: null, + parentTaskName: null, + processInstanceId: '86', + processInstanceName: null, + processDefinitionId: 'TranslationProcess:2:8', + processDefinitionName: 'Translation Process', + processDefinitionDescription: null, + processDefinitionKey: 'TranslationProcess', + processDefinitionCategory: 'http://www.activiti.org/processdef', + processDefinitionVersion: 2, + processDefinitionDeploymentId: '5', + formKey: '4', + processInstanceStartUserId: '1001', + initiatorCanCompleteTask: true, + adhocTaskCanBeReassigned: false, + taskDefinitionKey: 'sid-DDECD9E4-0299-433F-9193-C3D905C3EEBE', + executionId: '86', + involvedGroups: [], involvedPeople: [ { id: 1001, @@ -220,9 +255,9 @@ export let initiatorCanCompleteTaskDetailsMock = new TaskDetailsModel({ email: 'fake@app.activiti.com' } ], - memberOfCandidateUsers: false, - managerOfCandidateGroup: false, - memberOfCandidateGroup: false + memberOfCandidateUsers: true, + managerOfCandidateGroup: true, + memberOfCandidateGroup: true }); export let taskDetailsWithOutAssigneeMock = new TaskDetailsModel({ @@ -316,10 +351,13 @@ export let claimedTaskDetailsMock = new TaskDetailsModel({ endDate: null, duration: null, priority: 50, + formKey: '4', parentTaskId: null, parentTaskName: null, processInstanceId: '86', processInstanceName: null, + processInstanceStartUserId: '1002', + initiatorCanCompleteTask: false, processDefinitionId: 'TranslationProcess:2:8', processDefinitionName: 'Translation Process', involvedGroups: [ @@ -507,7 +545,7 @@ export let taskDetailsWithOutFormMock = new TaskDetailsModel({ 'name': 'Request translation', 'description': 'fake description', 'category': null, - 'assignee': {'id': 1001, 'firstName': 'Admin', 'lastName': 'Paul', 'email': 'my@mymail.com' }, + 'assignee': {'id': 1001, 'firstName': 'Admin', 'lastName': 'Paul', 'email': 'fake-email@gmail.com' }, 'created': '2016-11-03T15:25:42.749+0000', 'dueDate': '2016-11-03T15:25:42.749+0000', 'endDate': null, diff --git a/lib/process-services/src/lib/task-list/components/task-form/task-form.component.html b/lib/process-services/src/lib/task-list/components/task-form/task-form.component.html index 3ba824ba04..85d96084d9 100644 --- a/lib/process-services/src/lib/task-list/components/task-form/task-form.component.html +++ b/lib/process-services/src/lib/task-list/components/task-form/task-form.component.html @@ -5,7 +5,6 @@ [showValidationIcon]="showFormValidationIcon" [showRefreshButton]="showFormRefreshButton" [showCompleteButton]="showFormCompleteButton" - [disableCompleteButton]="!isCompleteButtonEnabled()" [showSaveButton]="isSaveButtonVisible()" [readOnly]="isReadOnlyForm()" [fieldValidators]="fieldValidators" @@ -61,10 +60,11 @@ - - @@ -73,18 +73,25 @@ + diff --git a/lib/process-services/src/lib/task-list/components/task-form/task-form.component.spec.ts b/lib/process-services/src/lib/task-list/components/task-form/task-form.component.spec.ts index a14c149544..bfae1e5c4b 100644 --- a/lib/process-services/src/lib/task-list/components/task-form/task-form.component.spec.ts +++ b/lib/process-services/src/lib/task-list/components/task-form/task-form.component.spec.ts @@ -40,7 +40,8 @@ import { initiatorCanCompleteTaskDetailsMock, taskDetailsWithOutCandidateGroup, claimedTaskDetailsMock, - claimedByGroupMemberMock + claimedByGroupMemberMock, + initiatorWithCandidatesTaskDetailsMock } from '../../../mock/task/task-details.mock'; import { TaskDetailsModel } from '../../models/task-details.model'; import { ProcessTestingModule } from '../../../testing/process.testing.module'; @@ -80,7 +81,7 @@ describe('TaskFormComponent', () => { taskDetailsMock.processDefinitionId = null; spyOn(formService, 'getTask').and.returnValue(of(taskDetailsMock)); authService = TestBed.get(AuthenticationService); - getBpmLoggedUserSpy = spyOn(authService, 'getBpmLoggedUser').and.returnValue(of({ email: 'fake-email' })); + getBpmLoggedUserSpy = spyOn(authService, 'getBpmLoggedUser').and.returnValue(of({ id: 1001, email: 'fake-email@gmail.com' })); }); afterEach(async() => { @@ -129,22 +130,6 @@ describe('TaskFormComponent', () => { expect(formCompletedSpy).toHaveBeenCalled(); }); - it('Should be able to complete the task as a process initiator', async () => { - const formCompletedSpy: jasmine.Spy = spyOn(component.formCompleted, 'emit'); - const completeTaskFormSpy = spyOn(formService, 'completeTaskForm').and.returnValue(of({})); - getTaskDetailsSpy.and.returnValue(of(initiatorCanCompleteTaskDetailsMock)); - component.taskId = '123'; - fixture.detectChanges(); - await fixture.whenStable(); - const activitFormSelector = element.querySelector('adf-form'); - const completeButton = fixture.debugElement.nativeElement.querySelector('#adf-form-complete'); - expect(activitFormSelector).toBeDefined(); - expect(completeButton['disabled']).toEqual(false); - completeButton.click(); - expect(completeTaskFormSpy).toHaveBeenCalled(); - expect(formCompletedSpy).toHaveBeenCalled(); - }); - it('Should emit error event in case form complete service fails', async () => { const errorSpy: jasmine.Spy = spyOn(component.error, 'emit'); const completeTaskFormSpy = spyOn(formService, 'completeTaskForm').and.returnValue(throwError({message: 'servce failed'})); @@ -160,7 +145,6 @@ describe('TaskFormComponent', () => { expect(completeTaskFormSpy).toHaveBeenCalled(); expect(errorSpy).toHaveBeenCalled(); }); - }); describe('change detection', () => { @@ -500,6 +484,97 @@ describe('TaskFormComponent', () => { }); }); + describe('Complete task', () => { + + it('Should be able to complete the assigned task in case process initiator not allowed to complete the task', async () => { + getBpmLoggedUserSpy.and.returnValue(of({ id: 1002, firstName: 'Wilbur', lastName: 'Adams', email: 'wilbur@app.activiti.com' })); + getTaskDetailsSpy.and.returnValue(of(taskDetailsMock)); + + const formCompletedSpy: jasmine.Spy = spyOn(component.formCompleted, 'emit'); + const completeTaskFormSpy = spyOn(formService, 'completeTaskForm').and.returnValue(of({})); + component.taskId = '123'; + component.ngOnInit(); + + fixture.detectChanges(); + await fixture.whenStable(); + + const completeButton = fixture.debugElement.nativeElement.querySelector('#adf-form-complete'); + + expect(component.isProcessInitiator()).toEqual(false); + expect(completeButton['disabled']).toEqual(false); + + completeButton.click(); + + expect(completeTaskFormSpy).toHaveBeenCalled(); + expect(formCompletedSpy).toHaveBeenCalled(); + }); + + it('Should be able to complete the task if process initiator allowed to complete the task', async () => { + getBpmLoggedUserSpy.and.returnValue(of({ id: 1001, firstName: 'Wilbur', lastName: 'Adams', email: 'wilbur@app.activiti.com' })); + const formCompletedSpy: jasmine.Spy = spyOn(component.formCompleted, 'emit'); + const completeTaskFormSpy = spyOn(formService, 'completeTaskForm').and.returnValue(of({})); + getTaskDetailsSpy.and.returnValue(of(initiatorCanCompleteTaskDetailsMock)); + + component.taskId = '123'; + fixture.detectChanges(); + await fixture.whenStable(); + + const activitFormSelector = element.querySelector('adf-form'); + const completeButton = fixture.debugElement.nativeElement.querySelector('#adf-form-complete'); + + expect(activitFormSelector).toBeDefined(); + expect(completeButton['disabled']).toEqual(false); + expect(component.isProcessInitiator()).toEqual(true); + + completeButton.click(); + + expect(completeTaskFormSpy).toHaveBeenCalled(); + expect(formCompletedSpy).toHaveBeenCalled(); + }); + + it('Should not be able to complete a task with candidates users if process initiator allowed to complete the task', async () => { + getTaskDetailsSpy.and.returnValue(of(initiatorWithCandidatesTaskDetailsMock)); + + component.taskId = '123'; + fixture.detectChanges(); + await fixture.whenStable(); + + expect(component.canInitiatorComplete()).toEqual(true); + expect(component.isProcessInitiator()).toEqual(true); + expect(component.isCandidateMember()).toEqual(true); + + const activitFormSelector = element.querySelector('adf-form'); + const completeButton = fixture.debugElement.nativeElement.querySelector('#adf-form-complete'); + + expect(activitFormSelector).toBeDefined(); + expect(completeButton['disabled']).toEqual(true); + }); + + it('Should be able to complete a task with candidates users if process initiator not allowed to complete the task', async () => { + const formCompletedSpy: jasmine.Spy = spyOn(component.formCompleted, 'emit'); + getBpmLoggedUserSpy.and.returnValue(of({ id: 1001, firstName: 'Wilbur', lastName: 'Adams', email: 'wilbur@app.activiti.com' })); + const completeTaskFormSpy = spyOn(formService, 'completeTaskForm').and.returnValue(of({})); + getTaskDetailsSpy.and.returnValue(of(claimedTaskDetailsMock)); + + component.taskId = '123'; + fixture.detectChanges(); + await fixture.whenStable(); + + const activitFormSelector = element.querySelector('adf-form'); + const completeButton = fixture.debugElement.nativeElement.querySelector('#adf-form-complete'); + + expect(activitFormSelector).toBeDefined(); + expect(component.canInitiatorComplete()).toEqual(false); + expect(component.isProcessInitiator()).toEqual(false); + expect(completeButton['disabled']).toEqual(false); + + completeButton.click(); + + expect(completeTaskFormSpy).toHaveBeenCalled(); + expect(formCompletedSpy).toHaveBeenCalled(); + }); + }); + describe('Claim/Unclaim buttons', () => { it('should display the claim button if no assignee', async() => { @@ -589,7 +664,7 @@ describe('TaskFormComponent', () => { component.taskId = 'mock-task-id'; - component.taskClaimed.subscribe((taskId) => { + component.taskClaimed.subscribe((taskId: string) => { expect(taskId).toEqual(component.taskId); done(); }); @@ -601,6 +676,25 @@ describe('TaskFormComponent', () => { claimBtn.nativeElement.click(); }); + it('should emit error event in case claim task api fails', (done) => { + const mockError = { message: 'Api Failed' }; + spyOn(taskListService, 'claimTask').and.returnValue(throwError(mockError)); + getTaskDetailsSpy.and.returnValue(of(claimableTaskDetailsMock)); + + component.taskId = 'mock-task-id'; + + component.error.subscribe((error: any) => { + expect(error).toEqual(mockError); + done(); + }); + + component.ngOnInit(); + fixture.detectChanges(); + + const claimBtn = fixture.debugElement.query(By.css('[adf-claim-task]')); + claimBtn.nativeElement.click(); + }); + it('should emit taskUnClaimed when task is unclaimed', (done) => { spyOn(taskListService, 'unclaimTask').and.returnValue(of({})); getBpmLoggedUserSpy.and.returnValue(of(claimedTaskDetailsMock.assignee)); @@ -619,5 +713,25 @@ describe('TaskFormComponent', () => { const unclaimBtn = fixture.debugElement.query(By.css('[adf-unclaim-task]')); unclaimBtn.nativeElement.click(); }); + + it('should emit error event in case unclaim task api fails', (done) => { + const mockError = { message: 'Api Failed' }; + spyOn(taskListService, 'unclaimTask').and.returnValue(throwError(mockError)); + getBpmLoggedUserSpy.and.returnValue(of(claimedTaskDetailsMock.assignee)); + getTaskDetailsSpy.and.returnValue(of(claimedTaskDetailsMock)); + + component.taskId = 'mock-task-id'; + + component.error.subscribe((error: any) => { + expect(error).toEqual(mockError); + done(); + }); + + component.ngOnInit(); + fixture.detectChanges(); + + const unclaimBtn = fixture.debugElement.query(By.css('[adf-unclaim-task]')); + unclaimBtn.nativeElement.click(); + }); }); }); diff --git a/lib/process-services/src/lib/task-list/components/task-form/task-form.component.ts b/lib/process-services/src/lib/task-list/components/task-form/task-form.component.ts index 77dca180fc..dd477fe1a3 100644 --- a/lib/process-services/src/lib/task-list/components/task-form/task-form.component.ts +++ b/lib/process-services/src/lib/task-list/components/task-form/task-form.component.ts @@ -272,15 +272,26 @@ export class TaskFormComponent implements OnInit { } isReadOnlyForm(): boolean { - return this.internalReadOnlyForm || !(this.isAssignedToMe() || this.canInitiatorComplete()); + let readOnlyForm: boolean; + if (this.isCandidateMember()) { + readOnlyForm = this.internalReadOnlyForm || !this.isAssignedToMe(); + } else { + readOnlyForm = this.internalReadOnlyForm || !(this.isAssignedToMe() || (this.canInitiatorComplete() && this.isProcessInitiator())); + } + + return readOnlyForm; + } + + isProcessInitiator(): boolean { + return this.currentLoggedUser && ( this.currentLoggedUser.id === +this.taskDetails.processInstanceStartUserId); } isSaveButtonVisible(): boolean { return this.showFormSaveButton && (!this.canInitiatorComplete() || this.isAssignedToMe()); } - canCompleteTask(): boolean { - return !this.isCompletedTask() && this.isAssignedToMe(); + canCompleteNoFormTask(): boolean { + return this.isReadOnlyForm(); } getCompletedTaskTranslatedMessage(): Observable { @@ -307,7 +318,15 @@ export class TaskFormComponent implements OnInit { this.taskClaimed.emit(taskId); } + onClaimTaskError(error: any) { + this.error.emit(error); + } + onUnclaimTask(taskId: string) { this.taskUnclaimed.emit(taskId); } + + onUnclaimTaskError(error: any) { + this.error.emit(error); + } }