package brainslug.flow.execution.token; import brainslug.AbstractExecutionTest; import brainslug.TestService; import brainslug.flow.builder.FlowBuilder; import brainslug.flow.builder.FlowBuilderSupport; import brainslug.flow.definition.FlowDefinition; import brainslug.flow.context.ExecutionContext; import brainslug.flow.context.Registry; import brainslug.flow.context.Trigger; import brainslug.flow.definition.Identifier; import brainslug.flow.execution.instance.DefaultFlowInstance; import brainslug.flow.execution.node.FlowNodeExecutionResult; import brainslug.flow.execution.node.TaskNodeExecutor; import brainslug.flow.execution.node.task.CallDefinitionExecutor; import brainslug.flow.context.BrainslugExecutionContext; import brainslug.flow.execution.node.task.Execute; import brainslug.flow.execution.node.task.SimpleTask; import brainslug.flow.execution.async.AsyncTrigger; import brainslug.flow.execution.expression.ContextPredicate; import brainslug.flow.expression.Property; import brainslug.flow.execution.instance.FlowInstance; import brainslug.flow.node.TaskDefinition; import brainslug.flow.node.task.Delegate; import brainslug.flow.node.task.GoalDefinition; import brainslug.util.IdUtil; import brainslug.util.Option; import org.assertj.core.api.Condition; import org.junit.Test; import static brainslug.util.IdUtil.id; import static brainslug.util.TestId.*; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.*; public class TaskNodeExecutorTest extends AbstractExecutionTest { @Test public void supportsServiceMethodCallDefinition() { // given: // # tag::service-call[] FlowDefinition serviceCallFlow = new FlowBuilder() { @Override public void define() { start(event(id(START))) .execute(task(id(TASK)).call(method(TestService.class).name("getString"))) .end(event(id(END))); } }.getDefinition(); // # end::service-call[] TaskNodeExecutor taskNodeExecutor = createTaskNodeExecutor(); // when: BrainslugExecutionContext context = new BrainslugExecutionContext(instanceMock(serviceCallFlow.getId()),new Trigger() .definitionId(serviceCallFlow.getId()) .nodeId(id(TASK)) .instanceId(id("instance")), registryWithServiceMock()); taskNodeExecutor.execute(serviceCallFlow.getNode(id(TASK), TaskDefinition.class), context); // then: verify(testServiceMock).getString(); } private FlowInstance instanceMock(Identifier definitionId) { return new DefaultFlowInstance(id("instance"), definitionId, propertyStore, tokenStore); } @Test public void typeSafeServiceMethodCallDefinition() { // given: // # tag::type-safe-call[] FlowDefinition serviceCallFlow = new FlowBuilder() { @Override public void define() { Property<String> echoProperty = FlowBuilderSupport.property(id("echo"), String.class); TestService testService = service(TestService.class); start(event(id(START))) .execute(task(id(TASK)).call(method(testService.echo(testService.getString())))) .execute(task(id(TASK2)).call(method(testService.echo(value(echoProperty))))) .end(event(id(END))); } }.getDefinition(); // # end::type-safe-call[] TaskNodeExecutor taskNodeExecutor = createTaskNodeExecutor(); // when: BrainslugExecutionContext instance = new BrainslugExecutionContext(instanceMock(serviceCallFlow.getId()),new Trigger() .definitionId(serviceCallFlow.getId()) .nodeId(id(TASK)) .property("echo", "Echo!") .instanceId(id("instance")), registryWithServiceMock()); taskNodeExecutor.execute(serviceCallFlow.getNode(id(TASK), TaskDefinition.class), instance); taskNodeExecutor.execute(serviceCallFlow.getNode(id(TASK2), TaskDefinition.class), instance); // then: verify(testServiceMock).getString(); verify(testServiceMock).echo(testServiceMock.getString()); verify(testServiceMock).echo("Echo!"); } @Test public void supportsHandlerCallDefinitionWithArgumentInjection() { // given: abstract // #tag::test-delegate[] class TestDelegate implements Delegate { @Execute abstract public void execute(TestService testService, ExecutionContext context); } // #end::test-delegate[] final TestDelegate testDelegate = spy(new TestDelegate() { @Override public void execute(TestService testService, ExecutionContext context) { // then: assertThat(testService.getString()).isEqualTo("a String"); assertThat(context).isNotNull(); } }); // #tag::delegate-flow[] FlowDefinition handlerFlow = new FlowBuilder() { @Override public void define() { start(event(id(START))) .execute(task(id(TASK)).delegate(TestDelegate.class)) .end(event(id(END))); } }.getDefinition(); // #end::delegate-flow[] TaskNodeExecutor taskNodeExecutor = createTaskNodeExecutor(); Registry registry = registryWithServiceMock(); when(registry.getService(TestDelegate.class)).thenReturn(testDelegate); // when: BrainslugExecutionContext instance = new BrainslugExecutionContext(instanceMock(handlerFlow.getId()),new Trigger() .definitionId(handlerFlow.getId()) .nodeId(id(TASK)) .instanceId(id("instance")), registry); taskNodeExecutor.execute(handlerFlow.getNode(id(TASK), TaskDefinition.class), instance); // then: verify(testDelegate).execute(any(TestService.class), any(ExecutionContext.class)); } @Test public void supportsDelegateClassExecution() { // given: class TestDelegate implements Delegate { @Execute public void doSomeThing() { } } TestDelegate delegateInstance = spy(new TestDelegate()); FlowDefinition delegateFlow = new FlowBuilder() { @Override public void define() { start(event(id(START))) .execute(task(id(TASK)).delegate(Delegate.class)) .end(event(id(END))); } }.getDefinition(); when(definitionStore.findById(delegateFlow.getId())).thenReturn(delegateFlow); when(registry.getService(Delegate.class)).thenReturn(delegateInstance); context.addFlowDefinition(delegateFlow); // when: context.startFlow(delegateFlow.getId(), id(START)); // then: verify(delegateInstance, times(1)).doSomeThing(); } @Test public void asyncTaskIsDelegatedToScheduler() { // given: TaskNodeExecutor taskNodeExecutor = createTaskNodeExecutor(); FlowDefinition asyncTaskFlow = new FlowBuilder() { @Override public void define() { flowId(id(ASYNCID)); start(event(id(START))) .execute(task(id(TASK)).async(true)) .end(event(id(END))); } }.getDefinition(); context.addFlowDefinition(asyncTaskFlow); // when: taskNodeExecutor.execute(asyncTaskFlow.getNode(id(TASK), TaskDefinition.class), new BrainslugExecutionContext(instanceMock(asyncTaskFlow.getId()),new Trigger() .definitionId(asyncTaskFlow.getId()) .nodeId(id(TASK)) .instanceId(id("instance")), registryWithServiceMock())); // then: verify(asyncTriggerScheduler).schedule(new AsyncTrigger() .withNodeId(id(TASK)) .withInstanceId(id("instance")) .withDefinitionId(id(ASYNCID))); } @Test public void shouldPropagateException() { FlowDefinition serviceCallFlow = new FlowBuilder() { @Override public void define() { start(event(id(START))) .execute(task(id(TASK), new SimpleTask() { @Override public void execute(ExecutionContext context) { throw new RuntimeException("error"); } })) .end(event(id(END))); } }.getDefinition(); TaskNodeExecutor taskNodeExecutor = createTaskNodeExecutor(); BrainslugExecutionContext context = new BrainslugExecutionContext(instanceMock(serviceCallFlow.getId()),new Trigger() .definitionId(serviceCallFlow.getId()) .nodeId(id(TASK)) .instanceId(id("instance")), registryWithServiceMock()); FlowNodeExecutionResult result = taskNodeExecutor.execute(serviceCallFlow.getNode(id(TASK), TaskDefinition.class), context); assertThat(result.isFailed()).isTrue(); assertThat(result.getException()) .isNotNull() .has(new Condition<Option<Exception>>() { @Override public boolean matches(Option<Exception> exceptionOption) { return exceptionOption.get().getMessage().equals("error"); } }); } private TaskNodeExecutor createTaskNodeExecutor() { return new TaskNodeExecutor(definitionStore, expressionEvaluator, new CallDefinitionExecutor(), asyncTriggerScheduler, scriptExecutor); } @Test public void taskIsExecutedIfGoalIsNotFulfilled() { // given: GoalFlow goalFlow = new GoalFlow().setup(); when(goalFlow.getGoalCondition().isFulfilled(any(ExecutionContext.class))).thenReturn(false); // when: taskNodeTriggered(goalFlow); // then: verify(goalFlow.getSimpleTask()).execute(any(ExecutionContext.class)); } @Test public void taskIsNotExecutedIfGoalIsAlreadyFulfilled() { // given: GoalFlow goalFlow = new GoalFlow().setup(); when(goalFlow.getGoalCondition().isFulfilled(any(ExecutionContext.class))).thenReturn(true); // when: taskNodeTriggered(goalFlow); // then: verifyZeroInteractions(goalFlow.getSimpleTask()); } private void taskNodeTriggered(GoalFlow goalFlow) { Trigger trigger = new Trigger() .definitionId(goalFlow.getGoalFlow().getId()) .nodeId(id(TASK)); FlowInstance flowInstance = instanceMock(goalFlow.getGoalFlow().getId()); BrainslugExecutionContext executionContext = new BrainslugExecutionContext(flowInstance, trigger, registryWithServiceMock()); createTaskNodeExecutor() .execute((TaskDefinition) goalFlow.getGoalFlow().getNode(IdUtil.id(TASK)), executionContext); } private class GoalFlow { private SimpleTask simpleTask; private ContextPredicate goalCondition; private FlowDefinition goalFlow; public SimpleTask getSimpleTask() { return simpleTask; } public ContextPredicate getGoalCondition() { return goalCondition; } public FlowDefinition getGoalFlow() { return goalFlow; } public GoalFlow setup() { simpleTask = mock(SimpleTask.class); goalCondition = mock(ContextPredicate.class); goalFlow = new FlowBuilder() { @Override public void define() { GoalDefinition testGoal = goal(id("aGoal")).check(predicate(goalCondition)); start(event(id(START))) .execute(task(id(TASK), simpleTask).goal(testGoal)) .end(event(id(END))); } @Override public String getId() { return id("GOAL").stringValue(); } }.getDefinition(); when(definitionStore.findById(goalFlow.getId())).thenReturn(goalFlow); return this; } } }