package forklift.consumer;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import forklift.Forklift;
import forklift.connectors.ForkliftConnectorI;
import forklift.connectors.ForkliftMessage;
import forklift.decorators.LifeCycle;
import forklift.decorators.OnMessage;
import forklift.decorators.OnValidate;
import forklift.decorators.Queue;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
@Queue("X")
public class MessageRunnableTest {
private static Logger log = LoggerFactory.getLogger(MessageRunnableTest.class);
private Forklift forklift;
private ForkliftConnectorI connector;
@Before
public void setup() {
LifeCycleMonitors lifeCycle = new LifeCycleMonitors();
forklift = mock(Forklift.class);
connector = mock(ForkliftConnectorI.class);
when(connector.supportsQueue()).thenReturn(true);
when(connector.supportsTopic()).thenReturn(true);
when(forklift.getLifeCycle()).thenReturn(lifeCycle);
when(forklift.getConnector()).thenReturn(connector);
}
/**
* Mock consumer that can give the lifeCycle to the message runnables below.
*/
public class BaseTestConsumer extends Consumer {
public BaseTestConsumer(Forklift forklift) {
super(MessageRunnableTest.class, forklift);
}
}
// Given a message runner, make sure that the invalid is called when the OnValidate signature
// is incorrect.
@Test
public void invalidOnValidate() {
final BaseTestConsumer consumer = new BaseTestConsumer(forklift);
forklift.getLifeCycle().register(TestListener1.class);
List<Method> onMessage = new ArrayList<>();
List<Method> onValidate = new ArrayList<>();
for (Method m : TestConsumer1.class.getDeclaredMethods()) {
if (m.isAnnotationPresent(OnMessage.class))
onMessage.add(m);
else if (m.isAnnotationPresent(OnValidate.class))
onValidate.add(m);
}
TestConsumer1 tc = new TestConsumer1();
MessageRunnable
mr =
new MessageRunnable(consumer, new ForkliftMessage("1"), null, tc, onMessage, onValidate, null,
createOnProcessStepMap(), Collections.emptyList());
mr.run();
assertTrue(TestConsumer1.success.get());
forklift.getLifeCycle().deregister(TestListener1.class);
}
// Supportive classes for invalidOnValidate
public static class TestConsumer1 {
public static AtomicBoolean success = new AtomicBoolean(false);
@OnMessage
public void consumeMsg() {
// This shouldn't run. If it does we have an issue
TestConsumer1.success.set(false);
}
// The following has a bad return type for an OnValidate
@OnValidate
public void validateMsg() {
}
}
public static class TestListener1 {
@LifeCycle(ProcessStep.Invalid)
public static void invalid(MessageRunnable mr) {
TestConsumer1.success.set(true);
}
@LifeCycle(ProcessStep.Processing)
public static void process(MessageRunnable mr) {
// This shouldn't run. If it does we have an issue
TestConsumer1.success.set(false);
}
}
// Given a message runner, make sure that validating is called but processing is not
// when a message doesn't validate with a boolean return of false.
@Test
public void invalidMessage() {
final BaseTestConsumer consumer = new BaseTestConsumer(forklift);
forklift.getLifeCycle().register(TestListener2.class);
List<Method> onMessage = new ArrayList<>();
List<Method> onValidate = new ArrayList<>();
for (Method m : TestConsumer2.class.getDeclaredMethods()) {
if (m.isAnnotationPresent(OnMessage.class))
onMessage.add(m);
else if (m.isAnnotationPresent(OnValidate.class))
onValidate.add(m);
}
TestConsumer2 tc = new TestConsumer2();
MessageRunnable
mr =
new MessageRunnable(consumer, new ForkliftMessage("2"), null, tc, onMessage, onValidate, null,
createOnProcessStepMap(), Collections.emptyList());
mr.run();
assertTrue(TestConsumer2.success.get());
forklift.getLifeCycle().deregister(TestListener2.class);
}
// Supportive classes for invalidMessage
public static class TestConsumer2 {
public static AtomicBoolean success = new AtomicBoolean(false);
@OnMessage
public void consumeMsg() {
// This shouldn't run. If it does we have an issue
TestConsumer2.success.set(false);
}
@OnValidate
public Boolean validateMsg() {
return false;
}
}
public static class TestListener2 {
@LifeCycle(ProcessStep.Validating)
public static void invalid(MessageRunnable mr) {
TestConsumer2.success.set(true);
log.debug("checing for known error: " + mr.getErrors());
}
@LifeCycle(ProcessStep.Processing)
public static void process(MessageRunnable mr) {
// This shouldn't run. If it does we have an issue
TestConsumer2.success.set(false);
}
@LifeCycle(ProcessStep.Error)
public static void error(MessageRunnable mr) {
TestConsumer2.success.set(true);
}
}
// For a message runner make sure that if boolean validation succeeds that processing is called.
@Test
public void validMessage() {
final BaseTestConsumer consumer = new BaseTestConsumer(forklift);
forklift.getLifeCycle().register(TestListener3.class);
List<Method> onMessage = new ArrayList<>();
List<Method> onValidate = new ArrayList<>();
for (Method m : TestConsumer3.class.getDeclaredMethods()) {
if (m.isAnnotationPresent(OnMessage.class))
onMessage.add(m);
else if (m.isAnnotationPresent(OnValidate.class))
onValidate.add(m);
}
TestConsumer3 tc = new TestConsumer3();
MessageRunnable
mr =
new MessageRunnable(consumer, new ForkliftMessage("3"), tc.getClass().getClassLoader(), tc, onMessage,
onValidate, null, createOnProcessStepMap(), Collections.emptyList());
mr.run();
assertTrue(TestConsumer3.success.get());
forklift.getLifeCycle().deregister(TestListener3.class);
}
// Supportive classes for validMessage
public static class TestConsumer3 {
public static AtomicBoolean success = new AtomicBoolean(false);
@OnMessage
public void consumeMsg() {
log.debug("processing");
}
@OnValidate
public boolean validateMsg() {
return true;
}
}
public static class TestListener3 {
@LifeCycle(ProcessStep.Validating)
public static void invalid(MessageRunnable mr) {
TestConsumer3.success.set(false);
log.debug("checing for known error: " + mr.getErrors());
}
@LifeCycle(ProcessStep.Processing)
public static void process(MessageRunnable mr) {
log.debug("processing");
TestConsumer3.success.set(true);
}
@LifeCycle(ProcessStep.Error)
public static void error(MessageRunnable mr) {
TestConsumer3.success.set(false);
}
}
// For a message runner make sure that if an empty List of errors in validation does call processing and completes.
@Test
public void emptyListRet() {
final BaseTestConsumer consumer = new BaseTestConsumer(forklift);
forklift.getLifeCycle().register(TestListener4.class);
List<Method> onMessage = new ArrayList<>();
List<Method> onValidate = new ArrayList<>();
for (Method m : TestConsumer4.class.getDeclaredMethods()) {
if (m.isAnnotationPresent(OnMessage.class))
onMessage.add(m);
else if (m.isAnnotationPresent(OnValidate.class))
onValidate.add(m);
}
TestConsumer4 tc = new TestConsumer4();
MessageRunnable
mr =
new MessageRunnable(consumer, new ForkliftMessage("4"), tc.getClass().getClassLoader(), tc, onMessage,
onValidate, null, createOnProcessStepMap(), Collections.emptyList());
mr.run();
assertTrue(TestConsumer4.success.get());
forklift.getLifeCycle().deregister(TestListener4.class);
}
// Supportive classes for emptyListRet
public static class TestConsumer4 {
public static AtomicBoolean success = new AtomicBoolean(false);
@OnMessage
public void consumeMsg() {
log.debug("processing");
}
@OnValidate
public List<String> validateMsg() {
// Send back empty list
TestConsumer4.success.set(false);
return new ArrayList<String>();
}
}
public static class TestListener4 {
@LifeCycle(ProcessStep.Validating)
public static void invalid(MessageRunnable mr) {
TestConsumer4.success.set(false);
log.debug("checing for known error: " + mr.getErrors());
}
@LifeCycle(ProcessStep.Processing)
public static void process(MessageRunnable mr) {
log.debug("processing");
}
@LifeCycle(ProcessStep.Error)
public static void error(MessageRunnable mr) {
TestConsumer4.success.set(false);
}
@LifeCycle(ProcessStep.Complete)
public static void complete(MessageRunnable mr) {
TestConsumer4.success.set(true);
}
}
// For a message runner make sure that if the List of errors contains a string that it does not process and errors out.
@Test
public void errorInRetList() {
final BaseTestConsumer consumer = new BaseTestConsumer(forklift);
forklift.getLifeCycle().register(TestListener5.class);
List<Method> onMessage = new ArrayList<>();
List<Method> onValidate = new ArrayList<>();
for (Method m : TestConsumer5.class.getDeclaredMethods()) {
if (m.isAnnotationPresent(OnMessage.class))
onMessage.add(m);
else if (m.isAnnotationPresent(OnValidate.class))
onValidate.add(m);
}
TestConsumer5 tc = new TestConsumer5();
MessageRunnable
mr =
new MessageRunnable(consumer, new ForkliftMessage("5"), tc.getClass().getClassLoader(), tc, onMessage,
onValidate, null, createOnProcessStepMap(), Collections.emptyList());
mr.run();
assertTrue(TestConsumer5.success.get());
forklift.getLifeCycle().deregister(TestListener5.class);
}
// Supportive classes for errorInRetList
public static class TestConsumer5 {
public static AtomicBoolean success = new AtomicBoolean(false);
@OnMessage
public void consumeMsg() {
TestConsumer5.success.set(false);
log.debug("processing");
}
@OnValidate
public List<String> validateMsg() {
List<String> list = new ArrayList<>();
list.add("boogers");
return list;
}
}
public static class TestListener5 {
@LifeCycle(ProcessStep.Validating)
public static void validate(MessageRunnable mr) {
TestConsumer5.success.set(false);
log.debug("checing for known error: " + mr.getErrors());
}
@LifeCycle(ProcessStep.Processing)
public static void process(MessageRunnable mr) {
TestConsumer5.success.set(false);
log.debug("processing");
}
@LifeCycle(ProcessStep.Invalid)
public static void invalid(MessageRunnable mr) {
TestConsumer5.success.set(true);
}
}
// For a message runner make sure that if null is returned for the list of errors from validate
// that it calls processing.
@Test
public void invalidListNull() {
final BaseTestConsumer consumer = new BaseTestConsumer(forklift);
forklift.getLifeCycle().register(TestListener6.class);
List<Method> onMessage = new ArrayList<>();
List<Method> onValidate = new ArrayList<>();
for (Method m : TestConsumer6.class.getDeclaredMethods()) {
if (m.isAnnotationPresent(OnMessage.class))
onMessage.add(m);
else if (m.isAnnotationPresent(OnValidate.class))
onValidate.add(m);
}
TestConsumer6 tc = new TestConsumer6();
MessageRunnable
mr =
new MessageRunnable(consumer, new ForkliftMessage("6"), tc.getClass().getClassLoader(), tc, onMessage,
onValidate, null, createOnProcessStepMap(), Collections.emptyList());
mr.run();
assertTrue(TestConsumer6.success.get());
forklift.getLifeCycle().deregister(TestListener6.class);
}
// Supportive classes for invalidListNull
public static class TestConsumer6 {
public static AtomicBoolean success = new AtomicBoolean(false);
@OnMessage
public void consumeMsg() {
TestConsumer6.success.set(true);
log.debug("processing");
}
@OnValidate
public List<String> validateMsg() {
return null;
}
}
public static class TestListener6 {
@LifeCycle(ProcessStep.Validating)
public static void invalid(MessageRunnable mr) {
TestConsumer6.success.set(false);
log.debug("checing for known error: " + mr.getErrors());
}
@LifeCycle(ProcessStep.Processing)
public static void process(MessageRunnable mr) {
log.debug("processing");
TestConsumer6.success.set(true);
}
@LifeCycle(ProcessStep.Error)
public static void error(MessageRunnable mr) {
TestConsumer6.success.set(false);
}
}
// What happens when validation throws an exception? Should throw error with reason of Exception.
@Test
public void validationException() {
final BaseTestConsumer consumer = new BaseTestConsumer(forklift);
forklift.getLifeCycle().register(TestListener7.class);
List<Method> onMessage = new ArrayList<>();
List<Method> onValidate = new ArrayList<>();
for (Method m : TestConsumer7.class.getDeclaredMethods()) {
if (m.isAnnotationPresent(OnMessage.class))
onMessage.add(m);
else if (m.isAnnotationPresent(OnValidate.class))
onValidate.add(m);
}
TestConsumer7 tc = new TestConsumer7();
MessageRunnable
mr =
new MessageRunnable(consumer, new ForkliftMessage("7"), tc.getClass().getClassLoader(), tc, onMessage,
onValidate, null, createOnProcessStepMap(), Collections.emptyList());
mr.run();
assertTrue(TestConsumer7.success.get());
forklift.getLifeCycle().deregister(TestListener7.class);
}
// Supportive classes for validationException
public static class TestConsumer7 {
public static AtomicBoolean success = new AtomicBoolean(false);
@OnMessage
public void consumeMsg() {
TestConsumer7.success.set(false);
log.debug("processing");
}
@OnValidate
public List<String> validateMsg() {
throw new RuntimeException("Error Validating");
}
}
public static class TestListener7 {
@LifeCycle(ProcessStep.Validating)
public static void invalid(MessageRunnable mr) {
TestConsumer7.success.set(false);
log.debug("checing for known error: " + mr.getErrors());
}
@LifeCycle(ProcessStep.Processing)
public static void process(MessageRunnable mr) {
log.debug("processing");
TestConsumer7.success.set(false);
}
@LifeCycle(ProcessStep.Invalid)
public static void error(MessageRunnable mr) {
log.debug(mr.getErrors().toString());
if (mr.getErrors().toString().contains("Error Validating"))
TestConsumer7.success.set(true);
}
}
// What happens when processing throws an exception? Should process with Error.
@Test
public void processException() {
final BaseTestConsumer consumer = new BaseTestConsumer(forklift);
forklift.getLifeCycle().register(TestListener8.class);
List<Method> onMessage = new ArrayList<>();
List<Method> onValidate = new ArrayList<>();
for (Method m : TestConsumer8.class.getDeclaredMethods()) {
if (m.isAnnotationPresent(OnMessage.class))
onMessage.add(m);
else if (m.isAnnotationPresent(OnValidate.class))
onValidate.add(m);
}
TestConsumer8 tc = new TestConsumer8();
MessageRunnable
mr =
new MessageRunnable(consumer, new ForkliftMessage("8"), tc.getClass().getClassLoader(), tc, onMessage,
onValidate, null, createOnProcessStepMap(), Collections.emptyList());
mr.run();
assertTrue(TestConsumer8.success.get());
forklift.getLifeCycle().deregister(TestListener8.class);
}
// Supportive classes for processException
public static class TestConsumer8 {
public static AtomicBoolean success = new AtomicBoolean(false);
@OnMessage
public void consumeMsg() {
TestConsumer8.success.set(false);
throw new RuntimeException("Error processing");
}
@OnValidate
public boolean validateMsg() {
return true;
}
}
public static class TestListener8 {
@LifeCycle(ProcessStep.Validating)
public static void invalid(MessageRunnable mr) {
TestConsumer8.success.set(false);
}
@LifeCycle(ProcessStep.Processing)
public static void process(MessageRunnable mr) {
log.debug("processing");
TestConsumer8.success.set(false);
}
@LifeCycle(ProcessStep.Error)
public static void error(MessageRunnable mr) {
log.debug(mr.getErrors().toString());
if (mr.getErrors().toString().contains("Error processing"))
TestConsumer8.success.set(true);
}
}
// Creates a mock map with empty on-trigger lists
private Map<ProcessStep, List<Method>> createOnProcessStepMap() {
Map<ProcessStep, List<Method>> map = new HashMap<>();
Arrays.stream(ProcessStep.values()).forEach(step -> map.put(step, new ArrayList<>()));
return map;
}
}