package com.forter.contracts; import static org.fest.assertions.api.Assertions.assertThat; import static org.mockito.Mockito.*; import com.forter.contracts.mocks.*; import backtype.storm.task.TopologyContext; import backtype.storm.topology.BasicOutputCollector; import backtype.storm.tuple.Tuple; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Optional; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableMap; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.mockito.ArgumentCaptor; import org.testng.annotations.Test; import org.unitils.reflectionassert.ReflectionAssert; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.util.List; import java.util.Map; /** * Unit Tests for {@link BaseContractsBoltExecutor} */ public class BaseContractsBoltExecutorTest { ObjectMapper mapper = new ObjectMapper(); final String id = "1"; @Test public void testContractsBoltWithJsonInput() { //mock copies input to output ObjectNode data = parseJson("{\"input1\":-1,\"optionalInput2\":-1}"); MockContractsBoltOutput output = new MockContractsBoltOutput(); output.output1 = -1; output.optionalOutput2 = Optional.of(-1); IContractsBolt contractsBolt = new MockContractsBolt(); BasicOutputCollector collector = execute(data, contractsBolt); assertEmitEquals(collector, output); } @Test public void testContractsBoltWithMapInput() { //mock copies input to output Map<String, Object> data = ImmutableMap.of("input1", (Object) new Integer(-1)); MockContractsBoltOutput output = new MockContractsBoltOutput(); output.output1 = -1; output.optionalOutput2 = Optional.absent(); IContractsBolt contractsBolt = new MockContractsBolt(); BasicOutputCollector collector = execute(data, contractsBolt); assertEmitEquals(collector, output); } @Test public void testCachedExecution() { MockContractsBolt bolt = new MockContractsBolt(); MockCacheDAO cache = new MockCacheDAO(); MockCachedContractBoltExecutor executor = new MockCachedContractBoltExecutor(bolt, cache); executor.prepare(mock(Map.class), mock(TopologyContext.class)); assertThat(cache.cache.size()).isEqualTo(0); ObjectNode data = parseJson("{\"input1\":-1,\"optionalInput2\":-1}"); BasicOutputCollector collector = execute(data, executor); Object output1 = getEmittedContract(collector); assertThat(cache.cache.size()).isEqualTo(1); assertThat(cache.cache.containsValue(output1)); collector = execute(data, executor); Object output2 = getEmittedContract(collector); ReflectionAssert.assertReflectionEquals(output2, output1); assertThat(cache.cache.size()).isEqualTo(1); } @Test public void testCollectionContractBolt() { //mock copies input to output twice ObjectNode data = parseJson("{\"input1\":-1,\"optionalInput2\":-1}"); MockCollectionContractsBolt contractsBolt = new MockCollectionContractsBolt(); BasicOutputCollector collector = execute(data, contractsBolt); verify(collector, times(2)).emit((String)any(), (List<Object>) any()); } @Test public void testOptionalContractsBolt() { //mock copies input to output String input = "{\"input1\":-1,\"optionalInput2\":-1}"; MockContractsBoltOutput output = new MockContractsBoltOutput(); output.output1 = -1; output.optionalOutput2 = Optional.of(-1); ObjectNode data = parseJson(input); MockOptionalContractsBolt contractsBolt = new MockOptionalContractsBolt(); execute(data, contractsBolt); } @Test(expectedExceptions = ContractViolationReportedFailedException.class) public void testNullOutput() { ObjectNode data = parseJson("{\"input1\":-1,\"optionalInput2\":-1}"); IContractsBolt contractsBolt = new MockNullContractsBolt(); BasicOutputCollector collector = execute(data, contractsBolt); } @Test(expectedExceptions = ContractViolationReportedFailedException.class) public void testNullOptionalOutput() { ObjectNode data = parseJson("{\"input1\":-1,\"optionalInput2\":-1}"); IContractsBolt contractsBolt = new MockNullOptionalContractsBolt(); execute(data, contractsBolt); } @Test(enabled = false) // TODO: Itai needs to fix this public void testInvalidOutput() { //optionalInput2 must be at most 10 and mock copies input to output resulting in invalid output ObjectNode data = parseJson("{\"input1\":-1,\"optionalInput2\":100}"); IContractsBolt contractsBolt = new MockContractsBolt(); BasicOutputCollector collector = execute(data, contractsBolt); verify(collector).reportError(any(IllegalStateException.class)); } @Test public void testInvalidInput() { //input1 must be at most 10 String input = "{\"input1\":10000,\"optionalInput2\":-1}"; MockContractsBoltOutput output = new MockContractsBoltOutput(); output.output1 = 0; output.optionalOutput2 = Optional.absent(); ObjectNode data = parseJson(input); MockOptionalContractsBolt contractsBolt = new MockOptionalContractsBolt(); BasicOutputCollector collector = mock(BasicOutputCollector.class); try { execute(data, contractsBolt, collector); } finally { assertEmitEquals(collector, output); } } @Test public void testInvalidInputTypes() { //input1 must be an integer String input = "{\"input1\":false,\"optionalInput2\":-1}"; MockContractsBoltOutput output = new MockContractsBoltOutput(); output.output1 = 0; output.optionalOutput2 = Optional.absent(); ObjectNode data = parseJson(input); MockOptionalContractsBolt contractsBolt = new MockOptionalContractsBolt(); BasicOutputCollector collector = mock(BasicOutputCollector.class); try { execute(data, contractsBolt, collector); } finally { assertEmitEquals(collector, output); } } @Test public void testNullInput() { //optional should accept json null token String input = "{\"input1\":-1,\"optionalInput2\":null}"; MockContractsBoltOutput output = new MockContractsBoltOutput(); output.output1 = -1; output.optionalOutput2 = Optional.absent(); ObjectNode data = parseJson(input); MockOptionalContractsBolt contractsBolt = new MockOptionalContractsBolt(); BasicOutputCollector collector = execute(data, contractsBolt); assertEmitEquals(collector, output); } @Test public void testMissingInput() { //optional should accept missing json value String input = "{\"input1\":-1}"; MockContractsBoltOutput output = new MockContractsBoltOutput(); output.output1 = -1; output.optionalOutput2 = Optional.absent(); ObjectNode data = parseJson(input); MockOptionalContractsBolt contractsBolt = new MockOptionalContractsBolt(); BasicOutputCollector collector = execute(data, contractsBolt); assertEmitEquals(collector, output); } @Test public void testAbsentOutput() { ObjectNode data = parseJson("{}"); IContractsBolt contractsBolt = new MockOptionalAbsentBolt(); BasicOutputCollector collector = mock(BasicOutputCollector.class); try { execute(data, contractsBolt, collector); } finally { verify(collector, times(0)).emit((String) any(), (List<Object>) any()); //no output } } @Test public void testEmptyCollectionOutput() { ObjectNode data = parseJson("{\"input1\":-1,\"optionalInput2\":-1}"); IContractsBolt contractsBolt = new MockEmptyCollectionBolt(); BasicOutputCollector collector = execute(data, contractsBolt); verify(collector, times(0)).emit((String)any(), (List<Object>) any()); } @Test public void testSerializable() { IContractsBolt contractsBolt = new MockEmptyCollectionBolt(); BaseContractsBoltExecutor bolt = new BaseContractsBoltExecutor(contractsBolt); try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream( baos )) { oos.writeObject(bolt); } catch (IOException e) { throw Throwables.propagate(e); } } private BasicOutputCollector execute(Object input, IContractsBolt bolt) { BasicOutputCollector collector = mock(BasicOutputCollector.class); execute(input, bolt, collector); return collector; } private BasicOutputCollector execute(Object input, BaseContractsBoltExecutor executor) { BasicOutputCollector collector = mock(BasicOutputCollector.class); execute(input, executor, collector); return collector; } private void execute(Object input, IContractsBolt bolt, BasicOutputCollector collector) { BaseContractsBoltExecutor baseContractsBoltExecutor = new BaseContractsBoltExecutor(bolt); baseContractsBoltExecutor.prepare(mock(Map.class), mock(TopologyContext.class)); execute(input, baseContractsBoltExecutor, collector); } private void execute(Object input, BaseContractsBoltExecutor executor, BasicOutputCollector collector) { Tuple tuple = mock(Tuple.class); when(tuple.getValue(0)).thenReturn(id); when(tuple.getValue(1)).thenReturn(input); executor.execute(tuple, collector); } private void assertEmitEquals(BasicOutputCollector collector, Object expectedOutput) { Object actual = getEmittedContract(collector); String actualString = ReflectionToStringBuilder.toString(actual, ToStringStyle.SHORT_PREFIX_STYLE, false, false); String expectedString = ReflectionToStringBuilder.toString(expectedOutput, ToStringStyle.SHORT_PREFIX_STYLE, false, false); assertThat(actualString).isEqualTo(expectedString); } private Object getEmittedContract(BasicOutputCollector collector) { ArgumentCaptor<List> actualOutput = ArgumentCaptor.forClass(List.class); verify(collector).emit((String)any(), actualOutput.capture()); List<Object> emittedObjects = (List<Object>) actualOutput.getValue(); return emittedObjects.get(1); } private ObjectNode parseJson(String input) { try { return (ObjectNode) mapper.readTree(input); } catch (IOException e) { throw Throwables.propagate(e); } } }