/*
* Copyright (c) 2010-2016 Evolveum
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.evolveum.midpoint.wf.impl.policy;
import com.evolveum.midpoint.model.api.ModelExecuteOptions;
import com.evolveum.midpoint.model.api.context.ModelContext;
import com.evolveum.midpoint.model.api.context.ModelState;
import com.evolveum.midpoint.model.api.hooks.HookOperationMode;
import com.evolveum.midpoint.model.impl.AbstractModelImplementationIntegrationTest;
import com.evolveum.midpoint.model.impl.controller.ModelOperationTaskHandler;
import com.evolveum.midpoint.model.impl.lens.Clockwork;
import com.evolveum.midpoint.model.impl.lens.LensContext;
import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.query.builder.QueryBuilder;
import com.evolveum.midpoint.prism.query.builder.S_AtomicFilterExit;
import com.evolveum.midpoint.prism.util.PrismUtil;
import com.evolveum.midpoint.schema.GetOperationOptions;
import com.evolveum.midpoint.schema.SearchResultList;
import com.evolveum.midpoint.schema.SelectorOptions;
import com.evolveum.midpoint.schema.constants.ObjectTypes;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ObjectTypeUtil;
import com.evolveum.midpoint.schema.util.WfContextUtil;
import com.evolveum.midpoint.security.api.SecurityUtil;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.task.api.TaskExecutionStatus;
import com.evolveum.midpoint.task.api.TaskManager;
import com.evolveum.midpoint.test.AbstractIntegrationTest;
import com.evolveum.midpoint.test.Checker;
import com.evolveum.midpoint.test.IntegrationTestTools;
import com.evolveum.midpoint.util.exception.*;
import com.evolveum.midpoint.wf.api.WorkflowManager;
import com.evolveum.midpoint.wf.impl.WfTestUtil;
import com.evolveum.midpoint.wf.impl.activiti.ActivitiEngine;
import com.evolveum.midpoint.wf.impl.processes.common.CommonProcessVariableNames;
import com.evolveum.midpoint.wf.impl.processes.common.LightweightObjectRef;
import com.evolveum.midpoint.wf.impl.WorkflowResult;
import com.evolveum.midpoint.wf.impl.processors.general.GeneralChangeProcessor;
import com.evolveum.midpoint.wf.impl.processors.primary.PrimaryChangeProcessor;
import com.evolveum.midpoint.wf.impl.tasks.WfTaskUtil;
import com.evolveum.midpoint.wf.impl.util.MiscDataUtil;
import com.evolveum.midpoint.wf.util.QueryUtils;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import com.evolveum.midpoint.xml.ns._public.common.common_3.WfContextType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemType;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.ContextConfiguration;
import javax.xml.namespace.QName;
import java.io.File;
import java.util.*;
import static com.evolveum.midpoint.prism.PrismConstants.T_PARENT;
import static com.evolveum.midpoint.schema.GetOperationOptions.createRetrieve;
import static com.evolveum.midpoint.schema.GetOperationOptions.resolveItemsNamed;
import static com.evolveum.midpoint.test.IntegrationTestTools.display;
import static com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType.F_WORKFLOW_CONTEXT;
import static com.evolveum.midpoint.xml.ns._public.common.common_3.WfContextType.*;
import static com.evolveum.midpoint.xml.ns._public.common.common_3.WfPrimaryChangeProcessorStateType.F_DELTAS_TO_PROCESS;
import static com.evolveum.midpoint.xml.ns._public.common.common_3.WorkItemType.*;
import static org.testng.AssertJUnit.*;
/**
* @author mederly
*
*/
@ContextConfiguration(locations = {"classpath:ctx-workflow-test-main.xml"})
@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
public class AbstractWfTestPolicy extends AbstractModelImplementationIntegrationTest {
protected static final File TEST_RESOURCE_DIR = new File("src/test/resources/policy");
private static final File SYSTEM_CONFIGURATION_FILE = new File(TEST_RESOURCE_DIR, "system-configuration.xml");
public static final File ROLE_SUPERUSER_FILE = new File(TEST_RESOURCE_DIR, "role-superuser.xml");
public static final File USER_ADMINISTRATOR_FILE = new File(TEST_RESOURCE_DIR, "user-administrator.xml");
protected static final File USER_JACK_FILE = new File(TEST_RESOURCE_DIR, "user-jack.xml");
protected static final File USER_LEAD1_FILE = new File(TEST_RESOURCE_DIR, "user-lead1.xml");
protected static final File USER_LEAD1_DEPUTY_1_FILE = new File(TEST_RESOURCE_DIR, "user-lead1-deputy1.xml");
protected static final File USER_LEAD1_DEPUTY_2_FILE = new File(TEST_RESOURCE_DIR, "user-lead1-deputy2.xml");
protected static final File USER_LEAD2_FILE = new File(TEST_RESOURCE_DIR, "user-lead2.xml");
protected static final File USER_LEAD3_FILE = new File(TEST_RESOURCE_DIR, "user-lead3.xml");
protected static final File USER_LEAD10_FILE = new File(TEST_RESOURCE_DIR, "user-lead10.xml");
protected static final File USER_SECURITY_APPROVER_FILE = new File(TEST_RESOURCE_DIR, "user-security-approver.xml");
protected static final File USER_SECURITY_APPROVER_DEPUTY_FILE = new File(TEST_RESOURCE_DIR, "user-security-approver-deputy.xml");
protected static final File USER_SECURITY_APPROVER_DEPUTY_LIMITED_FILE = new File(TEST_RESOURCE_DIR, "user-security-approver-deputy-limited.xml");
protected static final File ROLE_APPROVER_FILE = new File(TEST_RESOURCE_DIR, "041-role-approver.xml");
protected static final File METAROLE_DEFAULT_FILE = new File(TEST_RESOURCE_DIR, "metarole-default.xml");
protected static final File METAROLE_SECURITY_FILE = new File(TEST_RESOURCE_DIR, "metarole-security.xml");
// following 2 are not used by default (assigned when necessary)
protected static final File METAROLE_PRUNE_TEST2X_ROLES_FILE = new File(TEST_RESOURCE_DIR, "metarole-prune-test2x-roles.xml");
protected static final File METAROLE_APPROVE_UNASSIGN_FILE = new File(TEST_RESOURCE_DIR, "metarole-approve-unassign.xml");
protected static final File ROLE_ROLE1_FILE = new File(TEST_RESOURCE_DIR, "role-role1.xml");
protected static final File ROLE_ROLE1A_FILE = new File(TEST_RESOURCE_DIR, "role-role1a.xml");
protected static final File ROLE_ROLE1B_FILE = new File(TEST_RESOURCE_DIR, "role-role1b.xml");
protected static final File ROLE_ROLE2_FILE = new File(TEST_RESOURCE_DIR, "role-role2.xml");
protected static final File ROLE_ROLE2A_FILE = new File(TEST_RESOURCE_DIR, "role-role2a.xml");
protected static final File ROLE_ROLE2B_FILE = new File(TEST_RESOURCE_DIR, "role-role2b.xml");
protected static final File ROLE_ROLE3_FILE = new File(TEST_RESOURCE_DIR, "role-role3.xml");
protected static final File ROLE_ROLE3A_FILE = new File(TEST_RESOURCE_DIR, "role-role3a.xml");
protected static final File ROLE_ROLE3B_FILE = new File(TEST_RESOURCE_DIR, "role-role3b.xml");
protected static final File ROLE_ROLE4_FILE = new File(TEST_RESOURCE_DIR, "role-role4.xml");
protected static final File ROLE_ROLE4A_FILE = new File(TEST_RESOURCE_DIR, "role-role4a.xml");
protected static final File ROLE_ROLE4B_FILE = new File(TEST_RESOURCE_DIR, "role-role4b.xml");
protected static final File ROLE_ROLE10_FILE = new File(TEST_RESOURCE_DIR, "role-role10.xml");
protected static final File ROLE_ROLE10A_FILE = new File(TEST_RESOURCE_DIR, "role-role10a.xml");
protected static final File ROLE_ROLE10B_FILE = new File(TEST_RESOURCE_DIR, "role-role10b.xml");
protected static final File ROLE_FOCUS_ASSIGNMENT_MAPPING = new File(TEST_RESOURCE_DIR, "role-focus-assignment-mapping.xml");
protected static final File USER_TEMPLATE_ASSIGNING_ROLE_1A = new File(TEST_RESOURCE_DIR, "user-template-assigning-role1a.xml");
protected static final File USER_TEMPLATE_ASSIGNING_ROLE_1A_AFTER = new File(TEST_RESOURCE_DIR, "user-template-assigning-role1a-after.xml");
protected static final String USER_ADMINISTRATOR_OID = SystemObjectsType.USER_ADMINISTRATOR.value();
protected String userJackOid;
protected String userLead1Oid;
protected String userLead1Deputy1Oid;
protected String userLead1Deputy2Oid;
protected String userLead2Oid;
protected String userLead3Oid;
protected String userLead10Oid;
protected String userSecurityApproverOid;
protected String userSecurityApproverDeputyOid;
protected String userSecurityApproverDeputyLimitedOid;
protected String roleApproverOid;
protected String metaroleDefaultOid;
protected String metaroleSecurityOid;
protected String metarolePruneTest2xRolesOid;
protected String metaroleApproveUnassign;
protected String roleRole1Oid;
protected String roleRole1aOid;
protected String roleRole1bOid;
protected String roleRole2Oid;
protected String roleRole2aOid;
protected String roleRole2bOid;
protected String roleRole3Oid;
protected String roleRole3aOid;
protected String roleRole3bOid;
protected String roleRole4Oid;
protected String roleRole4aOid;
protected String roleRole4bOid;
protected String roleRole10Oid;
protected String roleRole10aOid;
protected String roleRole10bOid;
protected String roleFocusAssignmentMapping;
protected String userTemplateAssigningRole1aOid;
protected String userTemplateAssigningRole1aOidAfter;
@Autowired
protected Clockwork clockwork;
@Autowired
protected TaskManager taskManager;
@Autowired
protected WorkflowManager workflowManager;
@Autowired
protected WfTaskUtil wfTaskUtil;
@Autowired
protected ActivitiEngine activitiEngine;
@Autowired
protected MiscDataUtil miscDataUtil;
@Autowired
protected PrimaryChangeProcessor primaryChangeProcessor;
@Autowired
protected GeneralChangeProcessor generalChangeProcessor;
protected PrismObject<UserType> userAdministrator;
@Override
public void initSystem(Task initTask, OperationResult initResult) throws Exception {
super.initSystem(initTask, initResult);
modelService.postInit(initResult);
repoAddObjectFromFile(getSystemConfigurationFile(), initResult);
repoAddObjectFromFile(ROLE_SUPERUSER_FILE, initResult);
userAdministrator = repoAddObjectFromFile(USER_ADMINISTRATOR_FILE, initResult);
login(userAdministrator);
roleApproverOid = repoAddObjectFromFile(ROLE_APPROVER_FILE, initResult).getOid();
metaroleDefaultOid = repoAddObjectFromFile(METAROLE_DEFAULT_FILE, initResult).getOid();
metaroleSecurityOid = repoAddObjectFromFile(METAROLE_SECURITY_FILE, initResult).getOid();
metarolePruneTest2xRolesOid = repoAddObjectFromFile(METAROLE_PRUNE_TEST2X_ROLES_FILE, initResult).getOid();
metaroleApproveUnassign = repoAddObjectFromFile(METAROLE_APPROVE_UNASSIGN_FILE, initResult).getOid();
userJackOid = repoAddObjectFromFile(USER_JACK_FILE, initResult).getOid();
roleRole1Oid = repoAddObjectFromFile(ROLE_ROLE1_FILE, initResult).getOid();
roleRole1aOid = repoAddObjectFromFile(ROLE_ROLE1A_FILE, initResult).getOid();
roleRole1bOid = repoAddObjectFromFile(ROLE_ROLE1B_FILE, initResult).getOid();
roleRole2Oid = repoAddObjectFromFile(ROLE_ROLE2_FILE, initResult).getOid();
roleRole2aOid = repoAddObjectFromFile(ROLE_ROLE2A_FILE, initResult).getOid();
roleRole2bOid = repoAddObjectFromFile(ROLE_ROLE2B_FILE, initResult).getOid();
roleRole3Oid = repoAddObjectFromFile(ROLE_ROLE3_FILE, initResult).getOid();
roleRole3aOid = repoAddObjectFromFile(ROLE_ROLE3A_FILE, initResult).getOid();
roleRole3bOid = repoAddObjectFromFile(ROLE_ROLE3B_FILE, initResult).getOid();
roleRole4Oid = repoAddObjectFromFile(ROLE_ROLE4_FILE, initResult).getOid();
roleRole4aOid = repoAddObjectFromFile(ROLE_ROLE4A_FILE, initResult).getOid();
roleRole4bOid = repoAddObjectFromFile(ROLE_ROLE4B_FILE, initResult).getOid();
roleRole10Oid = repoAddObjectFromFile(ROLE_ROLE10_FILE, initResult).getOid();
roleRole10aOid = repoAddObjectFromFile(ROLE_ROLE10A_FILE, initResult).getOid();
roleRole10bOid = repoAddObjectFromFile(ROLE_ROLE10B_FILE, initResult).getOid();
roleFocusAssignmentMapping = repoAddObjectFromFile(ROLE_FOCUS_ASSIGNMENT_MAPPING, initResult).getOid();
userLead1Oid = addAndRecomputeUser(USER_LEAD1_FILE, initTask, initResult);
userLead2Oid = addAndRecomputeUser(USER_LEAD2_FILE, initTask, initResult);
userLead3Oid = addAndRecomputeUser(USER_LEAD3_FILE, initTask, initResult);
// LEAD10 will be imported later!
userSecurityApproverOid = addAndRecomputeUser(USER_SECURITY_APPROVER_FILE, initTask, initResult);
userSecurityApproverDeputyOid = addAndRecomputeUser(USER_SECURITY_APPROVER_DEPUTY_FILE, initTask, initResult);
userSecurityApproverDeputyLimitedOid = addAndRecomputeUser(USER_SECURITY_APPROVER_DEPUTY_LIMITED_FILE, initTask, initResult);
userTemplateAssigningRole1aOid = repoAddObjectFromFile(USER_TEMPLATE_ASSIGNING_ROLE_1A, initResult).getOid();
userTemplateAssigningRole1aOidAfter = repoAddObjectFromFile(USER_TEMPLATE_ASSIGNING_ROLE_1A_AFTER, initResult).getOid();
}
protected File getSystemConfigurationFile() {
return SYSTEM_CONFIGURATION_FILE;
}
protected void importLead10(Task task, OperationResult result) throws Exception {
userLead10Oid = addAndRecomputeUser(USER_LEAD10_FILE, task, result);
}
protected void importLead1Deputies(Task task, OperationResult result) throws Exception {
userLead1Deputy1Oid = addAndRecomputeUser(USER_LEAD1_DEPUTY_1_FILE, task, result);
userLead1Deputy2Oid = addAndRecomputeUser(USER_LEAD1_DEPUTY_2_FILE, task, result);
}
protected Map<String, WorkflowResult> createResultMap(String oid, WorkflowResult result) {
Map<String, WorkflowResult> retval = new HashMap<>();
retval.put(oid, result);
return retval;
}
protected Map<String, WorkflowResult> createResultMap(String oid, WorkflowResult approved, String oid2,
WorkflowResult approved2) {
Map<String, WorkflowResult> retval = new HashMap<String, WorkflowResult>();
retval.put(oid, approved);
retval.put(oid2, approved2);
return retval;
}
protected Map<String, WorkflowResult> createResultMap(String oid, WorkflowResult approved, String oid2,
WorkflowResult approved2, String oid3, WorkflowResult approved3) {
Map<String, WorkflowResult> retval = new HashMap<String, WorkflowResult>();
retval.put(oid, approved);
retval.put(oid2, approved2);
retval.put(oid3, approved3);
return retval;
}
protected void checkAuditRecords(Map<String, WorkflowResult> expectedResults) {
checkWorkItemAuditRecords(expectedResults);
checkWfProcessAuditRecords(expectedResults);
}
protected void checkWorkItemAuditRecords(Map<String, WorkflowResult> expectedResults) {
WfTestUtil.checkWorkItemAuditRecords(expectedResults, dummyAuditService);
}
protected void checkWfProcessAuditRecords(Map<String, WorkflowResult> expectedResults) {
WfTestUtil.checkWfProcessAuditRecords(expectedResults, dummyAuditService);
}
protected void removeAllAssignments(String oid, OperationResult result) throws Exception {
PrismObject<UserType> user = repositoryService.getObject(UserType.class, oid, null, result);
for (AssignmentType at : user.asObjectable().getAssignment()) {
ObjectDelta delta = ObjectDelta
.createModificationDeleteContainer(UserType.class, oid, UserType.F_ASSIGNMENT, prismContext,
at.asPrismContainerValue().clone());
repositoryService.modifyObject(UserType.class, oid, delta.getModifications(), result);
display("Removed assignment " + at + " from " + user);
}
}
public void createObject(final String TEST_NAME, ObjectType object, boolean immediate, boolean approve,
String assigneeOid) throws Exception {
ObjectDelta<RoleType> addObjectDelta = ObjectDelta.createAddDelta((PrismObject) object.asPrismObject());
executeTest(TEST_NAME, new TestDetails() {
@Override
protected LensContext createModelContext(OperationResult result) throws Exception {
LensContext<RoleType> lensContext = createLensContext((Class) object.getClass());
addFocusDeltaToContext(lensContext, addObjectDelta);
return lensContext;
}
@Override
protected void afterFirstClockworkRun(Task rootTask, List<Task> subtasks, List<WorkItemType> workItems, OperationResult result) throws Exception {
if (immediate) {
assertFalse("There is model context in the root task (it should not be there)",
wfTaskUtil.hasModelContext(rootTask));
} else {
ModelContext taskModelContext = wfTaskUtil.getModelContext(rootTask, result);
ObjectDelta realDelta0 = taskModelContext.getFocusContext().getPrimaryDelta();
assertTrue("Non-empty primary focus delta: " + realDelta0.debugDump(), realDelta0.isEmpty());
assertNoObject(object);
ExpectedTask expectedTask = new ExpectedTask(null, "Addition of " + object.getName().getOrig());
ExpectedWorkItem expectedWorkItem = new ExpectedWorkItem(assigneeOid, null, expectedTask);
assertWfContextAfterClockworkRun(rootTask, subtasks, workItems, result,
null,
Collections.singletonList(expectedTask),
Collections.singletonList(expectedWorkItem));
}
}
@Override
protected void afterTask0Finishes(Task task, OperationResult result) throws Exception {
assertNoObject(object);
}
@Override
protected void afterRootTaskFinishes(Task task, List<Task> subtasks, OperationResult result) throws Exception {
if (approve) {
assertObject(object);
} else {
assertNoObject(object);
}
}
@Override
protected boolean executeImmediately() {
return immediate;
}
@Override
protected Boolean decideOnApproval(String executionId, org.activiti.engine.task.Task task) throws Exception {
login(getUser(userLead1Oid));
return approve;
}
}, 1);
}
public <T extends ObjectType> void modifyObject(final String TEST_NAME, ObjectDelta<T> objectDelta,
ObjectDelta<T> expectedDelta0, ObjectDelta<T> expectedDelta1,
boolean immediate, boolean approve,
String assigneeOid,
List<ExpectedTask> expectedTasks, List<ExpectedWorkItem> expectedWorkItems,
Runnable assertDelta0Executed,
Runnable assertDelta1NotExecuted, Runnable assertDelta1Executed) throws Exception {
executeTest(TEST_NAME, new TestDetails() {
@Override
protected LensContext createModelContext(OperationResult result) throws Exception {
Class<T> clazz = objectDelta.getObjectTypeClass();
//PrismObject<T> object = getObject(clazz, objectDelta.getOid());
LensContext<T> lensContext = createLensContext(clazz);
addFocusDeltaToContext(lensContext, objectDelta);
return lensContext;
}
@Override
protected void afterFirstClockworkRun(Task rootTask, List<Task> subtasks, List<WorkItemType> workItems, OperationResult result) throws Exception {
if (immediate) {
assertFalse("There is model context in the root task (it should not be there)",
wfTaskUtil.hasModelContext(rootTask));
} else {
ModelContext taskModelContext = wfTaskUtil.getModelContext(rootTask, result);
ObjectDelta realDelta0 = taskModelContext.getFocusContext().getPrimaryDelta();
assertDeltasEqual("Wrong delta left as primary focus delta.", expectedDelta0, realDelta0);
assertDelta1NotExecuted.run();
assertWfContextAfterClockworkRun(rootTask, subtasks, workItems, result,
objectDelta.getOid(), expectedTasks, expectedWorkItems);
}
}
@Override
protected void afterTask0Finishes(Task task, OperationResult result) throws Exception {
assertDelta0Executed.run();
assertDelta1NotExecuted.run();
}
@Override
protected void afterRootTaskFinishes(Task task, List<Task> subtasks, OperationResult result) throws Exception {
assertDelta0Executed.run();
if (approve) {
assertDelta1Executed.run();
} else {
assertDelta1NotExecuted.run();
}
}
@Override
protected boolean executeImmediately() {
return immediate;
}
@Override
protected Boolean decideOnApproval(String executionId, org.activiti.engine.task.Task task) throws Exception {
login(getUser(assigneeOid));
return approve;
}
}, 1);
}
public <T extends ObjectType> void deleteObject(final String TEST_NAME, Class<T> clazz, String objectOid,
boolean immediate, boolean approve,
String assigneeOid,
List<ExpectedTask> expectedTasks, List<ExpectedWorkItem> expectedWorkItems) throws Exception {
executeTest(TEST_NAME, new TestDetails() {
@Override
protected LensContext createModelContext(OperationResult result) throws Exception {
LensContext<T> lensContext = createLensContext(clazz);
ObjectDelta<T> deleteDelta = ObjectDelta.createDeleteDelta(clazz, objectOid, prismContext);
addFocusDeltaToContext(lensContext, deleteDelta);
return lensContext;
}
@Override
protected void afterFirstClockworkRun(Task rootTask, List<Task> subtasks, List<WorkItemType> workItems, OperationResult result) throws Exception {
if (immediate) {
assertFalse("There is model context in the root task (it should not be there)",
wfTaskUtil.hasModelContext(rootTask));
} else {
ModelContext taskModelContext = wfTaskUtil.getModelContext(rootTask, result);
ObjectDelta realDelta0 = taskModelContext.getFocusContext().getPrimaryDelta();
assertTrue("Delta0 is not empty: " + realDelta0.debugDump(), realDelta0.isEmpty());
assertWfContextAfterClockworkRun(rootTask, subtasks, workItems, result,
objectOid, expectedTasks, expectedWorkItems);
}
}
@Override
protected void afterTask0Finishes(Task task, OperationResult result) throws Exception {
assertObjectExists(clazz, objectOid);
}
@Override
protected void afterRootTaskFinishes(Task task, List<Task> subtasks, OperationResult result) throws Exception {
if (approve) {
assertObjectDoesntExist(clazz, objectOid);
} else {
assertObjectExists(clazz, objectOid);
}
}
@Override
protected boolean executeImmediately() {
return immediate;
}
@Override
protected Boolean decideOnApproval(String executionId, org.activiti.engine.task.Task task) throws Exception {
return approve;
}
}, 1);
}
protected WorkItemType getWorkItem(Task task, OperationResult result)
throws SchemaException, SecurityViolationException, ConfigurationException, ObjectNotFoundException {
//Collection<SelectorOptions<GetOperationOptions>> options = GetOperationOptions.resolveItemsNamed(WorkItemType.F_TASK_REF);
SearchResultList<WorkItemType> itemsAll = modelService.searchContainers(WorkItemType.class, null, null, task, result);
if (itemsAll.size() != 1) {
System.out.println("Unexpected # of work items: " + itemsAll.size());
for (WorkItemType workItem : itemsAll) {
System.out.println(PrismUtil.serializeQuietly(prismContext, workItem));
}
}
assertEquals("Wrong # of total work items", 1, itemsAll.size());
return itemsAll.get(0);
}
protected SearchResultList<WorkItemType> getWorkItems(Task task, OperationResult result) throws Exception {
return modelService.searchContainers(WorkItemType.class, null, null, task, result);
}
protected void displayWorkItems(String title, List<WorkItemType> workItems) {
workItems.forEach(wi -> display(title, wi));
}
protected ObjectReferenceType ort(String oid) {
return ObjectTypeUtil.createObjectRef(oid, ObjectTypes.USER);
}
protected PrismReferenceValue prv(String oid) {
return ObjectTypeUtil.createObjectRef(oid, ObjectTypes.USER).asReferenceValue();
}
protected PrismReference ref(List<ObjectReferenceType> orts) {
PrismReference rv = new PrismReference(new QName("dummy"));
orts.forEach(ort -> rv.add(ort.asReferenceValue().clone()));
return rv;
}
protected PrismReference ref(ObjectReferenceType ort) {
return ref(Collections.singletonList(ort));
}
protected abstract class TestDetails {
protected LensContext createModelContext(OperationResult result) throws Exception {
return null;
}
protected void afterFirstClockworkRun(Task rootTask, List<Task> wfSubtasks, List<WorkItemType> workItems,
OperationResult result) throws Exception {
}
protected void afterTask0Finishes(Task task, OperationResult result) throws Exception {
}
protected void afterRootTaskFinishes(Task task, List<Task> subtasks, OperationResult result) throws Exception {
}
protected boolean executeImmediately() {
return false;
}
protected Boolean decideOnApproval(String executionId, org.activiti.engine.task.Task task) throws Exception {
return true;
}
public List<ApprovalInstruction> getApprovalSequence() {
return null;
}
}
protected <F extends FocusType> void executeTest(String testName, TestDetails testDetails, int expectedSubTaskCount)
throws Exception {
// GIVEN
prepareNotifications();
dummyAuditService.clear();
Task modelTask = taskManager.createTaskInstance(AbstractWfTestPolicy.class.getName() + "." + testName);
modelTask.setOwner(userAdministrator);
OperationResult result = new OperationResult("execution");
LensContext<F> modelContext = testDetails.createModelContext(result);
display("Model context at test start", modelContext);
// this has problems with deleting assignments by ID
//assertFocusModificationSanity(modelContext);
// WHEN
HookOperationMode mode = clockwork.run(modelContext, modelTask, result);
// THEN
display("Model context after first clockwork.run", modelContext);
assertEquals("Unexpected state of the context", ModelState.PRIMARY, modelContext.getState());
assertEquals("Wrong mode after clockwork.run in " + modelContext.getState(), HookOperationMode.BACKGROUND, mode);
modelTask.refresh(result);
display("Model task after first clockwork.run", modelTask);
String rootTaskOid = wfTaskUtil.getRootTaskOid(modelTask);
assertNotNull("Root task OID is not set in model task", rootTaskOid);
Task rootTask = taskManager.getTask(rootTaskOid, result);
assertTrue("Root task is not persistent", rootTask.isPersistent());
UriStack uriStack = rootTask.getOtherHandlersUriStack();
if (!testDetails.executeImmediately()) {
assertEquals("Invalid handler at stack position 0", ModelOperationTaskHandler.MODEL_OPERATION_TASK_URI,
uriStack.getUriStackEntry().get(0).getHandlerUri());
} else {
assertTrue("There should be no handlers for root tasks with immediate execution mode",
uriStack == null || uriStack.getUriStackEntry().isEmpty());
}
ModelContext rootModelContext = testDetails.executeImmediately() ? null : wfTaskUtil.getModelContext(rootTask, result);
if (!testDetails.executeImmediately()) {
assertNotNull("Model context is not present in root task", rootModelContext);
} else {
assertNull("Model context is present in root task (execution mode = immediate)", rootModelContext);
}
List<Task> subtasks = rootTask.listSubtasks(result);
Task task0 = findAndRemoveTask0(subtasks, testDetails);
assertEquals("Incorrect number of subtasks", expectedSubTaskCount, subtasks.size());
final Collection<SelectorOptions<GetOperationOptions>> options1 = resolveItemsNamed(
new ItemPath(T_PARENT, F_OBJECT_REF),
new ItemPath(T_PARENT, F_TARGET_REF),
F_ASSIGNEE_REF,
F_ORIGINAL_ASSIGNEE_REF,
new ItemPath(T_PARENT, F_REQUESTER_REF));
List<WorkItemType> workItems = modelService.searchContainers(WorkItemType.class, null, options1, modelTask, result);
testDetails.afterFirstClockworkRun(rootTask, subtasks, workItems, result);
if (testDetails.executeImmediately()) {
if (task0 != null) {
waitForTaskClose(task0, 20000);
}
testDetails.afterTask0Finishes(rootTask, result);
}
for (int i = 0; i < subtasks.size(); i++) {
Task subtask = subtasks.get(i);
PrismProperty<ObjectTreeDeltasType> deltas = subtask.getTaskPrismObject()
.findProperty(new ItemPath(F_WORKFLOW_CONTEXT, F_PROCESSOR_SPECIFIC_STATE, F_DELTAS_TO_PROCESS));
assertNotNull("There are no modifications in subtask #" + i + ": " + subtask, deltas);
assertEquals("Incorrect number of modifications in subtask #" + i + ": " + subtask, 1, deltas.getRealValues().size());
// todo check correctness of the modification?
// now check the workflow state
String pid = wfTaskUtil.getProcessId(subtask);
assertNotNull("Workflow process instance id not present in subtask " + subtask, pid);
List<org.activiti.engine.task.Task> tasks = activitiEngine.getTaskService().createTaskQuery().processInstanceId(pid).list();
assertFalse("activiti task not found", tasks.isEmpty());
for (org.activiti.engine.task.Task task : tasks) {
String executionId = task.getExecutionId();
display("Execution id = " + executionId);
Boolean approve = testDetails.decideOnApproval(executionId, task);
if (approve != null) {
workflowManager.completeWorkItem(task.getId(), approve, null, null, null, result);
login(userAdministrator);
break;
}
}
}
// alternative way of approvals executions
if (CollectionUtils.isNotEmpty(testDetails.getApprovalSequence())) {
List<ApprovalInstruction> instructions = new ArrayList<>(testDetails.getApprovalSequence());
while (!instructions.isEmpty()) {
List<WorkItemType> currentWorkItems = modelService
.searchContainers(WorkItemType.class, null, options1, modelTask, result);
boolean matched = false;
main:
for (ApprovalInstruction approvalInstruction : instructions) {
for (WorkItemType workItem : currentWorkItems) {
if (approvalInstruction.matches(workItem)) {
if (approvalInstruction.beforeApproval != null) {
approvalInstruction.beforeApproval.run();
}
login(getUserFromRepo(approvalInstruction.approverOid));
workflowManager.completeWorkItem(workItem.getExternalId(), approvalInstruction.approval, null,
null, null, result);
if (approvalInstruction.afterApproval != null) {
approvalInstruction.afterApproval.run();
}
login(userAdministrator);
matched = true;
instructions.remove(approvalInstruction);
break main;
}
}
}
if (!matched) {
fail("None of approval instructions " + instructions + " matched any of current work items: "
+ currentWorkItems);
}
}
}
waitForTaskClose(rootTask, 60000);
subtasks = rootTask.listSubtasks(result);
findAndRemoveTask0(subtasks, testDetails);
testDetails.afterRootTaskFinishes(rootTask, subtasks, result);
notificationManager.setDisabled(true);
// Check audit
display("Audit", dummyAuditService);
display("Output context", modelContext);
}
private Task findAndRemoveTask0(List<Task> subtasks, TestDetails testDetails) {
Task task0 = null;
for (Task subtask : subtasks) {
if (subtask.getTaskPrismObject().asObjectable().getWorkflowContext() == null
|| subtask.getTaskPrismObject().asObjectable().getWorkflowContext().getProcessInstanceId() == null) {
assertNull("More than one non-wf-monitoring subtask", task0);
task0 = subtask;
}
}
if (testDetails.executeImmediately()) {
if (task0 != null) {
subtasks.remove(task0);
}
} else {
assertNull("Subtask for immediate execution was found even if it shouldn't be there", task0);
}
return task0;
}
protected void assertObjectInTaskTree(Task rootTask, String oid, boolean checkObjectOnSubtasks, OperationResult result)
throws SchemaException {
assertObjectInTask(rootTask, oid);
if (checkObjectOnSubtasks) {
for (Task task : rootTask.listSubtasks(result)) {
assertObjectInTask(task, oid);
}
}
}
protected void assertObjectInTask(Task task, String oid) {
assertEquals("Missing or wrong object OID in task " + task, oid, task.getObjectOid());
}
protected void waitForTaskClose(final Task task, final int timeout) throws Exception {
final OperationResult waitResult = new OperationResult(AbstractIntegrationTest.class + ".waitForTaskClose");
Checker checker = new Checker() {
@Override
public boolean check() throws CommonException {
task.refresh(waitResult);
OperationResult result = task.getResult();
if (verbose)
display("Check result", result);
return task.getExecutionStatus() == TaskExecutionStatus.CLOSED;
}
@Override
public void timeout() {
try {
task.refresh(waitResult);
} catch (Throwable e) {
display("Exception during task refresh", e);
}
OperationResult result = task.getResult();
display("Result of timed-out task", result);
assert false : "Timeout (" + timeout + ") while waiting for " + task + " to finish. Last result " + result;
}
};
IntegrationTestTools.waitFor("Waiting for " + task + " finish", checker, timeout, 1000);
}
protected void assertWfContextAfterClockworkRun(Task rootTask, List<Task> subtasks, List<WorkItemType> workItems,
OperationResult result,
String objectOid,
List<ExpectedTask> expectedTasks,
List<ExpectedWorkItem> expectedWorkItems) throws Exception {
final Collection<SelectorOptions<GetOperationOptions>> options =
SelectorOptions.createCollection(new ItemPath(F_WORKFLOW_CONTEXT, F_WORK_ITEM), createRetrieve());
Task opTask = taskManager.createTaskInstance();
TaskType rootTaskType = modelService.getObject(TaskType.class, rootTask.getOid(), options, opTask, result).asObjectable();
display("rootTask", rootTaskType);
assertTrue("unexpected process instance id in root task",
rootTaskType.getWorkflowContext() == null || rootTaskType.getWorkflowContext().getProcessInstanceId() == null);
assertEquals("Wrong # of wf subtasks (" + expectedTasks + ")", expectedTasks.size(), subtasks.size());
int i = 0;
for (Task subtask : subtasks) {
TaskType subtaskType = modelService.getObject(TaskType.class, subtask.getOid(), options, opTask, result).asObjectable();
display("Subtask #" + (i + 1) + ": ", subtaskType);
checkTask(subtaskType, subtask.toString(), expectedTasks.get(i));
WfTestUtil
.assertRef("requester ref", subtaskType.getWorkflowContext().getRequesterRef(), USER_ADMINISTRATOR_OID, false, false);
i++;
}
assertEquals("Wrong # of work items", expectedWorkItems.size(), workItems.size());
i = 0;
for (WorkItemType workItem : workItems) {
display("Work item #" + (i + 1) + ": ", workItem);
display("Task", WfContextUtil.getTask(workItem));
if (objectOid != null) {
WfTestUtil.assertRef("object reference", WfContextUtil.getObjectRef(workItem), objectOid, true, true);
}
String targetOid = expectedWorkItems.get(i).targetOid;
if (targetOid != null) {
WfTestUtil.assertRef("target reference", WfContextUtil.getTargetRef(workItem), targetOid, true, true);
}
WfTestUtil
.assertRef("assignee reference", workItem.getOriginalAssigneeRef(), expectedWorkItems.get(i).assigneeOid, false, true);
// name is not known, as it is not stored in activiti (only OID is)
//WfTestUtil.assertRef("task reference", workItem.getTaskRef(), null, false, true);
final TaskType subtaskType = WfContextUtil.getTask(workItem);
checkTask(subtaskType, "task in workItem", expectedWorkItems.get(i).task);
WfTestUtil
.assertRef("requester ref", subtaskType.getWorkflowContext().getRequesterRef(), USER_ADMINISTRATOR_OID, false, true);
i++;
}
}
private void checkTask(TaskType subtaskType, String subtaskName, ExpectedTask expectedTask) {
assertNull("Unexpected fetch result in wf subtask: " + subtaskName, subtaskType.getFetchResult());
WfContextType wfc = subtaskType.getWorkflowContext();
assertNotNull("Missing workflow context in wf subtask: " + subtaskName, wfc);
assertNotNull("No process ID in wf subtask: " + subtaskName, wfc.getProcessInstanceId());
assertEquals("Wrong process ID name in subtask: " + subtaskName, expectedTask.processName, wfc.getProcessInstanceName());
if (expectedTask.targetOid != null) {
assertEquals("Wrong target OID in subtask: " + subtaskName, expectedTask.targetOid, wfc.getTargetRef().getOid());
} else {
assertNull("TargetRef in subtask: " + subtaskName + " present even if it shouldn't", wfc.getTargetRef());
}
assertNotNull("Missing process start time in subtask: " + subtaskName, wfc.getStartTimestamp());
assertNull("Unexpected process end time in subtask: " + subtaskName, wfc.getEndTimestamp());
assertEquals("Wrong outcome", null, wfc.getOutcome());
//assertEquals("Wrong state", null, wfc.getState());
}
protected String getTargetOid(String executionId)
throws ConfigurationException, ObjectNotFoundException, SchemaException, CommunicationException,
SecurityViolationException {
LightweightObjectRef targetRef = (LightweightObjectRef) activitiEngine.getRuntimeService()
.getVariable(executionId, CommonProcessVariableNames.VARIABLE_TARGET_REF);
assertNotNull("targetRef not found", targetRef);
String roleOid = targetRef.getOid();
assertNotNull("requested role OID not found", roleOid);
return roleOid;
}
protected void checkTargetOid(String executionId, String expectedOid)
throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException,
SecurityViolationException {
String realOid = getTargetOid(executionId);
assertEquals("Unexpected target OID", expectedOid, realOid);
}
protected abstract class TestDetails2<F extends FocusType> {
protected PrismObject<F> getFocus(OperationResult result) throws Exception { return null; }
protected ObjectDelta<F> getFocusDelta() throws Exception { return null; }
protected int getNumberOfDeltasToApprove() { return 0; }
protected List<Boolean> getApprovals() { return null; }
protected List<ObjectDelta<F>> getExpectedDeltasToApprove() {
return null;
}
protected ObjectDelta<F> getExpectedDelta0() {
return null;
}
protected String getObjectOid() {
return null;
}
protected List<ExpectedTask> getExpectedTasks() { return null; }
protected List<ExpectedWorkItem> getExpectedWorkItems() { return null; }
protected void assertDeltaExecuted(int number, boolean yes, Task rootTask, OperationResult result) throws Exception { }
protected Boolean decideOnApproval(String executionId, org.activiti.engine.task.Task task) throws Exception { return true; }
protected void sortSubtasks(List<Task> subtasks) {
Collections.sort(subtasks, Comparator.comparing(this::getCompareKey));
}
protected void sortWorkItems(List<WorkItemType> workItems) {
Collections.sort(workItems, Comparator.comparing(this::getCompareKey));
}
protected String getCompareKey(Task task) {
return task.getTaskPrismObject().asObjectable().getWorkflowContext().getTargetRef().getOid();
}
protected String getCompareKey(WorkItemType workItem) {
return workItem.getOriginalAssigneeRef().getOid();
}
public List<ApprovalInstruction> getApprovalSequence() {
return null;
}
protected void afterFirstClockworkRun(Task rootTask, List<Task> subtasks, List<WorkItemType> workItems,
OperationResult result) throws Exception { }
}
protected <F extends FocusType> void executeTest2(String testName, TestDetails2<F> testDetails2, int expectedSubTaskCount,
boolean immediate) throws Exception {
executeTest(testName, new TestDetails() {
@Override
protected LensContext<F> createModelContext(OperationResult result) throws Exception {
PrismObject<F> focus = testDetails2.getFocus(result);
// TODO "object create" context
LensContext<F> lensContext = createLensContext(focus.getCompileTimeClass());
fillContextWithFocus(lensContext, focus);
addFocusDeltaToContext(lensContext, testDetails2.getFocusDelta());
if (immediate) {
lensContext.setOptions(ModelExecuteOptions.createExecuteImmediatelyAfterApproval());
}
return lensContext;
}
@Override
protected void afterFirstClockworkRun(Task rootTask, List<Task> subtasks, List<WorkItemType> workItems,
OperationResult result) throws Exception {
if (immediate) {
assertFalse("There is model context in the root task (it should not be there)",
wfTaskUtil.hasModelContext(rootTask));
} else {
ModelContext taskModelContext = wfTaskUtil.getModelContext(rootTask, result);
ObjectDelta expectedDelta0 = testDetails2.getExpectedDelta0();
ObjectDelta realDelta0 = taskModelContext.getFocusContext().getPrimaryDelta();
assertDeltasEqual("Wrong delta left as primary focus delta. ", expectedDelta0, realDelta0);
for (int i = 0; i <= testDetails2.getNumberOfDeltasToApprove(); i++) {
testDetails2.assertDeltaExecuted(i, false, rootTask, result);
}
testDetails2.sortSubtasks(subtasks);
testDetails2.sortWorkItems(workItems);
assertWfContextAfterClockworkRun(rootTask, subtasks, workItems, result,
testDetails2.getObjectOid(),
testDetails2.getExpectedTasks(), testDetails2.getExpectedWorkItems());
}
testDetails2.afterFirstClockworkRun(rootTask, subtasks, workItems, result);
}
@Override
protected void afterTask0Finishes(Task task, OperationResult result) throws Exception {
if (!immediate) {
return;
}
for (int i = 1; i <= testDetails2.getNumberOfDeltasToApprove(); i++) {
testDetails2.assertDeltaExecuted(i, false, task, result);
}
testDetails2.assertDeltaExecuted(0, true, task, result);
}
@Override
protected void afterRootTaskFinishes(Task task, List<Task> subtasks, OperationResult result) throws Exception {
for (int i = 0; i <= testDetails2.getNumberOfDeltasToApprove(); i++) {
testDetails2.assertDeltaExecuted(i, i == 0 || testDetails2.getApprovals().get(i-1), task, result);
}
}
@Override
protected boolean executeImmediately() {
return immediate;
}
@Override
protected Boolean decideOnApproval(String executionId, org.activiti.engine.task.Task task) throws Exception {
return testDetails2.decideOnApproval(executionId, task);
}
@Override
public List<ApprovalInstruction> getApprovalSequence() {
return testDetails2.getApprovalSequence();
}
}, expectedSubTaskCount);
}
protected void assertDeltasEqual(String message, ObjectDelta expectedDelta, ObjectDelta realDelta) {
// removeOldValues(expectedDelta);
// removeOldValues(realDelta);
if (!expectedDelta.equivalent(realDelta)) {
fail(message + "\nExpected:\n" + expectedDelta.debugDump() + "\nReal:\n" + realDelta.debugDump());
}
}
// private void removeOldValues(ObjectDelta<?> delta) {
// if (delta.isModify()) {
// delta.getModifications().forEach(mod -> mod.setEstimatedOldValues(null));
// }
// }
protected void assertNoObject(ObjectType object) throws SchemaException, com.evolveum.midpoint.util.exception.ObjectNotFoundException, com.evolveum.midpoint.util.exception.SecurityViolationException, com.evolveum.midpoint.util.exception.CommunicationException, com.evolveum.midpoint.util.exception.ConfigurationException {
assertNull("Object was created but it shouldn't be",
searchObjectByName(object.getClass(), object.getName().getOrig()));
}
protected <T extends ObjectType> void assertObject(T object) throws SchemaException, com.evolveum.midpoint.util.exception.ObjectNotFoundException, com.evolveum.midpoint.util.exception.SecurityViolationException, com.evolveum.midpoint.util.exception.CommunicationException, com.evolveum.midpoint.util.exception.ConfigurationException {
PrismObject<T> objectFromRepo = searchObjectByName((Class<T>) object.getClass(), object.getName().getOrig());
assertNotNull("Object " + object + " was not created", object);
objectFromRepo.removeItem(new ItemPath(ObjectType.F_METADATA), Item.class);
objectFromRepo.removeItem(new ItemPath(ObjectType.F_OPERATION_EXECUTION), Item.class);
assertEquals("Object is different from the one that was expected", object, objectFromRepo.asObjectable());
}
protected void checkVisibleWorkItem(ExpectedWorkItem expectedWorkItem, int count, Task task, OperationResult result)
throws SchemaException, ObjectNotFoundException, ConfigurationException, SecurityViolationException {
S_AtomicFilterExit q = QueryUtils
.filterForAssignees(QueryBuilder.queryFor(WorkItemType.class, prismContext), SecurityUtil.getPrincipal(),
OtherPrivilegesLimitationType.F_APPROVAL_WORK_ITEMS);
List<WorkItemType> currentWorkItems = modelService.searchContainers(WorkItemType.class, q.build(), null, task, result);
long found = currentWorkItems.stream().filter(wi -> expectedWorkItem == null || expectedWorkItem.matches(wi)).count();
assertEquals("Wrong # of matching work items", count, found);
}
}