diff --git a/modules/flowable-assertj/pom.xml b/modules/flowable-assertj/pom.xml new file mode 100644 index 00000000000..5fb3762fa71 --- /dev/null +++ b/modules/flowable-assertj/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + org.flowable + flowable-root + ../.. + 7.1.0-SNAPSHOT + + + flowable-assertj + + + + org.assertj + assertj-core + compile + + + org.flowable + flowable-engine + provided + + + com.zaxxer + HikariCP + test + + + com.h2database + h2 + test + + + org.junit.jupiter + junit-jupiter + test + + + \ No newline at end of file diff --git a/modules/flowable-assertj/src/main/java/org/flowable/assertj/process/FlowableProcessAssertions.java b/modules/flowable-assertj/src/main/java/org/flowable/assertj/process/FlowableProcessAssertions.java new file mode 100644 index 00000000000..866fe74a99f --- /dev/null +++ b/modules/flowable-assertj/src/main/java/org/flowable/assertj/process/FlowableProcessAssertions.java @@ -0,0 +1,32 @@ +/* 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 org.flowable.assertj.process; + +import org.assertj.core.api.Assertions; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.runtime.ProcessInstance; + +/** + * @author martin.grofcik + */ +public class FlowableProcessAssertions extends Assertions { + + public static ProcessInstanceAssert assertThat(ProcessInstance processInstance) { + return new ProcessInstanceAssert(processInstance); + } + public static HistoricProcessInstanceAssert assertThat(HistoricProcessInstance historicProcessInstance) { + return new HistoricProcessInstanceAssert(historicProcessInstance); + } + +} diff --git a/modules/flowable-assertj/src/main/java/org/flowable/assertj/process/HistoricProcessInstanceAssert.java b/modules/flowable-assertj/src/main/java/org/flowable/assertj/process/HistoricProcessInstanceAssert.java new file mode 100644 index 00000000000..d59bcd5b376 --- /dev/null +++ b/modules/flowable-assertj/src/main/java/org/flowable/assertj/process/HistoricProcessInstanceAssert.java @@ -0,0 +1,160 @@ +/* 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 org.flowable.assertj.process; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.assertj.core.api.AbstractAssert; +import org.assertj.core.api.ListAssert; +import org.flowable.engine.ProcessEngine; +import org.flowable.engine.history.HistoricActivityInstance; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.identitylink.api.IdentityLink; +import org.flowable.identitylink.api.history.HistoricIdentityLink; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.flowable.variable.api.history.HistoricVariableInstance; + +/** + * @author martin.grofcik + */ +public class HistoricProcessInstanceAssert extends AbstractAssert { + + protected final ProcessServicesProvider processServicesProvider; + + protected HistoricProcessInstanceAssert(ProcessEngine processEngine, HistoricProcessInstance historicProcessInstance) { + super(historicProcessInstance, HistoricProcessInstanceAssert.class); + processServicesProvider = ProcessServicesProvider.of(processEngine); + } + + protected HistoricProcessInstanceAssert(HistoricProcessInstance historicProcessInstance) { + this(Utils.getProcessEngine(), historicProcessInstance); + } + + /** + * Assert historic activities ordered by activity instance start time. + * + * @return Assertion of {@link HistoricActivityInstance} list. + */ + public ListAssert activities() { + processExistsInHistory(); + + return assertThat(processServicesProvider.getHistoryService().createHistoricActivityInstanceQuery().processInstanceId(actual.getId()) + .orderByHistoricActivityInstanceStartTime().desc().list()); + } + + /** + * Assert historic process instance exists in the history and is finished. + * + * @return Historic process instance assertion. + */ + public HistoricProcessInstanceAssert isFinished() { + processExistsInHistory(); + + if (processServicesProvider.getHistoryService().createHistoricProcessInstanceQuery().finished().processInstanceId(actual.getId()).count() != 1) { + failWithMessage(Utils.getProcessDescription(actual) + " to be finished, but is running in history."); + } + + return this; + } + + public ListAssert variables() { + processExistsInHistory(); + + return assertThat(processServicesProvider.getHistoryService().createHistoricVariableInstanceQuery().processInstanceId(actual.getId()).orderByVariableName().asc().list()); + } + + /** + * Assert that process instance has variable in history. + * + * @param variableName variable to check. + * @return Historic process instance assertion + */ + + public HistoricProcessInstanceAssert hasVariable(String variableName) { + processExistsInHistory(); + + if (processServicesProvider.getHistoryService().createHistoricProcessInstanceQuery().processInstanceId(actual.getId()).variableExists(variableName).count() != 1) { + failWithMessage(Utils.getProcessDescription(actual) + " has variable <%s> but variable does not exist in history.", variableName); + } + + return this; + } + + /** + * Assert that process instance does not have variable in history. + * @param variableName variable to check + * @return Historic process instance assertion + */ + public HistoricProcessInstanceAssert doesNotHaveVariable(String variableName) { + processExistsInHistory(); + + if (processServicesProvider.getHistoryService().createHistoricProcessInstanceQuery().processInstanceId(actual.getId()).variableExists(variableName).count() != 0) { + failWithMessage(Utils.getProcessDescription(actual)+" does not have variable <%s> but variable exists in history.", variableName); + } + + return this; + } + + /** + * Assert that process instance has variable in history with value equals to expectedValue. + * + * @param variableName variable to check. + * @param expectedValue expected variable value. + * @return Historic process instance assertion + */ + public HistoricProcessInstanceAssert hasVariableWithValue(String variableName, Object expectedValue) { + processExistsInHistory(); + hasVariable(variableName); + + HistoricVariableInstance actualVariable = processServicesProvider.getHistoryService().createHistoricVariableInstanceQuery().processInstanceId(actual.getId()).variableName(variableName).singleResult(); + assertThat(actualVariable.getValue()).isEqualTo(expectedValue); + return this; + } + + /** + * Assert list of historic identity links without ordering. + * + * @return Assertion of #{@link IdentityLink} list. + */ + public ListAssert identityLinks() { + processExistsInHistory(); + + return assertThat(processServicesProvider.getHistoryService().getHistoricIdentityLinksForProcessInstance(actual.getId())); + } + + /** + * Assert list of user tasks in the history ordered by the task name ascending. + * Process, Task variables and identityLinks are included. + * + * @return Assertion of {@link HistoricTaskInstance} list. + */ + public ListAssert userTasks() { + processExistsInHistory(); + + return assertThat(processServicesProvider.getHistoryService().createHistoricTaskInstanceQuery().processInstanceId(actual.getId()).orderByTaskName().asc() + .includeProcessVariables().includeIdentityLinks().includeTaskLocalVariables().list()); + } + + private void processExistsInHistory() { + isNotNull(); + isInHistory(); + } + + private void isInHistory() { + if (processServicesProvider.getHistoryService().createHistoricProcessInstanceQuery().processInstanceId(actual.getId()).count() != 1) { + failWithMessage(Utils.getProcessDescription(actual)+"> exists in history but process instance not found."); + } + } + +} diff --git a/modules/flowable-assertj/src/main/java/org/flowable/assertj/process/ProcessInstanceAssert.java b/modules/flowable-assertj/src/main/java/org/flowable/assertj/process/ProcessInstanceAssert.java new file mode 100644 index 00000000000..b134eb071ba --- /dev/null +++ b/modules/flowable-assertj/src/main/java/org/flowable/assertj/process/ProcessInstanceAssert.java @@ -0,0 +1,195 @@ +/* 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 org.flowable.assertj.process; + +import org.assertj.core.api.AbstractAssert; +import org.assertj.core.api.Assertions; +import org.assertj.core.api.ListAssert; +import org.flowable.engine.ProcessEngine; +import org.flowable.engine.runtime.ActivityInstance; +import org.flowable.engine.runtime.Execution; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.eventsubscription.api.EventSubscription; +import org.flowable.identitylink.api.IdentityLink; +import org.flowable.task.api.Task; +import org.flowable.variable.api.persistence.entity.VariableInstance; + +import static org.flowable.assertj.process.FlowableProcessAssertions.assertThat; + +/** + * @author martin.grofcik + */ +public class ProcessInstanceAssert extends AbstractAssert { + protected final ProcessServicesProvider processServicesProvider; + + protected ProcessInstanceAssert(ProcessEngine processEngine, ProcessInstance processInstance) { + super(processInstance, ProcessInstanceAssert.class); + processServicesProvider = ProcessServicesProvider.of(processEngine); + } + + protected ProcessInstanceAssert(ProcessInstance processInstance) { + this(Utils.getProcessEngine(), processInstance); + } + + /** + * Assert that process instance exists in runtime. + * + * @return Process instance assert. + */ + public ProcessInstanceAssert isRunning() { + isNotNull(); + + if (processServicesProvider.getRuntimeService().createProcessInstanceQuery().processInstanceId(actual.getId()).count() < 1) { + failWithMessage(Utils.getProcessDescription(actual)+" to be running but is not.", actual.getId()); + } + return this; + } + + /** + * Assert that process instance has variable in runtime. + * + * @param variableName variable to check. + * @return Process instance assertion + */ + public ProcessInstanceAssert hasVariable(String variableName) { + isNotNull(); + + if (processServicesProvider.getRuntimeService().createProcessInstanceQuery().processInstanceId(actual.getId()).variableExists(variableName).count() != 1) { + failWithMessage(Utils.getProcessDescription(actual)+" has variable <%s> but variable does not exist.", variableName); + } + + return this; + } + + /** + * Assert that process instance has variable in runtime with value equals to expectedValue. + * + * @param variableName variable to check. + * @param expectedValue expected variable value. + * @return Process instance assertion + */ + public ProcessInstanceAssert hasVariableWithValue(String variableName, Object expectedValue) { + isNotNull(); + hasVariable(variableName); + + VariableInstance actualVariable = processServicesProvider.getRuntimeService().createVariableInstanceQuery().processInstanceId(actual.getId()).variableName(variableName).singleResult(); + Assertions.assertThat(actualVariable.getValue()).isEqualTo(expectedValue); + return this; + } + + /** + * Assert that process instance does not have variable in runtime. + * @param variableName variable to check + * @return Process instance assertion + */ + public ProcessInstanceAssert doesNotHaveVariable(String variableName) { + isNotNull(); + + if (processServicesProvider.getRuntimeService().createProcessInstanceQuery().processInstanceId(actual.getId()).variableExists(variableName).count() != 0) { + failWithMessage(Utils.getProcessDescription(actual)+" does not have variable <%s> but variable exists.", variableName); + } + + return this; + } + + /** + * Assert that process instance does not exist in runtime. + * + * @return Process instance assertion + */ + public ProcessInstanceAssert doesNotExist() { + isNotNull(); + + if (processServicesProvider.getRuntimeService().createProcessInstanceQuery().processInstanceId(actual.getId()).count() != 0) { + failWithMessage(Utils.getProcessDescription(actual)+" is finished but instance exists in runtime."); + } + + return this; + } + + /** + * @return Historic process instance assertion + */ + public HistoricProcessInstanceAssert inHistory() { + return assertThat(processServicesProvider.getHistoryService().createHistoricProcessInstanceQuery().processInstanceId(actual.getId()).singleResult()); + } + + /** + * Assert list of runtime process instance activities ordered by activity start time. + * + * @return Assertion of #{@link ActivityInstance} list. + */ + public ListAssert activities() { + isNotNull(); + + return assertThat(processServicesProvider.getRuntimeService().createActivityInstanceQuery().processInstanceId(actual.getId()).orderByActivityInstanceStartTime().asc().list()); + } + + /** + * Assert list of runtime execution instances without ordering. + * + * @return Assertion of #{@link Execution} list. + */ + public ListAssert executions() { + isNotNull(); + + return assertThat(processServicesProvider.getRuntimeService().createExecutionQuery().processInstanceId(actual.getId()).list()); + } + + /** + * Assert list of runtime variable instances ordered by variable name ascending. + * + * @return Assertion of #{@link VariableInstance} list. + */ + public ListAssert variables() { + isNotNull(); + + return assertThat(processServicesProvider.getRuntimeService().createVariableInstanceQuery().processInstanceId(actual.getId()).orderByVariableName().asc().list()); + } + + /** + * Assert list of runtime identity links without ordering. + * + * @return Assertion of #{@link IdentityLink} list. + */ + public ListAssert identityLinks() { + isNotNull(); + + return assertThat(processServicesProvider.getRuntimeService().getIdentityLinksForProcessInstance(actual.getId())); + } + + /** + * Assert list of user tasks in the runtime ordered by the task name ascending. + * + * @return Assertion of {@link Task} list. + */ + public ListAssert userTasks() { + isNotNull(); + + return assertThat(Utils.getTaskService().createTaskQuery().processInstanceId(actual.getId()).orderByTaskName().asc() + .includeProcessVariables().includeIdentityLinks().includeTaskLocalVariables().list()); + } + + /** + * Assert list of event subscriptions in the runtime ordered by the event name ascending. + * + * @return Assertion of {@link EventSubscription} list. + */ + + public ListAssert eventSubscription() { + isNotNull(); + + return assertThat(processServicesProvider.getRuntimeService().createEventSubscriptionQuery().processInstanceId(actual.getId()).orderByEventName().asc().list()); + } +} diff --git a/modules/flowable-assertj/src/main/java/org/flowable/assertj/process/ProcessServicesProvider.java b/modules/flowable-assertj/src/main/java/org/flowable/assertj/process/ProcessServicesProvider.java new file mode 100644 index 00000000000..5a13ed5ef87 --- /dev/null +++ b/modules/flowable-assertj/src/main/java/org/flowable/assertj/process/ProcessServicesProvider.java @@ -0,0 +1,41 @@ +/* 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 org.flowable.assertj.process; + +import org.flowable.engine.HistoryService; +import org.flowable.engine.ProcessEngine; +import org.flowable.engine.RuntimeService; + +/** + * @author martin.grofcik + */ +public class ProcessServicesProvider { + final ProcessEngine processEngine; + + private ProcessServicesProvider(ProcessEngine processEngine) { + this.processEngine = processEngine; + } + + static ProcessServicesProvider of(ProcessEngine processEngine) { + return new ProcessServicesProvider(processEngine); + } + + RuntimeService getRuntimeService() { + return processEngine.getRuntimeService(); + } + + HistoryService getHistoryService() { + return processEngine.getHistoryService(); + } +} diff --git a/modules/flowable-assertj/src/main/java/org/flowable/assertj/process/Utils.java b/modules/flowable-assertj/src/main/java/org/flowable/assertj/process/Utils.java new file mode 100644 index 00000000000..12da07c1e10 --- /dev/null +++ b/modules/flowable-assertj/src/main/java/org/flowable/assertj/process/Utils.java @@ -0,0 +1,45 @@ +/* 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 org.flowable.assertj.process; + +import org.flowable.engine.*; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.runtime.ProcessInstance; + +/** + * @author martin.grofcik + */ +public class Utils { + + protected static String getProcessDescription(ProcessInstance actual) { + return getProcessDescription(actual.getProcessDefinitionKey(), actual.getId()); + } + + protected static String getProcessDescription(HistoricProcessInstance actual) { + return getProcessDescription(actual.getProcessDefinitionKey(), actual.getId()); + } + + protected static String getProcessDescription(String processDefinitionKey, String id) { + return "Expected process instance <"+processDefinitionKey+", "+id+">"; + } + + protected static TaskService getTaskService() { + return getProcessEngine().getTaskService(); + } + + protected static ProcessEngine getProcessEngine() { + return ProcessEngines.getProcessEngines().get("default"); + } + +} diff --git a/modules/flowable-assertj/src/test/java/org/flowable/assertj/process/HistoricProcessInstanceAssertTest.java b/modules/flowable-assertj/src/test/java/org/flowable/assertj/process/HistoricProcessInstanceAssertTest.java new file mode 100644 index 00000000000..4db6d14735a --- /dev/null +++ b/modules/flowable-assertj/src/test/java/org/flowable/assertj/process/HistoricProcessInstanceAssertTest.java @@ -0,0 +1,127 @@ +/* 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 org.flowable.assertj.process; + +import org.assertj.core.groups.Tuple; +import org.flowable.engine.HistoryService; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.TaskService; +import org.flowable.engine.history.HistoricActivityInstance; +import org.flowable.engine.history.HistoricProcessInstance; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.engine.test.Deployment; +import org.flowable.engine.test.FlowableTest; +import org.flowable.task.api.Task; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * @author martin.grofcik + */ +@FlowableTest +class HistoricProcessInstanceAssertTest { + + @Test + @Deployment(resources = "oneTask.bpmn20.xml") + void isFinishedForFinishedProcessInstance(RuntimeService runtimeService, TaskService taskService, HistoryService historyService) { + ProcessInstance oneTaskProcess = TestUtils.createOneTaskProcess(runtimeService); + + ProcessInstanceAssert assertThatOneTaskProcess = FlowableProcessAssertions.assertThat(oneTaskProcess); + assertThatOneTaskProcess.inHistory().activities().extracting(HistoricActivityInstance::getActivityId).contains( + "theStart", "theStart-theTask", "theTask" + ); + + taskService.complete(taskService.createTaskQuery().processInstanceId(oneTaskProcess.getId()).singleResult().getId()); + + assertThatOneTaskProcess.inHistory().isFinished() + .activities().extracting(HistoricActivityInstance::getActivityId).contains( + "theStart", "theStart-theTask", "theTask", "theTask-theEnd", "theEnd" + ); + + HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(oneTaskProcess.getId()).singleResult(); + FlowableProcessAssertions.assertThat(historicProcessInstance).isFinished() + .activities().extracting(HistoricActivityInstance::getActivityId).contains( + "theStart", "theStart-theTask", "theTask", "theTask-theEnd", "theEnd" + ); + } + + @Test + @Deployment(resources = "oneTask.bpmn20.xml") + void variables(RuntimeService runtimeService, TaskService taskService) { + ProcessInstance oneTaskProcess = TestUtils.createOneTaskProcess(runtimeService); + + ProcessInstanceAssert assertThatOneTaskProcess = FlowableProcessAssertions.assertThat(oneTaskProcess); + assertThatOneTaskProcess.as("No variable exists in the process scope.") + .inHistory().variables().isEmpty(); + + runtimeService.setVariable(oneTaskProcess.getId(), "testVariable", "variableValue"); + + assertThatOneTaskProcess.as("Variable exists in the process scope, the variable must be present in the history.") + .inHistory() + .hasVariable("testVariable") + .hasVariableWithValue("testVariable", "variableValue") + .variables().hasSize(1).extracting("name", "value"). + containsExactly(Tuple.tuple("testVariable", "variableValue")); + + Task task = taskService.createTaskQuery().processInstanceId(oneTaskProcess.getId()).singleResult(); + taskService.complete(task.getId()); + + assertThatOneTaskProcess.as("Variable exists in the process scope, the variable must be present in the history.") + .doesNotExist() + .inHistory() + .isFinished() + .hasVariable("testVariable") + .hasVariableWithValue("testVariable", "variableValue") + .variables().hasSize(1).extracting("name", "value"). + containsExactly(Tuple.tuple("testVariable", "variableValue")); + } + + @Test + @Deployment(resources = "oneTask.bpmn20.xml") + void hasVariable(RuntimeService runtimeService) { + ProcessInstance oneTaskProcess = TestUtils.createOneTaskProcess(runtimeService); + + ProcessInstanceAssert assertThatOneTaskProcess = FlowableProcessAssertions.assertThat(oneTaskProcess); + assertThatOneTaskProcess.as("No variable exists in the process scope.") + .inHistory().variables().isEmpty(); + + runtimeService.setVariable(oneTaskProcess.getId(), "testVariable", "variableValue"); + + assertThatOneTaskProcess.as("Variable exists in the process scope, the variable must be present in the history.") + .inHistory().variables().hasSize(1).extracting("name", "value"). + containsExactly(Tuple.tuple("testVariable", "variableValue")); + } + + @Test + @Deployment(resources = "oneTask.bpmn20.xml") + void doesNotHaveVariable(RuntimeService runtimeService) { + ProcessInstance oneTaskProcess = TestUtils.createOneTaskProcess(runtimeService); + + ProcessInstanceAssert assertThatOneTaskProcess = FlowableProcessAssertions.assertThat(oneTaskProcess); + assertThatOneTaskProcess.as("No variable exists in the process scope.") + .inHistory().doesNotHaveVariable("nonExistingVariable"); + + runtimeService.setVariable(oneTaskProcess.getId(), "testVariable", "variableValue"); + + assertThatOneTaskProcess.as("Variable exists in the process scope, the variable must be present in the history.") + .inHistory().doesNotHaveVariable("nonExistingVariable") + .hasVariable("testVariable"); + + assertThatThrownBy(() -> assertThatOneTaskProcess.inHistory().doesNotHaveVariable("testVariable")) + .isInstanceOf(AssertionError.class) + .hasMessage("Expected process instance does not have variable but variable exists in history."); + } + +} \ No newline at end of file diff --git a/modules/flowable-assertj/src/test/java/org/flowable/assertj/process/LongRunningProcessInstanceAssertTest.java b/modules/flowable-assertj/src/test/java/org/flowable/assertj/process/LongRunningProcessInstanceAssertTest.java new file mode 100644 index 00000000000..243b4793fec --- /dev/null +++ b/modules/flowable-assertj/src/test/java/org/flowable/assertj/process/LongRunningProcessInstanceAssertTest.java @@ -0,0 +1,62 @@ +/* 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 org.flowable.assertj.process; + +import org.flowable.engine.RuntimeService; +import org.flowable.engine.TaskService; +import org.flowable.engine.history.HistoricActivityInstance; +import org.flowable.engine.runtime.ActivityInstance; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.engine.test.Deployment; +import org.flowable.engine.test.FlowableTest; +import org.junit.jupiter.api.Test; + +/** + * @author martin.grofcik + */ +@FlowableTest +class LongRunningProcessInstanceAssertTest { + + @Test + @Deployment(resources = "twoTasks.bpmn20.xml") + void useOneAssertInstanceThroughAllTest(RuntimeService runtimeService, TaskService taskService) { + ProcessInstance twoTasksProcess = runtimeService.createProcessInstanceBuilder().processDefinitionKey("twoTasksProcess").start(); + ProcessInstanceAssert asserThatProcessInstance = FlowableProcessAssertions.assertThat( + twoTasksProcess + ); + asserThatProcessInstance.isRunning() + .activities().extracting(ActivityInstance::getActivityId).containsExactlyInAnyOrder( + "theStart", "theStart-theTask", "theTask1" + ); + + taskService.complete(geTaskIdForProcessInstance(twoTasksProcess.getId(), taskService)); + + asserThatProcessInstance.isRunning().activities().extracting(ActivityInstance::getActivityId).containsExactlyInAnyOrder( + "theStart", "theStart-theTask", "theTask1", "theTask1-theTask2", "theTask2" + ); + + taskService.complete(geTaskIdForProcessInstance(twoTasksProcess.getId(), taskService)); + + asserThatProcessInstance.doesNotExist().inHistory() + .activities().extracting(HistoricActivityInstance::getActivityId).containsExactlyInAnyOrder( + "theStart", "theStart-theTask", "theTask1", "theTask1-theTask2", "theTask2", "theTask-theEnd", + "theEnd" + ); + } + + private String geTaskIdForProcessInstance(String processInstanceId, TaskService taskService) { + return taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult().getId(); + } + +} \ No newline at end of file diff --git a/modules/flowable-assertj/src/test/java/org/flowable/assertj/process/ProcessInstanceAssertTest.java b/modules/flowable-assertj/src/test/java/org/flowable/assertj/process/ProcessInstanceAssertTest.java new file mode 100644 index 00000000000..c4d09e6aa09 --- /dev/null +++ b/modules/flowable-assertj/src/test/java/org/flowable/assertj/process/ProcessInstanceAssertTest.java @@ -0,0 +1,308 @@ +/* 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 org.flowable.assertj.process; + +import org.assertj.core.groups.Tuple; +import org.flowable.engine.ManagementService; +import org.flowable.engine.ProcessEngineConfiguration; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.TaskService; +import org.flowable.engine.impl.test.JobTestHelper; +import org.flowable.engine.runtime.ActivityInstance; +import org.flowable.engine.runtime.Execution; +import org.flowable.engine.runtime.ProcessInstance; +import org.flowable.engine.test.Deployment; +import org.flowable.engine.test.FlowableTest; +import org.flowable.eventsubscription.api.EventSubscription; +import org.flowable.identitylink.api.IdentityLink; +import org.flowable.identitylink.api.IdentityLinkType; +import org.flowable.identitylink.api.history.HistoricIdentityLink; +import org.flowable.task.api.Task; +import org.flowable.task.api.history.HistoricTaskInstance; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * @author martin.grofcik + */ +@FlowableTest +class ProcessInstanceAssertTest { + + @Test + @Deployment(resources = "oneTask.bpmn20.xml") + void isRunning(RuntimeService runtimeService) { + ProcessInstance oneTaskProcess = TestUtils.createOneTaskProcess(runtimeService); + + FlowableProcessAssertions.assertThat(oneTaskProcess).isRunning(); + } + + @Test + @Deployment(resources = "oneTask.bpmn20.xml") + void isRunningForNonRunningProcess(RuntimeService runtimeService, TaskService taskService) { + ProcessInstance oneTaskProcess = TestUtils.createOneTaskProcess(runtimeService); + taskService.complete(taskService.createTaskQuery().processInstanceId(oneTaskProcess.getId()).singleResult().getId()); + + assertThatThrownBy(() -> FlowableProcessAssertions.assertThat(oneTaskProcess).isRunning()) + .isInstanceOf(AssertionError.class) + .hasMessage("Expected process instance to be running but is not."); + } + + @Test + void isRunningForNull() { + assertThatThrownBy(() -> FlowableProcessAssertions.assertThat((ProcessInstance) null).isRunning()) + .isInstanceOf(AssertionError.class) + .hasMessageContaining("Expecting actual not to be null"); + } + + @Test + @Deployment(resources = "oneTask.bpmn20.xml") + void hasVariable(RuntimeService runtimeService) { + ProcessInstance oneTaskProcess = TestUtils.createOneTaskProcess(runtimeService); + runtimeService.setVariable(oneTaskProcess.getId(), "variableName", "variableValue"); + + ProcessInstanceAssert assertThatOneTaskProcess = FlowableProcessAssertions.assertThat(oneTaskProcess); + assertThatOneTaskProcess.hasVariable("variableName"); + assertThatOneTaskProcess.hasVariable("variableName").isRunning(); + assertThatOneTaskProcess.isRunning().hasVariable("variableName"); + assertThatThrownBy(() -> assertThatOneTaskProcess.hasVariable("nonExistingVariable")) + .isInstanceOf(AssertionError.class) + .hasMessage("Expected process instance has variable but variable does not exist."); + } + + @Test + @Deployment(resources = "oneTask.bpmn20.xml") + void hasVariableForNonRunningProcess(RuntimeService runtimeService, TaskService taskService) { + ProcessInstance oneTaskProcess = TestUtils.createOneTaskProcess(runtimeService); + runtimeService.setVariable(oneTaskProcess.getId(), "variableName", "variableValue"); + taskService.complete(taskService.createTaskQuery().processInstanceId(oneTaskProcess.getId()).singleResult().getId()); + + assertThatThrownBy(() -> FlowableProcessAssertions.assertThat(oneTaskProcess).hasVariable("variableName")) + .isInstanceOf(AssertionError.class) + .hasMessage("Expected process instance has variable but variable does not exist."); + assertThatThrownBy(() -> FlowableProcessAssertions.assertThat(oneTaskProcess).hasVariable("nonExistingVariable")) + .isInstanceOf(AssertionError.class) + .hasMessage("Expected process instance has variable but variable does not exist."); + } + + @Test + void hasVariableForNull() { + assertThatThrownBy(() -> FlowableProcessAssertions.assertThat((ProcessInstance) null).hasVariable("variableName")) + .isInstanceOf(AssertionError.class) + .hasMessageContaining("Expecting actual not to be null"); + } + + @Test + @Deployment(resources = "oneTask.bpmn20.xml") + void doesNotHaveVariable(RuntimeService runtimeService) { + ProcessInstance oneTaskProcess = TestUtils.createOneTaskProcess(runtimeService); + runtimeService.setVariable(oneTaskProcess.getId(), "variableName", "variableValue"); + + ProcessInstanceAssert assertThatOneTaskProcess = FlowableProcessAssertions.assertThat(oneTaskProcess); + assertThatOneTaskProcess.doesNotHaveVariable("NonExistingVariableName"); + assertThatOneTaskProcess.doesNotHaveVariable("NonExistingVariableName").isRunning(); + assertThatThrownBy(() -> assertThatOneTaskProcess.doesNotHaveVariable("variableName")) + .isInstanceOf(AssertionError.class) + .hasMessage("Expected process instance does not have variable but variable exists."); + } + + @Test + @Deployment(resources = "oneTask.bpmn20.xml") + void doesNotHaveVariableForNonRunningProcess(RuntimeService runtimeService, TaskService taskService) { + ProcessInstance oneTaskProcess = TestUtils.createOneTaskProcess(runtimeService); + runtimeService.setVariable(oneTaskProcess.getId(), "variableName", "variableValue"); + taskService.complete(taskService.createTaskQuery().processInstanceId(oneTaskProcess.getId()).singleResult().getId()); + + ProcessInstanceAssert assertThatOneTaskProcess = FlowableProcessAssertions.assertThat(oneTaskProcess); + assertThatOneTaskProcess.doesNotHaveVariable("variableName"); + assertThatOneTaskProcess.doesNotHaveVariable("nonExistingVariable"); + } + + @Test + void doesNotHaveVariableForNull() { + assertThatThrownBy(() -> FlowableProcessAssertions.assertThat((ProcessInstance) null).doesNotHaveVariable("variableName")) + .isInstanceOf(AssertionError.class) + .hasMessageContaining("Expecting actual not to be null"); + } + + @Test + @Deployment(resources = "oneTask.bpmn20.xml") + void doesNotExistForRunningInstance(RuntimeService runtimeService) { + ProcessInstance oneTaskProcess = TestUtils.createOneTaskProcess(runtimeService); + + assertThatThrownBy(() -> FlowableProcessAssertions.assertThat(oneTaskProcess).doesNotExist()) + .isInstanceOf(AssertionError.class) + .hasMessage("Expected process instance is finished but instance exists in runtime."); + } + + @Test + @Deployment(resources = "oneTask.bpmn20.xml") + void doesNotExistForNonRunningProcess(RuntimeService runtimeService, TaskService taskService) { + ProcessInstance oneTaskProcess = TestUtils.createOneTaskProcess(runtimeService); + taskService.complete(taskService.createTaskQuery().processInstanceId(oneTaskProcess.getId()).singleResult().getId()); + + FlowableProcessAssertions.assertThat(oneTaskProcess).doesNotExist(); + } + + @Test + void doesNotExistForNull() { + assertThatThrownBy(() -> FlowableProcessAssertions.assertThat((ProcessInstance) null).doesNotExist()) + .isInstanceOf(AssertionError.class) + .hasMessageContaining("Expecting actual not to be null"); + } + + @Test + @Deployment(resources = "oneTask.bpmn20.xml") + void activitiesForRunningInstance(RuntimeService runtimeService) { + ProcessInstance oneTaskProcess = TestUtils.createOneTaskProcess(runtimeService); + + FlowableProcessAssertions.assertThat(oneTaskProcess).activities().extracting(ActivityInstance::getActivityId) + .contains("theStart", "theStart-theTask", "theTask"); + } + + @Test + @Deployment(resources = "oneTask.bpmn20.xml") + void activitiesForNonRunningProcess(RuntimeService runtimeService, TaskService taskService) { + ProcessInstance oneTaskProcess = TestUtils.createOneTaskProcess(runtimeService); + taskService.complete(taskService.createTaskQuery().processInstanceId(oneTaskProcess.getId()).singleResult().getId()); + + FlowableProcessAssertions.assertThat(oneTaskProcess).activities().isEmpty(); + } + + @Test + @Deployment(resources = "oneTask.bpmn20.xml") + void executions(RuntimeService runtimeService, TaskService taskService) { + ProcessInstance oneTaskProcess = TestUtils.createOneTaskProcess(runtimeService); + + ProcessInstanceAssert assertThatOneTaskProcess = FlowableProcessAssertions.assertThat(oneTaskProcess); + assertThatOneTaskProcess.as("Running process has at least 2 active executions." + + "(ProcessInstance + Child)") + .executions().extracting(Execution::getId).contains(oneTaskProcess.getId()) + .hasSize(2); + + taskService.complete(taskService.createTaskQuery().processInstanceId(oneTaskProcess.getId()).singleResult().getId()); + + assertThatOneTaskProcess.doesNotExist().as("There must be no execution for the finished process.") + .executions().hasSize(0); + } + + @Test + @Deployment(resources = "oneTask.bpmn20.xml") + void variables(RuntimeService runtimeService) { + ProcessInstance oneTaskProcess = TestUtils.createOneTaskProcess(runtimeService); + + ProcessInstanceAssert assertThatOneTaskProcess = FlowableProcessAssertions.assertThat(oneTaskProcess); + assertThatOneTaskProcess.variables().isEmpty(); + assertThatThrownBy(() -> assertThatOneTaskProcess.hasVariableWithValue("nonExistingVar", "anyValue")) + .isInstanceOf(AssertionError.class).hasMessage("Expected process instance has variable but variable does not exist."); + + runtimeService.setVariable(oneTaskProcess.getId(), "testVariable", "initialValue"); + + assertThatOneTaskProcess.variables().extracting("name", "value").contains(Tuple.tuple("testVariable", "initialValue")); + assertThatOneTaskProcess.hasVariableWithValue("testVariable", "initialValue"); + + runtimeService.setVariable(oneTaskProcess.getId(), "testVariable", "updatedValue"); + + assertThatOneTaskProcess.variables().extracting("name", "value").contains(Tuple.tuple("testVariable", "updatedValue")); + assertThatOneTaskProcess.hasVariableWithValue("testVariable", "updatedValue").isRunning(); + + runtimeService.setVariable(oneTaskProcess.getId(), "firstVariable", "initialValue"); + assertThatOneTaskProcess.as("Variables are ordered by names ascending.") + .variables().extracting("name") + .containsExactly("firstVariable", "testVariable"); + + } + + @Test + @Deployment(resources = "oneTask.bpmn20.xml") + void identityLinks(RuntimeService runtimeService) { + ProcessInstance oneTaskProcess = TestUtils.createOneTaskProcess(runtimeService); + + ProcessInstanceAssert assertThatOneTaskProcess = FlowableProcessAssertions.assertThat(oneTaskProcess); + assertThatOneTaskProcess.identityLinks().isEmpty(); + assertThatOneTaskProcess.inHistory().identityLinks().isEmpty(); + + runtimeService.addUserIdentityLink(oneTaskProcess.getId(), "testUser", IdentityLinkType.ASSIGNEE); + runtimeService.addGroupIdentityLink(oneTaskProcess.getId(), "testGroup", IdentityLinkType.CANDIDATE); + + assertThatOneTaskProcess.identityLinks().hasSize(2) + .extracting(IdentityLink::getUserId, IdentityLink::getGroupId, IdentityLink::getType) + .containsExactlyInAnyOrder(Tuple.tuple("testUser", null, IdentityLinkType.ASSIGNEE), + Tuple.tuple(null, "testGroup", IdentityLinkType.CANDIDATE)); + assertThatOneTaskProcess.inHistory().identityLinks().hasSize(2) + .extracting(HistoricIdentityLink::getUserId, HistoricIdentityLink::getGroupId, HistoricIdentityLink::getType) + .containsExactlyInAnyOrder(Tuple.tuple("testUser", null, IdentityLinkType.ASSIGNEE), + Tuple.tuple(null, "testGroup", IdentityLinkType.CANDIDATE)); + + } + + @Test + @Deployment(resources = "oneTask.bpmn20.xml") + void userTasks(RuntimeService runtimeService, ManagementService managementService, + TaskService taskService, ProcessEngineConfiguration processEngineConfiguration) { + ProcessInstance oneTaskProcess = runtimeService.createProcessInstanceBuilder().processDefinitionKey("oneTaskProcess").startAsync(); + + ProcessInstanceAssert assertThatOneTaskProcess = FlowableProcessAssertions.assertThat(oneTaskProcess); + assertThatOneTaskProcess.as("Process instance is started asynchronously and did not reach userTask") + .userTasks().isEmpty(); + assertThatOneTaskProcess.as("Process instance is started asynchronously and did not reach userTask in history") + .inHistory().userTasks().isEmpty(); + + JobTestHelper.waitForJobExecutorToProcessAllJobs(processEngineConfiguration, managementService, 10_000, 500); + + assertThatOneTaskProcess.as("Async executions executed and userTask reached.") + .userTasks().hasSize(1).extracting(Task::getTaskDefinitionKey).containsExactly("theTask"); + assertThatOneTaskProcess.as("Async executions executed and userTask reached in history.") + .inHistory().userTasks().hasSize(1).extracting(HistoricTaskInstance::getTaskDefinitionKey) + .containsExactly("theTask"); + + Task task = taskService.createTaskQuery().processInstanceId(oneTaskProcess.getId()).singleResult(); + taskService.complete(task.getId()); + + assertThatOneTaskProcess.as("User tasks are empty for non existing process").doesNotExist() + .userTasks().isEmpty(); + assertThatOneTaskProcess.as("The userTask is completed, but must exist in history.") + .inHistory().isFinished() + .userTasks().hasSize(1).extracting(HistoricTaskInstance::getTaskDefinitionKey) + .containsExactly("theTask"); + } + + @Test + @Deployment(resources = "oneTask.bpmn20.xml") + void subscriptionsWithoutSubscription(RuntimeService runtimeService) { + ProcessInstance oneTaskProcess = runtimeService.createProcessInstanceBuilder().processDefinitionKey("oneTaskProcess").start(); + + FlowableProcessAssertions.assertThat(oneTaskProcess).as("One task process does not have any subscription") + .eventSubscription().isEmpty(); + + } + + @Test + @Deployment(resources = "oneTaskWithBoundaryEvent.bpmn20.xml") + void subscriptions(RuntimeService runtimeService) { + ProcessInstance oneTaskProcessWithSubscription = runtimeService.createProcessInstanceBuilder().processDefinitionKey("oneTaskProcessWithBoundaryEvent").start(); + + FlowableProcessAssertions.assertThat(oneTaskProcessWithSubscription).as("One task process with subscription has exactly one subscription") + .eventSubscription().hasSize(1).extracting(EventSubscription::getEventName).contains("eventMessage"); + } + + @Test + void activitiesForNull() { + assertThatThrownBy(() -> FlowableProcessAssertions.assertThat((ProcessInstance) null).doesNotExist()) + .isInstanceOf(AssertionError.class) + .hasMessageContaining("Expecting actual not to be null"); + } + +} \ No newline at end of file diff --git a/modules/flowable-assertj/src/test/java/org/flowable/assertj/process/TestUtils.java b/modules/flowable-assertj/src/test/java/org/flowable/assertj/process/TestUtils.java new file mode 100644 index 00000000000..de2fd1e69f3 --- /dev/null +++ b/modules/flowable-assertj/src/test/java/org/flowable/assertj/process/TestUtils.java @@ -0,0 +1,26 @@ +/* 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 org.flowable.assertj.process; + +import org.flowable.engine.RuntimeService; +import org.flowable.engine.runtime.ProcessInstance; + +/** + * @author martin.grofcik + */ +abstract class TestUtils { + static ProcessInstance createOneTaskProcess(RuntimeService runtimeService) { + return runtimeService.createProcessInstanceBuilder().processDefinitionKey("oneTaskProcess").start(); + } +} diff --git a/modules/flowable-assertj/src/test/resources/flowable.cfg.xml b/modules/flowable-assertj/src/test/resources/flowable.cfg.xml new file mode 100644 index 00000000000..9f1f9937cc2 --- /dev/null +++ b/modules/flowable-assertj/src/test/resources/flowable.cfg.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/flowable-assertj/src/test/resources/log4j.properties b/modules/flowable-assertj/src/test/resources/log4j.properties new file mode 100644 index 00000000000..3cb5809086f --- /dev/null +++ b/modules/flowable-assertj/src/test/resources/log4j.properties @@ -0,0 +1,9 @@ +log4j.rootLogger=INFO, CA + +# ConsoleAppender +log4j.appender.CA=org.apache.log4j.ConsoleAppender +log4j.appender.CA.layout=org.apache.log4j.PatternLayout +log4j.appender.CA.layout.ConversionPattern= %d{hh:mm:ss,SSS} [%t] %-5p %c %x - %m%n + +log4j.logger.org.apache.ibatis=INFO +log4j.logger.javax.activation=INFO diff --git a/modules/flowable-assertj/src/test/resources/oneTask.bpmn20.xml b/modules/flowable-assertj/src/test/resources/oneTask.bpmn20.xml new file mode 100644 index 00000000000..1649c6f4ff4 --- /dev/null +++ b/modules/flowable-assertj/src/test/resources/oneTask.bpmn20.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/modules/flowable-assertj/src/test/resources/oneTaskWithBoundaryEvent.bpmn20.xml b/modules/flowable-assertj/src/test/resources/oneTaskWithBoundaryEvent.bpmn20.xml new file mode 100644 index 00000000000..53bad04ca24 --- /dev/null +++ b/modules/flowable-assertj/src/test/resources/oneTaskWithBoundaryEvent.bpmn20.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + diff --git a/modules/flowable-assertj/src/test/resources/twoTasks.bpmn20.xml b/modules/flowable-assertj/src/test/resources/twoTasks.bpmn20.xml new file mode 100644 index 00000000000..accf69c9c30 --- /dev/null +++ b/modules/flowable-assertj/src/test/resources/twoTasks.bpmn20.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 3a76fe2bcb9..3ca4e154691 100644 --- a/pom.xml +++ b/pom.xml @@ -536,6 +536,7 @@ modules/flowable-spring-security modules/flowable-http-common modules/flowable-mail + modules/flowable-assertj @@ -1023,6 +1024,7 @@ modules/flowable-ldap modules/flowable-ldap-configurator modules/flowable-jmx + modules/flowable-assertj tooling/archetypes/flowable-archetype-unittest