Skip to content

Commit 3e21f34

Browse files
committed
Added exercise tests
1 parent c29fc74 commit 3e21f34

4 files changed

Lines changed: 499 additions & 0 deletions

File tree

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
package dev.dsf.process.tutorial.exercise_7;
2+
3+
import static dev.dsf.process.tutorial.ConstantsTutorial.CODESYSTEM_VOTING_PROCESS;
4+
import static dev.dsf.process.tutorial.ConstantsTutorial.CODESYSTEM_VOTING_PROCESS_VALUE_BINARY_QUESTION;
5+
import static org.junit.Assert.assertEquals;
6+
import static org.junit.Assert.assertNotNull;
7+
import static org.junit.Assert.assertTrue;
8+
import static org.mockito.ArgumentMatchers.any;
9+
10+
import java.lang.reflect.Constructor;
11+
import java.lang.reflect.InvocationTargetException;
12+
import java.lang.reflect.Method;
13+
import java.lang.reflect.Modifier;
14+
import java.util.ArrayList;
15+
import java.util.Arrays;
16+
import java.util.HashMap;
17+
import java.util.List;
18+
import java.util.Map;
19+
import java.util.Optional;
20+
import java.util.function.BiConsumer;
21+
import java.util.function.Predicate;
22+
import java.util.stream.Collectors;
23+
24+
import org.camunda.bpm.engine.delegate.DelegateTask;
25+
import org.camunda.bpm.model.bpmn.Bpmn;
26+
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
27+
import org.camunda.bpm.model.bpmn.instance.ExclusiveGateway;
28+
import org.camunda.bpm.model.bpmn.instance.Process;
29+
import org.camunda.bpm.model.bpmn.instance.SequenceFlow;
30+
import org.camunda.bpm.model.bpmn.instance.ServiceTask;
31+
import org.camunda.bpm.model.bpmn.instance.UserTask;
32+
import org.camunda.bpm.model.bpmn.instance.camunda.CamundaTaskListener;
33+
import org.hl7.fhir.r4.model.QuestionnaireResponse;
34+
import org.hl7.fhir.r4.model.Task;
35+
import org.junit.BeforeClass;
36+
import org.junit.Test;
37+
import org.junit.runner.RunWith;
38+
import org.mockito.Mockito;
39+
import org.mockito.invocation.Invocation;
40+
import org.mockito.junit.MockitoJUnitRunner;
41+
42+
import dev.dsf.bpe.v1.ProcessPluginApi;
43+
import dev.dsf.bpe.v1.ProcessPluginDefinition;
44+
import dev.dsf.bpe.v1.activity.DefaultUserTaskListener;
45+
import dev.dsf.bpe.v1.plugin.ProcessPluginImpl;
46+
import dev.dsf.bpe.v1.service.TaskHelper;
47+
import dev.dsf.bpe.v1.variables.Variables;
48+
import dev.dsf.process.tutorial.ConstantsTutorial;
49+
import dev.dsf.process.tutorial.TestProcessPluginGenerator;
50+
import dev.dsf.process.tutorial.TutorialProcessPluginDefinition;
51+
52+
@RunWith(MockitoJUnitRunner.class)
53+
public class BpmnAndUserTaskListenerTest
54+
{
55+
@BeforeClass
56+
public static void loadResources()
57+
{
58+
ProcessPluginDefinition definition = new TutorialProcessPluginDefinition();
59+
ProcessPluginImpl processPlugin = TestProcessPluginGenerator.generate(definition, false,
60+
BpmnAndUserTaskListenerTest.class);
61+
boolean initialized = processPlugin.initializeAndValidateResources(ConstantsTutorial.TUTORIAL_DIC_ORGANIZATION_IDENTIFIER);
62+
63+
assertEquals(true, initialized);
64+
}
65+
66+
@Test
67+
public void testVoteBpmnFile()
68+
{
69+
String filename = "bpe/vote.bpmn";
70+
String processId = "dsfdev_vote";
71+
String questionnaireUrl = "http://dsf.dev/fhir/Questionnaire/user-vote|#{version}";
72+
73+
BpmnModelInstance model = Bpmn.readModelFromStream(
74+
this.getClass().getClassLoader().getResourceAsStream(filename));
75+
assertNotNull(model);
76+
77+
List<Process> processes = model.getModelElementsByType(Process.class).stream()
78+
.filter(p -> processId.equals(p.getId())).collect(Collectors.toList());
79+
assertEquals(1, processes.size());
80+
81+
Process process = processes.get(0);
82+
83+
String errorMissingUserTask = "Process '" + processId + "' in file '" + filename + "is missing a User Task";
84+
int userTaskCount = process.getChildElementsByType(UserTask.class).size();
85+
assertTrue(errorMissingUserTask, userTaskCount > 0);
86+
87+
String errorMissingCorrectUserTask = "Process '" + processId + "' in file '" + filename + " is missing User Task with incoming flow from exclusive gateway with name 'User Vote?' and outgoing flow to service task with name 'Save User Vote'";
88+
Optional<UserTask> optUserTask = process.getChildElementsByType(UserTask.class).stream()
89+
.filter(userTask -> userTask.getIncoming().stream().anyMatch(isFlowConnectingUserTaskAndExclusiveGateway(userTask)))
90+
.filter(userTask -> userTask.getOutgoing().stream().anyMatch(isFlowConnectingUserTaskAndSaveUserVoteServer(userTask))).findFirst();
91+
assertTrue(errorMissingCorrectUserTask, optUserTask.isPresent());
92+
UserTask userTask = optUserTask.get();
93+
94+
String errorUserTaskIncomingFlowMissingCondition =
95+
"User Task in process '" + processId + "' in file '" + filename + " with name " + userTask.getOutgoing()
96+
+ " is missing condition expression '${userVote}' on incoming flow from exclusive gateway with name 'User Vote?'";
97+
assertTrue(errorUserTaskIncomingFlowMissingCondition,
98+
userTask.getIncoming().stream().filter(isFlowConnectingUserTaskAndExclusiveGateway(userTask)).allMatch(hasCorrectConditionExpression()));
99+
100+
String errorUserTaskIsMissingCorrectFormKey =
101+
"User Task in process '" + processId + "' in file '" + filename + " with name " + userTask.getOutgoing()
102+
+ " is missing Form Key with value " + questionnaireUrl;
103+
assertEquals(errorUserTaskIsMissingCorrectFormKey, userTask.getCamundaFormKey(), questionnaireUrl);
104+
105+
String packageName = "dev.dsf.process.tutorial.listener";
106+
String errorNoUserTaskListenerFound =
107+
"No class extending DefaultUserTaskListener found in package '" + packageName + "'. Unable to verify if User Task has correct Task Listener set.";
108+
List<Class<? extends DefaultUserTaskListener>> userTaskListeners = Utils.getUserTaskListeners(packageName);
109+
assertTrue(errorNoUserTaskListenerFound, !userTaskListeners.isEmpty());
110+
111+
String errorUserTaskIsMissingTaskListener =
112+
"User Task in process '" + processId + "' in file '" + filename + " with name " + userTask.getOutgoing()
113+
+ " is missing at least one Task Listener which extends DefaultUserTaskListener. Found classes to add which extend DefaultUserTaskListener: " + userTaskListeners.stream().map(Class::getSimpleName).reduce("", (i, next) -> i + next + " ");
114+
List<CamundaTaskListener> camundaTaskListeners = userTask.getExtensionElements().getElements().stream()
115+
.filter(extensionElement -> extensionElement instanceof CamundaTaskListener)
116+
.map(extensionElement -> (CamundaTaskListener) extensionElement)
117+
.filter(camundaTaskListener -> userTaskListeners.stream().anyMatch(userTaskListener -> userTaskListener.getName().equals(camundaTaskListener.getAttributeValue("class"))))
118+
.toList();
119+
assertTrue(errorUserTaskIsMissingTaskListener, !camundaTaskListeners.isEmpty());
120+
121+
List<Class<? extends DefaultUserTaskListener>> userTaskListenersInUserTask = userTaskListeners.stream().filter(userTaskListener -> camundaTaskListeners.stream().anyMatch(camundaTaskListener -> camundaTaskListener.getAttributeValue("class").equals(userTaskListener.getName()))).toList();
122+
userTaskListenersInUserTask.forEach(userTaskListener -> assertEquals(Utils.errorMessageBeanMethod(userTaskListener), 1, Utils.countBeanMethods(userTaskListener)));
123+
124+
Map<Class<? extends DefaultUserTaskListener>, List<String>> userTaskListenersWithErrors = userTaskListenersInUserTask.stream().collect(Collectors.toMap(userTaskListener -> userTaskListener, this::validateUserTaskListener));
125+
126+
String errorNoTaskListenerInUserTaskIsValid = "User Task in process '" + processId + "' in file '" + filename + " with name " + userTask.getOutgoing() + " is missing at least one valid UserTaskListener. Errors are: \n";
127+
errorNoTaskListenerInUserTaskIsValid += userTaskListenersWithErrors.keySet().stream().map(key -> formatErrors(key, userTaskListenersWithErrors.get(key))).collect(Collectors.joining());
128+
129+
assertTrue(errorNoTaskListenerInUserTaskIsValid, userTaskListenersWithErrors.keySet().stream().anyMatch(userTaskListener -> userTaskListenersWithErrors.get(userTaskListener).isEmpty()));
130+
}
131+
132+
private Predicate<SequenceFlow> isFlowConnectingUserTaskAndExclusiveGateway(UserTask userTask)
133+
{
134+
return flow -> flow.getTarget().equals(userTask) && flow.getSource() instanceof ExclusiveGateway && flow.getSource().getName().equals("User Vote?");
135+
}
136+
137+
private Predicate<SequenceFlow> hasCorrectConditionExpression()
138+
{
139+
return flow -> flow.getConditionExpression().getTextContent().equals("${userVote}");
140+
}
141+
142+
private Predicate<SequenceFlow> isFlowConnectingUserTaskAndSaveUserVoteServer(UserTask userTask)
143+
{
144+
return flow -> flow.getSource().equals(userTask) && flow.getTarget() instanceof ServiceTask && flow.getTarget().getName().equals("Save User Vote");
145+
}
146+
147+
private String formatErrors(Class<? extends DefaultUserTaskListener> userTaskListener, List<String> errors)
148+
{
149+
String formatted = "";
150+
151+
formatted += "Class: " + userTaskListener.getSimpleName() + "\n";
152+
formatted += " Errors:\n" + errors.stream().reduce("", (i, next) -> i + " " + next + "\n");
153+
154+
return formatted;
155+
}
156+
157+
private BiConsumer<HashMap<Class<? extends DefaultUserTaskListener>, List<String>>, Class<? extends DefaultUserTaskListener>> putInMap()
158+
{
159+
return (map, userTaskListener) -> map.put(userTaskListener, validateUserTaskListener(userTaskListener));
160+
}
161+
162+
private BiConsumer<HashMap<Class<? extends DefaultUserTaskListener>, List<String>>, HashMap<Class<? extends DefaultUserTaskListener>, List<String>>> combineMaps()
163+
{
164+
return HashMap::putAll;
165+
}
166+
167+
// A UserTaskListener ist considered valid if beforeQuestionnaireResponseCreate() reads the input parameter
168+
// 'binary-question' from the Start Task and set the item.text value of the item with linkId 'binary-question'
169+
// to the value of the input parameter in the QuestionnaireResponse
170+
private List<String> validateUserTaskListener(Class<? extends DefaultUserTaskListener> userTaskListenerClass)
171+
{
172+
List<String> errors = new ArrayList<>();
173+
try
174+
{
175+
Constructor<? extends DefaultUserTaskListener> constructor = userTaskListenerClass.getConstructor(
176+
ProcessPluginApi.class);
177+
178+
ProcessPluginApi apiMock = Mockito.mock(ProcessPluginApi.class);
179+
TaskHelper taskHelperMock = Mockito.mock(TaskHelper.class);
180+
DelegateTask taskMock = Mockito.mock(DelegateTask.class);
181+
QuestionnaireResponse questionnaireResponseMock = Mockito.mock(QuestionnaireResponse.class);
182+
Variables variablesMock = Mockito.mock(Variables.class);
183+
Task startTaskMock = Mockito.mock(Task.class);
184+
QuestionnaireResponse.QuestionnaireResponseItemComponent itemMock = Mockito.mock(QuestionnaireResponse.QuestionnaireResponseItemComponent.class);
185+
186+
String binaryQuestion = "test?";
187+
188+
Mockito.lenient().when(apiMock.getVariables(any())).thenReturn(variablesMock);
189+
Mockito.lenient().when(apiMock.getTaskHelper()).thenReturn(taskHelperMock);
190+
Mockito.lenient().when(variablesMock.getStartTask()).thenReturn(startTaskMock);
191+
Mockito.lenient().when(taskHelperMock.getFirstInputParameterStringValue(startTaskMock, CODESYSTEM_VOTING_PROCESS,
192+
CODESYSTEM_VOTING_PROCESS_VALUE_BINARY_QUESTION)).thenReturn(Optional.of(binaryQuestion));
193+
Mockito.lenient().when(questionnaireResponseMock.getItem()).thenReturn(List.of(itemMock));
194+
Mockito.lenient().when(itemMock.getLinkId()).thenReturn(CODESYSTEM_VOTING_PROCESS_VALUE_BINARY_QUESTION);
195+
Mockito.lenient().when(itemMock.getText()).thenReturn("foo");
196+
Mockito.lenient().when(itemMock.hasText()).thenReturn(true);
197+
198+
DefaultUserTaskListener listenerSpy = Mockito.spy(constructor.newInstance(apiMock));
199+
Method method = userTaskListenerClass.getDeclaredMethod("beforeQuestionnaireResponseCreate",
200+
DelegateTask.class, QuestionnaireResponse.class);
201+
method.setAccessible(true);
202+
method.invoke(listenerSpy, taskMock, questionnaireResponseMock);
203+
204+
Optional<Invocation> optionalInvocation = Mockito.mockingDetails(taskHelperMock).getInvocations().stream()
205+
.filter(invocation -> invocation.getMethod().getName().equals("getFirstInputParameterStringValue"))
206+
.filter(invocation -> invocation.getArguments()[0].equals(startTaskMock))
207+
.filter(invocation -> invocation.getArguments()[1].equals(CODESYSTEM_VOTING_PROCESS))
208+
.filter(invocation -> invocation.getArguments()[2].equals(CODESYSTEM_VOTING_PROCESS_VALUE_BINARY_QUESTION))
209+
.findFirst();
210+
if(optionalInvocation.isEmpty()) errors.add("Expected one call to TaskHelper#getFirstInputParameterStringValue for Start Task and CodeSystem '" + CODESYSTEM_VOTING_PROCESS + "' and Code '" + CODESYSTEM_VOTING_PROCESS_VALUE_BINARY_QUESTION + "'");
211+
212+
optionalInvocation = Mockito.mockingDetails(itemMock).getInvocations().stream()
213+
.filter(invocation -> invocation.getMethod().getName().equals("setText"))
214+
.filter(invocation -> invocation.getArguments()[0].equals(binaryQuestion))
215+
.findFirst();
216+
if(optionalInvocation.isEmpty()) errors.add("Expected one call to QuestionnaireResponseItemComponent#setText for the QuestionnaireResponseItemComponent with linkId 'binary-question'");
217+
}
218+
catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e)
219+
{
220+
throw new RuntimeException(e);
221+
}
222+
223+
return errors;
224+
}
225+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package dev.dsf.process.tutorial.exercise_7;
2+
3+
import java.lang.reflect.Modifier;
4+
import java.util.ArrayList;
5+
import java.util.Arrays;
6+
import java.util.List;
7+
8+
import org.reflections.Reflections;
9+
import org.reflections.scanners.Scanners;
10+
import org.springframework.context.annotation.Bean;
11+
12+
import dev.dsf.bpe.v1.activity.DefaultUserTaskListener;
13+
import dev.dsf.process.tutorial.spring.config.TutorialConfig;
14+
15+
public class Utils
16+
{
17+
public static List<Class<? extends DefaultUserTaskListener>> getUserTaskListeners(String packageName)
18+
{
19+
Reflections reflections = new Reflections(packageName, Scanners.SubTypes);
20+
return new ArrayList<>(reflections.getSubTypesOf(DefaultUserTaskListener.class));
21+
}
22+
23+
public static long countBeanMethods(Class<?> returnType)
24+
{
25+
return Arrays.stream(TutorialConfig.class.getMethods()).filter(m -> returnType.equals(m.getReturnType()))
26+
.filter(m -> Modifier.isPublic(m.getModifiers())).filter(m -> m.getAnnotation(Bean.class) != null)
27+
.count();
28+
}
29+
30+
public static String errorMessageBeanMethod(Class<?> returnType)
31+
{
32+
return "One public spring bean method with return type " + returnType.getSimpleName() + " and annotation "
33+
+ Bean.class.getSimpleName() + " expected in " + TutorialConfig.class.getSimpleName();
34+
}
35+
}

0 commit comments

Comments
 (0)