[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
This commit is contained in:
siva kumar 2020-06-22 16:29:07 +05:30 committed by GitHub
parent 29d953e2d1
commit 9a6fd0125b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 212 additions and 34 deletions

View File

@ -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,

View File

@ -5,7 +5,6 @@
[showValidationIcon]="showFormValidationIcon"
[showRefreshButton]="showFormRefreshButton"
[showCompleteButton]="showFormCompleteButton"
[disableCompleteButton]="!isCompleteButtonEnabled()"
[showSaveButton]="isSaveButtonVisible()"
[readOnly]="isReadOnlyForm()"
[fieldValidators]="fieldValidators"
@ -61,10 +60,11 @@
</mat-card-content>
<mat-card-actions class="adf-task-form-actions">
<ng-template [ngTemplateOutlet]="taskFormButtons"></ng-template>
<button id="adf-no-form-cancel-button" mat-button *ngIf="showCancelButton" (click)="onCancel()">
{{'ADF_TASK_FORM.EMPTY_FORM.BUTTONS.CANCEL' | translate}}
</button>
<button mat-button *ngIf="!isCompletedTask()" color="primary" (click)="onCompleteTask()" id="adf-no-form-complete-button">
<button mat-button
*ngIf="!isCompletedTask()" id="adf-no-form-complete-button"
color="primary"
[disabled]="canCompleteNoFormTask()"
(click)="onCompleteTask()">
{{'ADF_TASK_FORM.EMPTY_FORM.BUTTONS.COMPLETE' | translate}}
</button>
</mat-card-actions>
@ -73,18 +73,25 @@
</ng-template>
<ng-template #taskFormButtons>
<button mat-button id="adf-no-form-cancel-button"
*ngIf="showCancelButton"
(click)="onCancel()">
{{'ADF_TASK_FORM.EMPTY_FORM.BUTTONS.CANCEL' | translate}}
</button>
<button mat-button data-automation-id="adf-task-form-claim-button"
*ngIf="isTaskClaimable()"
adf-claim-task
[taskId]="taskId"
(success)="onClaimTask($event)">
(success)="onClaimTask($event)"
(error)="onClaimTaskError($event)">
{{ 'ADF_TASK_LIST.DETAILS.BUTTON.CLAIM' | translate }}
</button>
<button mat-button data-automation-id="adf-task-form-unclaim-button"
*ngIf="isTaskClaimedByCandidateMember()"
adf-unclaim-task
[taskId]="taskId"
(success)="onUnclaimTask($event)">
(success)="onUnclaimTask($event)"
(error)="onUnclaimTaskError($event)">
{{ 'ADF_TASK_LIST.DETAILS.BUTTON.UNCLAIM' | translate }}
</button>
</ng-template>

View File

@ -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();
});
});
});

View File

@ -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<string> {
@ -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);
}
}