package brainslug.flow.execution.token;
import brainslug.AbstractExecutionTest;
import brainslug.TestService;
import brainslug.flow.builder.FlowBuilder;
import brainslug.flow.definition.FlowDefinition;
import brainslug.flow.definition.Identifier;
import brainslug.flow.context.ExecutionContext;
import brainslug.flow.context.Trigger;
import brainslug.flow.execution.instance.DefaultFlowInstance;
import brainslug.flow.execution.instance.InstanceSelector;
import brainslug.flow.execution.node.FlowNodeExecutionException;
import brainslug.flow.execution.node.task.SimpleTask;
import brainslug.flow.execution.instance.FlowInstance;
import brainslug.flow.execution.instance.FlowInstanceProperty;
import brainslug.util.IdUtil;
import brainslug.util.Option;
import org.junit.Test;
import org.mockito.InOrder;
import static brainslug.flow.execution.property.ExecutionProperties.newProperties;
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 TokenFlowExecutorTest extends AbstractExecutionTest {
@Test
public void shouldExecuteParallel() {
// given:
when(definitionStore.findById(id(PARALLELID))).thenReturn(new FlowBuilder() {
@Override
public void define() {
flowId(id(PARALLELID));
start(event(id(START))).parallel(id(PARALLEL))
.execute(task(id(TASK)))
.and()
.execute(task(id(TASK2)));
}
}.getDefinition());
TokenFlowExecutor tokenFlowExecutor = tokenFlowExecutorWithMocks();
// when:
Trigger trigger = new Trigger().definitionId(id(PARALLELID)).nodeId(id(START));
Identifier instanceId = tokenFlowExecutor.startFlow(trigger).getIdentifier();
// then:
InOrder eventOrder = inOrder(tokenFlowExecutor);
eventOrder.verify(tokenFlowExecutor).trigger(new Trigger().nodeId(id(START)).definitionId(PARALLELID).instanceId(instanceId));
eventOrder.verify(tokenFlowExecutor).trigger(new Trigger().nodeId(id(PARALLEL)).definitionId(PARALLELID).instanceId(instanceId));
eventOrder.verify(tokenFlowExecutor).trigger(new Trigger().nodeId(id(TASK)).definitionId(PARALLELID).instanceId(instanceId));
eventOrder.verify(tokenFlowExecutor).trigger(new Trigger().nodeId(id(TASK2)).definitionId(PARALLELID).instanceId(instanceId));
}
@Test
public void shouldExecuteMerge() {
// given:
when(definitionStore.findById(id(MERGEID))).thenReturn(new FlowBuilder() {
String x = "test";
@Override
public void define() {
flowId(id(MERGEID));
start(event(id(START)))
.choice(id(CHOICE))
.when(eq(constant(x) ,"test")).execute(task(id(TASK)))
.or()
.when(eq(constant(x), "test2")).execute(task(id(TASK2)));
merge(id(MERGE), id(TASK), id(TASK2))
.end(event(id(END)));
}
}.getDefinition());
TokenFlowExecutor tokenFlowExecutor = tokenFlowExecutorWithMocks();
// when:
Trigger trigger = new Trigger().definitionId(id(MERGEID)).nodeId(id(START));
Identifier instanceId = tokenFlowExecutor.startFlow(trigger).getIdentifier();
// then:
InOrder eventOrder = inOrder(tokenFlowExecutor);
eventOrder.verify(tokenFlowExecutor).trigger(new Trigger().nodeId(id(START)).definitionId(MERGEID).instanceId(instanceId));
eventOrder.verify(tokenFlowExecutor).trigger(new Trigger().nodeId(id(CHOICE)).definitionId(MERGEID).instanceId(instanceId));
eventOrder.verify(tokenFlowExecutor).trigger(new Trigger().nodeId(id(TASK)).definitionId(MERGEID).instanceId(instanceId));
eventOrder.verify(tokenFlowExecutor).trigger(new Trigger().nodeId(id(MERGE)).definitionId(MERGEID).instanceId(instanceId));
eventOrder.verify(tokenFlowExecutor).trigger(new Trigger().nodeId(id(END)).definitionId(MERGEID).instanceId(instanceId));
}
@Test
public void shouldExecuteJoin() {
// given:
when(definitionStore.findById(id(JOINID))).thenReturn(new FlowBuilder() {
@Override
public void define() {
flowId(id(JOINID));
start(event(id(START)))
.parallel(id(PARALLEL))
.execute(task(id(TASK)))
.and()
.execute(task(id(TASK2)));
join(id(JOIN), id(TASK), id(TASK2))
.end(event(id(END)));
}
}.getDefinition());
TokenFlowExecutor tokenFlowExecutor = tokenFlowExecutorWithMocks();
// when:
Trigger trigger = new Trigger().definitionId(id(JOINID)).nodeId(id(START));
Identifier instanceId = tokenFlowExecutor.startFlow(trigger).getIdentifier();
// then:
InOrder eventOrder = inOrder(tokenFlowExecutor);
eventOrder.verify(tokenFlowExecutor).trigger(new Trigger().nodeId(id(START)).definitionId(JOINID).instanceId(instanceId));
eventOrder.verify(tokenFlowExecutor).trigger(new Trigger().nodeId(id(PARALLEL)).definitionId(JOINID).instanceId(instanceId));
eventOrder.verify(tokenFlowExecutor).trigger(new Trigger().nodeId(id(TASK)).definitionId(JOINID).instanceId(instanceId));
eventOrder.verify(tokenFlowExecutor).trigger(new Trigger().nodeId(id(JOIN)).definitionId(JOINID).instanceId(instanceId));
eventOrder.verify(tokenFlowExecutor).trigger(new Trigger().nodeId(id(TASK2)).definitionId(JOINID).instanceId(instanceId));
eventOrder.verify(tokenFlowExecutor).trigger(new Trigger().nodeId(id(JOIN)).definitionId(JOINID).instanceId(instanceId));
eventOrder.verify(tokenFlowExecutor).trigger(new Trigger().nodeId(id(END)).definitionId(JOINID).instanceId(instanceId));
}
@Test
public void shouldEvaluateServiceCallResultInChoice() {
// given:
when(definitionStore.findById(id(CHOICEID))).thenReturn(new FlowBuilder() {
String x = "test";
@Override
public void define() {
start(event(id(START))).choice(id(CHOICE))
.when(resultOf(method(TestService.class).name("getString"))
.isEqualTo("a String"))
.execute(task(id(TASK)))
.or()
.when(eq(constant(x), "test2")).execute(task(id(TASK2)));
}
}.getDefinition());
TokenFlowExecutor tokenFlowExecutor = tokenFlowExecutorWithMocks();
// when:
Trigger trigger = new Trigger().definitionId(id(CHOICEID)).nodeId(id(START));
Identifier instanceId = tokenFlowExecutor.startFlow(trigger).getIdentifier();
// then:
InOrder eventOrder = inOrder(tokenFlowExecutor);
eventOrder.verify(tokenFlowExecutor).trigger(new Trigger().nodeId(id(START)).definitionId(CHOICEID).instanceId(instanceId));
eventOrder.verify(tokenFlowExecutor).trigger(new Trigger().nodeId(id(CHOICE)).definitionId(CHOICEID).instanceId(instanceId));
eventOrder.verify(tokenFlowExecutor).trigger(new Trigger().nodeId(id(TASK)).definitionId(CHOICEID).instanceId(instanceId));
}
@Test
public void shouldStorePropertiesOnTrigger() {
// given:
FlowBuilder flow = propertyFlow();
when(definitionStore.findById(flow.getDefinition().getId())).thenReturn(flow.getDefinition());
Identifier<?> instanceId = id("instance");
givenInstance(instanceId);
Trigger property = new Trigger()
.instanceId(instanceId)
.nodeId(IdUtil.id("start"))
.definitionId(flow.getId())
.property("key", "value");
// when:
context.trigger(property);
// for each node trigger
verify(propertyStore, times(2)).setProperty(eq(instanceId), any(FlowInstanceProperty.class));
}
private FlowBuilder propertyFlow() {
return new FlowBuilder() {
@Override
public void define() {
start(event(id("start")))
.execute(task(id("propertyTask"), new SimpleTask() {
@Override
public void execute(ExecutionContext context) {
assertThat(context.property("key", String.class)).isEqualTo("value");
}
}));
}
};
}
private FlowInstance givenInstance(Identifier<?> instanceId) {
FlowInstance flowInstance = new DefaultFlowInstance(instanceId, id("definition"), propertyStore, tokenStore);
doReturn(Option.of(flowInstance)).when(instanceStore).findInstance(any(InstanceSelector.class));
return flowInstance;
}
@Test
public void shouldStorePropertiesOnFlowStart() {
// given:
FlowBuilder startFlow = startFlow();
FlowDefinition definition = startFlow.getDefinition();
when(definitionStore.findById(definition.getId())).thenReturn(definition);
// when:
Identifier newInstanceId = id("instance");
when(instanceStore.createInstance(definition.getId())).thenReturn(new DefaultFlowInstance(newInstanceId, definition.getId(), propertyStore, tokenStore));
givenInstance(newInstanceId);
context.startFlow(definition, newProperties().with("foo", "bar"));
// before trigger and during start trigger
verify(propertyStore, times(2)).setProperty(eq(newInstanceId), any(FlowInstanceProperty.class));
}
@Test
public void shouldNotStoreTransientProperties() {
// given:
FlowBuilder flow = givenFlow(propertyFlow());
FlowInstance instance = givenInstance(id("instance"));
when(instanceStore.createInstance(flow.getDefinition().getId()))
.thenReturn(instance);
// when:
context.startFlow(flow.getDefinition(), newProperties().with("key", "value", true));
Trigger startNodeTrigger = new Trigger()
.instanceId(instance.getIdentifier())
.nodeId(IdUtil.id("start"))
.definitionId(flow.getId())
.property("key", "value", true);
context.trigger(startNodeTrigger);
// never store
verify(propertyStore, never()).setProperty(eq(instance.getIdentifier()), any(FlowInstanceProperty.class));
}
@Test(expected = FlowNodeExecutionException.class)
public void shouldRethrowExceptionOnFailedExecution() {
// given:
FlowBuilder flow = givenFlow(new FlowBuilder() {
@Override
public void define() {
start(id("start")).execute(task(id("task_with_error"), new SimpleTask() {
@Override
public void execute(ExecutionContext context) {
throw new RuntimeException("an error");
}
}));
}
});
FlowInstance instance = givenInstance(id("instance"));
when(instanceStore.createInstance(flow.getDefinition().getId()))
.thenReturn(instance);
// when:
context.startFlow(flow.getDefinition(), newProperties().with("key", "value"));
}
private FlowBuilder givenFlow(FlowBuilder flowBuilder) {
FlowDefinition definition = flowBuilder.getDefinition();
when(definitionStore.findById(definition.getId())).thenReturn(definition);
return flowBuilder;
}
private FlowBuilder startFlow() {
return new FlowBuilder() {
@Override
public void define() {
start(event(id("start")));
}
};
}
}