/* * Copyright (c) 2010-2017 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.legacy; 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.AbstractInternalModelIntegrationTest; 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.model.impl.util.Utils; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismProperty; import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.delta.builder.DeltaBuilder; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.util.PrismTestUtil; import com.evolveum.midpoint.schema.DeltaConvertor; import com.evolveum.midpoint.schema.result.OperationResult; 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.xml.ns._public.common.api_types_3.ObjectModificationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; import org.apache.commons.lang.BooleanUtils; 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 org.testng.annotations.*; import javax.xml.bind.JAXBException; import java.io.File; import java.io.IOException; import java.util.*; 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.F_PROCESSOR_SPECIFIC_STATE; import static com.evolveum.midpoint.xml.ns._public.common.common_3.WfPrimaryChangeProcessorStateType.F_DELTAS_TO_PROCESS; import static org.testng.AssertJUnit.*; /** * @author semancik * */ @ContextConfiguration(locations = {"classpath:ctx-workflow-test-main.xml"}) @DirtiesContext(classMode = ClassMode.AFTER_CLASS) //@DependsOn("workflowServiceImpl") public class AbstractWfTestLegacy extends AbstractInternalModelIntegrationTest { protected static final File TEST_RESOURCE_DIR = new File("src/test/resources/legacy"); protected static final String DONT_CHECK = "dont-check"; @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; public static final File USERS_AND_ROLES_FILE = new File(TEST_RESOURCE_DIR, "users-and-roles.xml"); public static final String ROLE_R1_OID = "00000001-d34d-b33f-f00d-000000000001"; public static final String ROLE_R2_OID = "00000001-d34d-b33f-f00d-000000000002"; public static final String ROLE_R3_OID = "00000001-d34d-b33f-f00d-000000000003"; public static final String ROLE_R4_OID = "00000001-d34d-b33f-f00d-000000000004"; public static final File USER_BILL_FILE = new File(TEST_RESOURCE_DIR, "user-bill.xml"); public static final String USER_BILL_OID = "c0c010c0-d34d-b33f-f00d-11111111111a"; public static final String ROLE_R10_OID = "00000001-d34d-b33f-f00d-000000000010"; public static final File ROLE_R11_FILE = new File(TEST_RESOURCE_DIR, "role11.xml"); public static final String ROLE_R11_OID = "00000001-d34d-b33f-f00d-000000000011"; public static final File ROLE_R12_FILE = new File(TEST_RESOURCE_DIR, "role12.xml"); public static final String ROLE_R12_OID = "00000001-d34d-b33f-f00d-000000000012"; public static final File ROLE_R13_FILE = new File(TEST_RESOURCE_DIR, "role13.xml"); public static final String ROLE_R13_OID = "00000001-d34d-b33f-f00d-000000000013"; public static final String R1BOSS_OID = "00000000-d34d-b33f-f00d-111111111111"; public static final String R2BOSS_OID = "00000000-d34d-b33f-f00d-111111111112"; public static final String R3BOSS_OID = "00000000-d34d-b33f-f00d-111111111113"; public static final String DUMMYBOSS_OID = "00000000-d34d-b33f-f00d-111111111333"; public static final String GROUP_TESTERS_OID = "20000000-0000-0000-3333-000000000002"; public static final File GROUP_TESTERS_FILE = new File(TEST_RESOURCE_DIR, "group-testers-dummy.xml"); public static final String GROUP_TESTERS_NAME = "testers"; public static final String GROUP_GUESTS_OID = "20000000-0000-0000-3333-000000000072"; public static final File GROUP_GUESTS_FILE = new File(TEST_RESOURCE_DIR, "/group-guests-dummy.xml"); public static final String GROUP_GUESTS_NAME = "guests"; public static final File USER_ELISABETH_FILE = new File(TEST_RESOURCE_DIR, "user-elisabeth.xml"); public static final String USER_ELISABETH_OID = "c0c010c0-d34d-b33f-f00d-111111112222"; public static final File ACCOUNT_SHADOW_ELISABETH_DUMMY_FILE = new File(TEST_RESOURCE_DIR, "account-shadow-elisabeth-dummy.xml"); public AbstractWfTestLegacy() throws JAXBException { super(); } protected boolean enablePolicyRuleBasedAspect; @Override public void initSystem(Task initTask, OperationResult initResult) throws Exception { super.initSystem(initTask, initResult); importObjectFromFile(USERS_AND_ROLES_FILE, initResult); importObjectFromFile(ROLE_R11_FILE, initResult); importObjectFromFile(ROLE_R12_FILE, initResult); importObjectFromFile(ROLE_R13_FILE, initResult); // add dummyboss as approver for Dummy Resource ResourceBusinessConfigurationType businessConfigurationType = new ResourceBusinessConfigurationType(prismContext); ObjectReferenceType dummyApproverRef = new ObjectReferenceType(); dummyApproverRef.setType(UserType.COMPLEX_TYPE); dummyApproverRef.setOid(DUMMYBOSS_OID); businessConfigurationType.getApproverRef().add(dummyApproverRef); ObjectDelta objectDelta = ObjectDelta.createModificationAddContainer(ResourceType.class, RESOURCE_DUMMY_OID, new ItemPath(ResourceType.F_BUSINESS), prismContext, businessConfigurationType); repositoryService.modifyObject(ResourceType.class, RESOURCE_DUMMY_OID, objectDelta.getModifications(), initResult); // check Role2 approver OID (it is filled-in using search filter) List<PrismObject<RoleType>> roles = findRoleInRepoUnchecked("Role2", initResult); assertEquals("Wrong number of Role2 objects found in repo", 1, roles.size()); RoleType role2 = roles.get(0).asObjectable(); // could be done also like this // RoleType role2 = repositoryService.getObject(RoleType.class, ROLE_R2_OID, null, initResult).asObjectable(); ObjectReferenceType approver = role2.getApprovalSchema().getStage().get(0).getApproverRef().get(0); assertEquals("Wrong OID of Role2's approver", R2BOSS_OID, approver.getOid()); importObjectFromFile(GROUP_TESTERS_FILE, initResult); importObjectFromFile(GROUP_GUESTS_FILE, initResult); getDummyResourceController().addGroup(GROUP_TESTERS_NAME); getDummyResourceController().addGroup(GROUP_GUESTS_NAME); display("setting policyRuleBasedAspect.enabled to", enablePolicyRuleBasedAspect); List<ItemDelta<?, ?>> deltas = DeltaBuilder.deltaFor(SystemConfigurationType.class, prismContext) .item(SystemConfigurationType.F_WORKFLOW_CONFIGURATION, WfConfigurationType.F_PRIMARY_CHANGE_PROCESSOR, PrimaryChangeProcessorConfigurationType.F_POLICY_RULE_BASED_ASPECT, PcpAspectConfigurationType.F_ENABLED) .replace(enablePolicyRuleBasedAspect) .asItemDeltas(); repositoryService.modifyObject(SystemConfigurationType.class, SYSTEM_CONFIGURATION_OID, deltas, initResult); display("policyRuleBasedAspect.enabled was set to", enablePolicyRuleBasedAspect); systemObjectCache.invalidateCaches(); } @BeforeClass @Parameters({ "enablePolicyRuleBasedAspect" }) public void temp(@org.testng.annotations.Optional Boolean enablePolicyRuleBasedAspect) { this.enablePolicyRuleBasedAspect = BooleanUtils.isNotFalse(enablePolicyRuleBasedAspect); System.out.println("Testing with policy rule based aspect = " + this.enablePolicyRuleBasedAspect); } protected Map<String, WorkflowResult> createResultMap(String oid, WorkflowResult result) { Map<String,WorkflowResult> retval = new HashMap<String,WorkflowResult>(); 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); } 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); LOGGER.info("Removed assignment " + at + " from " + user); } } protected abstract class TestDetails { abstract int subtaskCount(); abstract boolean immediate(); abstract boolean checkObjectOnSubtasks(); boolean approvedAutomatically() { return false; } LensContext createModelContext(OperationResult result) throws Exception { return null; } void assertsAfterClockworkRun(Task rootTask, List<Task> wfSubtasks, OperationResult result) throws Exception { } void assertsAfterImmediateExecutionFinished(Task task, OperationResult result) throws Exception { } void assertsRootTaskFinishes(Task task, List<Task> subtasks, OperationResult result) throws Exception { } boolean decideOnApproval(String executionId) throws Exception { return true; } String getObjectOid(Task task, OperationResult result) throws SchemaException { return null; }; boolean removeAssignmentsBeforeTest() { return true; } } protected boolean decideOnRoleApproval(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); if (ROLE_R1_OID.equals(roleOid)) { login(getUser(R1BOSS_OID)); return true; } else if (ROLE_R2_OID.equals(roleOid)) { login(getUser(R2BOSS_OID)); return false; } else if (ROLE_R3_OID.equals(roleOid)) { login(getUser(R3BOSS_OID)); return true; } else { throw new AssertionError("Unknown role OID in assignment to be approved: " + roleOid); } } protected void executeTest(String testName, String oid, TestDetails testDetails) throws Exception { // GIVEN prepareNotifications(); dummyAuditService.clear(); Task modelTask = taskManager.createTaskInstance(AbstractWfTestLegacy.class.getName() + "."+testName); OperationResult result = new OperationResult("execution"); modelTask.setOwner(repositoryService.getObject(UserType.class, USER_ADMINISTRATOR_OID, null, result)); if (oid != null && testDetails.removeAssignmentsBeforeTest()) { removeAllAssignments(oid, result); } LensContext<UserType> context = (LensContext<UserType>) testDetails.createModelContext(result); display("Input context", context); assertFocusModificationSanity(context); // WHEN HookOperationMode mode = clockwork.run(context, modelTask, result); // THEN assertEquals("Unexpected state of the context", ModelState.PRIMARY, context.getState()); assertEquals("Wrong mode after clockwork.run in " + context.getState(), HookOperationMode.BACKGROUND, mode); modelTask.refresh(result); 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()); // trivial ;) if (!testDetails.approvedAutomatically()) { UriStack uriStack = rootTask.getOtherHandlersUriStack(); if (!testDetails.immediate()) { 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 taskModelContext = testDetails.immediate() ? null : wfTaskUtil.getModelContext(rootTask, result); if (!testDetails.immediate()) { assertNotNull("Model context is not present in root task", taskModelContext); } else { assertFalse("Model context is present in root task (execution mode = immediate)", wfTaskUtil.hasModelContext(rootTask)); } List<Task> subtasks = rootTask.listSubtasks(result); assertEquals("Incorrect number of subtasks", testDetails.subtaskCount(), subtasks.size()); Task task0 = extractTask0(subtasks, testDetails); testDetails.assertsAfterClockworkRun(rootTask, subtasks, result); // ZZZ TEMPORARY // checkDummyTransportMessages("simpleWorkflowNotifier-Processes", workflowSubtaskCount); // checkDummyTransportMessages("simpleWorkflowNotifier-WorkItems", workflowSubtaskCount); if (testDetails.immediate()) { waitForTaskClose(task0, 20000); //TestUtil.assertSuccess(task0.getResult()); // todo enable this testDetails.assertsAfterImmediateExecutionFinished(rootTask, result); } for (int i = 0; i < subtasks.size(); i++) { Task subtask = subtasks.get(i); //assertEquals("Subtask #" + i + " is not recurring: " + subtask, TaskRecurrence.RECURRING, subtask.getRecurrenceStatus()); //assertEquals("Incorrect execution status of subtask #" + i + ": " + subtask, TaskExecutionStatus.RUNNABLE, subtask.getExecutionStatus()); 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); // WfProcessInstanceType processInstance = workflowServiceImpl.getProcessInstanceById(pid, false, true, result); // assertNotNull("Process instance information cannot be retrieved", processInstance); // assertEquals("Incorrect number of work items", 1, processInstance.getWorkItems().size()); //String taskId = processInstance.getWorkItems().get(0).getWorkItemId(); //WorkItemDetailed workItemDetailed = wfDataAccessor.getWorkItemDetailsById(taskId, result); org.activiti.engine.task.Task t = activitiEngine.getTaskService().createTaskQuery().processInstanceId(pid).singleResult(); assertNotNull("activiti task not found", t); String executionId = t.getExecutionId(); LOGGER.trace("Execution id = {}", executionId); boolean approve = testDetails.decideOnApproval(executionId); workflowManager.completeWorkItem(t.getId(), approve, null, null, null, result); login(userAdministrator); } } waitForTaskClose(rootTask, 60000); List<Task> subtasks = rootTask.listSubtasks(result); extractTask0(subtasks, testDetails); //TestUtil.assertSuccess(rootTask.getResult()); testDetails.assertsRootTaskFinishes(rootTask, subtasks, result); if (oid == null) { oid = testDetails.getObjectOid(rootTask, result); } assertNotNull("object oid is null after operation", oid); if (!oid.equals(DONT_CHECK)) { assertObjectInTaskTree(rootTask, oid, testDetails.checkObjectOnSubtasks(), result); } if (!testDetails.approvedAutomatically()) { // ZZZ temporarily disabled // checkDummyTransportMessages("simpleWorkflowNotifier-Processes", workflowSubtaskCount * 2); // checkDummyTransportMessages("simpleWorkflowNotifier-WorkItems", workflowSubtaskCount * 2); } notificationManager.setDisabled(true); // Check audit display("Audit", dummyAuditService); display("Output context", context); } private Task extractTask0(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.immediate()) { assertNotNull("Subtask for immediate execution was not found", task0); subtasks.remove(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 (ObjectNotFoundException e) { LOGGER.error("Exception during task refresh: {}", e,e); } catch (SchemaException e) { LOGGER.error("Exception during task refresh: {}", e,e); } OperationResult result = task.getResult(); LOGGER.debug("Result of timed-out task:\n{}", result.debugDump()); assert false : "Timeout ("+timeout+") while waiting for "+task+" to finish. Last result "+result; } }; IntegrationTestTools.waitFor("Waiting for "+task+" finish", checker, timeout, 1000); } protected void deleteUserFromModel(String name) throws SchemaException, ObjectNotFoundException, CommunicationException, ObjectAlreadyExistsException, PolicyViolationException, SecurityViolationException, ConfigurationException, ExpressionEvaluationException { OperationResult result = new OperationResult("dummy"); Task t = taskManager.createTaskInstance(); t.setOwner(repositoryService.getObject(UserType.class, USER_ADMINISTRATOR_OID, null, result)); if (!findUserInRepoUnchecked(name, result).isEmpty()) { PrismObject<UserType> user = findUserInRepo(name, result); Collection<ObjectDelta<? extends ObjectType>> deltas = new ArrayList<ObjectDelta<? extends ObjectType>>(); deltas.add(ObjectDelta.createDeleteDelta(UserType.class, user.getOid(), prismContext)); modelService.executeChanges(deltas, new ModelExecuteOptions(), t, result); LOGGER.info("User " + name + " was deleted"); } else { LOGGER.info("User {} was not found", name); } } <O extends ObjectType> ObjectDelta<O> encryptAndAddFocusModificationToContext( LensContext<O> context, File file) throws JAXBException, SchemaException, IOException { ObjectModificationType modElement = PrismTestUtil.parseAtomicValue( file, ObjectModificationType.COMPLEX_TYPE); ObjectDelta<O> focusDelta = DeltaConvertor.createObjectDelta( modElement, context.getFocusClass(), prismContext); Utils.encrypt((Collection) Arrays.asList(focusDelta), protector, null, new OperationResult("dummy")); return addFocusDeltaToContext(context, focusDelta); } }