/** * This file is part of Graylog. * * Graylog is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Graylog is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Graylog. If not, see <http://www.gnu.org/licenses/>. */ package org.graylog2.plugin; import com.codahale.metrics.Meter; import com.codahale.metrics.MetricRegistry; import com.eaio.uuid.UUID; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import org.graylog2.indexer.IndexSet; import org.graylog2.plugin.streams.Stream; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; import static com.google.common.collect.Sets.symmetricDifference; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class MessageTest { @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule(); private Message message; private DateTime originalTimestamp; private MetricRegistry metricRegistry; private Meter invalidTimestampMeter; @Before public void setUp() { metricRegistry = new MetricRegistry(); originalTimestamp = Tools.nowUTC(); message = new Message("foo", "bar", originalTimestamp); invalidTimestampMeter = metricRegistry.meter("test"); } @Test public void testAddFieldDoesOnlyAcceptAlphanumericKeys() throws Exception { Message m = new Message("foo", "bar", Tools.nowUTC()); m.addField("some_thing", "bar"); assertEquals("bar", m.getField("some_thing")); m = new Message("foo", "bar", Tools.nowUTC()); m.addField("some-thing", "bar"); assertEquals("bar", m.getField("some-thing")); m = new Message("foo", "bar", Tools.nowUTC()); m.addField("somethin$g", "bar"); assertNull(m.getField("somethin$g")); m = new Message("foo", "bar", Tools.nowUTC()); m.addField("someäthing", "bar"); assertNull(m.getField("someäthing")); } @Test public void testAddFieldTrimsValue() throws Exception { Message m = new Message("foo", "bar", Tools.nowUTC()); m.addField("something", " bar "); assertEquals("bar", m.getField("something")); m.addField("something2", " bar"); assertEquals("bar", m.getField("something2")); m.addField("something3", "bar "); assertEquals("bar", m.getField("something3")); } @Test public void testConstructorsTrimValues() throws Exception { final Map<String, Object> messageFields = ImmutableMap.of( Message.FIELD_ID, new UUID().toString(), Message.FIELD_MESSAGE, " foo ", Message.FIELD_SOURCE, " bar ", "something", " awesome ", "something_else", " " ); Message m = new Message((String) messageFields.get(Message.FIELD_MESSAGE), (String) messageFields.get(Message.FIELD_SOURCE), Tools.nowUTC()); assertEquals("foo", m.getMessage()); assertEquals("bar", m.getSource()); Message m2 = new Message(messageFields); assertEquals("foo", m2.getMessage()); assertEquals("bar", m2.getSource()); assertEquals("awesome", m2.getField("something")); assertNull(m2.getField("something_else")); } @Test public void testAddFieldWorksWithIntegers() throws Exception { Message m = new Message("foo", "bar", Tools.nowUTC()); m.addField("something", 3); assertEquals(3, m.getField("something")); } @Test public void testAddFields() throws Exception { final Map<String, Object> map = Maps.newHashMap(); map.put("field1", "Foo"); map.put("field2", 1); message.addFields(map); assertEquals("Foo", message.getField("field1")); assertEquals(1, message.getField("field2")); } @Test public void testAddStringFields() throws Exception { final Map<String, String> map = Maps.newHashMap(); map.put("field1", "Foo"); map.put("field2", "Bar"); message.addStringFields(map); assertEquals("Foo", message.getField("field1")); assertEquals("Bar", message.getField("field2")); } @Test public void testAddLongFields() throws Exception { final Map<String, Long> map = Maps.newHashMap(); map.put("field1", 10L); map.put("field2", 230L); message.addLongFields(map); assertEquals(10L, message.getField("field1")); assertEquals(230L, message.getField("field2")); } @Test public void testAddDoubleFields() throws Exception { final Map<String, Double> map = Maps.newHashMap(); map.put("field1", 10.0d); map.put("field2", 230.2d); message.addDoubleFields(map); assertEquals(10.0d, message.getField("field1")); assertEquals(230.2d, message.getField("field2")); } @Test public void testRemoveField() throws Exception { message.addField("foo", "bar"); message.removeField("foo"); assertNull(message.getField("foo")); } @Test public void testRemoveFieldNotDeletingReservedFields() throws Exception { message.removeField("message"); message.removeField("source"); message.removeField("timestamp"); assertNotNull(message.getField("message")); assertNotNull(message.getField("source")); assertNotNull(message.getField("timestamp")); } @Test public void testGetFieldAs() throws Exception { message.addField("fields", Lists.newArrayList("hello")); assertEquals(Lists.newArrayList("hello"), message.getFieldAs(List.class, "fields")); } @Test(expected = ClassCastException.class) public void testGetFieldAsWithIncompatibleCast() throws Exception { message.addField("fields", Lists.newArrayList("hello")); message.getFieldAs(Map.class, "fields"); } @Test public void testSetAndGetStreams() throws Exception { final Stream stream1 = mock(Stream.class); final Stream stream2 = mock(Stream.class); message.addStreams(Lists.newArrayList(stream2, stream1)); // make sure all streams we've added are being returned. Internally it's a set, so don't check the order, it doesn't matter anyway. assertThat(message.getStreams()).containsOnly(stream1, stream2); } @Test public void testStreamMutators() { final Stream stream1 = mock(Stream.class); final Stream stream2 = mock(Stream.class); final Stream stream3 = mock(Stream.class); assertThat(message.getStreams()).isNotNull(); assertThat(message.getStreams()).isEmpty(); message.addStream(stream1); final Set<Stream> onlyWithStream1 = message.getStreams(); assertThat(onlyWithStream1).containsOnly(stream1); message.addStreams(Sets.newHashSet(stream3, stream2)); assertThat(message.getStreams()).containsOnly(stream1, stream2, stream3); // getStreams is a copy and doesn't change after mutations assertThat(onlyWithStream1).containsOnly(stream1); // stream2 was assigned assertThat(message.removeStream(stream2)).isTrue(); // streams2 is no longer assigned assertThat(message.removeStream(stream2)).isFalse(); assertThat(message.getStreams()).containsOnly(stream1, stream3); } @Test public void testStreamMutatorsWithIndexSets() { final Stream stream1 = mock(Stream.class); final Stream stream2 = mock(Stream.class); final Stream stream3 = mock(Stream.class); final IndexSet indexSet1 = mock(IndexSet.class); final IndexSet indexSet2 = mock(IndexSet.class); assertThat(message.getIndexSets()).isEmpty(); when(stream1.getIndexSet()).thenReturn(indexSet1); when(stream2.getIndexSet()).thenReturn(indexSet1); when(stream3.getIndexSet()).thenReturn(indexSet2); message.addStream(stream1); message.addStreams(Sets.newHashSet(stream2, stream3)); assertThat(message.getIndexSets()).containsOnly(indexSet1, indexSet2); message.removeStream(stream3); assertThat(message.getIndexSets()).containsOnly(indexSet1); final Set<IndexSet> indexSets = message.getIndexSets(); message.addStream(stream3); // getIndexSets is a copy and doesn't change after mutations assertThat(indexSets).containsOnly(indexSet1); } @Test public void testGetStreamIds() throws Exception { message.addField("streams", Lists.newArrayList("stream-id")); assertThat(message.getStreamIds()).containsOnly("stream-id"); } @Test public void testGetAndSetFilterOut() throws Exception { assertFalse(message.getFilterOut()); message.setFilterOut(true); assertTrue(message.getFilterOut()); message.setFilterOut(false); assertFalse(message.getFilterOut()); } @Test public void testGetId() throws Exception { final Pattern pattern = Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"); assertTrue(pattern.matcher(message.getId()).matches()); } @Test public void testGetTimestamp() { try { final DateTime timestamp = message.getTimestamp(); assertNotNull(timestamp); assertEquals(originalTimestamp.getZone(), timestamp.getZone()); } catch (ClassCastException e) { fail("timestamp wasn't a DateTime " + e.getMessage()); } } @Test public void testTimestampAsDate() { final DateTime dateTime = new DateTime(2015, 9, 8, 0, 0, DateTimeZone.UTC); message.addField(Message.FIELD_TIMESTAMP, dateTime.toDate()); final Map<String, Object> elasticSearchObject = message.toElasticSearchObject(invalidTimestampMeter); final Object esTimestampFormatted = elasticSearchObject.get(Message.FIELD_TIMESTAMP); assertEquals("Setting message timestamp as java.util.Date results in correct format for elasticsearch", Tools.buildElasticSearchTimeFormat(dateTime), esTimestampFormatted); } @Test public void testGetMessage() throws Exception { assertEquals("foo", message.getMessage()); } @Test public void testGetSource() throws Exception { assertEquals("bar", message.getSource()); } @Test public void testValidKeys() throws Exception { assertTrue(Message.validKey("foo123")); assertTrue(Message.validKey("foo-bar123")); assertTrue(Message.validKey("foo_bar123")); assertTrue(Message.validKey("foo.bar123")); assertTrue(Message.validKey("foo@bar")); assertTrue(Message.validKey("123")); assertTrue(Message.validKey("")); assertFalse(Message.validKey("foo bar")); assertFalse(Message.validKey("foo+bar")); assertFalse(Message.validKey("foo$bar")); assertFalse(Message.validKey(" ")); } @Test public void testToElasticSearchObject() throws Exception { message.addField("field1", "wat"); message.addField("field2", "that"); message.addField(Message.FIELD_STREAMS, Collections.singletonList("test-stream")); final Map<String, Object> object = message.toElasticSearchObject(invalidTimestampMeter); assertEquals("foo", object.get("message")); assertEquals("bar", object.get("source")); assertEquals("wat", object.get("field1")); assertEquals("that", object.get("field2")); assertEquals(Tools.buildElasticSearchTimeFormat((DateTime) message.getField("timestamp")), object.get("timestamp")); @SuppressWarnings("unchecked") final Collection<String> streams = (Collection<String>) object.get("streams"); assertThat(streams).containsOnly("test-stream"); } @Test public void testToElasticSearchObjectWithInvalidKey() throws Exception { message.addField("field.3", "dot"); final Map<String, Object> object = message.toElasticSearchObject(invalidTimestampMeter); // Elasticsearch >=2.0 does not allow "." in keys. Make sure we replace them before writing the message. assertEquals("#toElasticsearchObject() should replace \".\" in keys with a \"_\"", "dot", object.get("field_3")); assertEquals("foo", object.get("message")); assertEquals("bar", object.get("source")); assertEquals(Tools.buildElasticSearchTimeFormat((DateTime) message.getField("timestamp")), object.get("timestamp")); @SuppressWarnings("unchecked") final Collection<String> streams = (Collection<String>) object.get("streams"); assertThat(streams).isEmpty(); } @Test public void testToElasticSearchObjectWithoutDateTimeTimestamp() throws Exception { message.addField("timestamp", "time!"); final Meter errorMeter = metricRegistry.meter("test-meter"); final Map<String, Object> object = message.toElasticSearchObject(errorMeter); assertNotEquals("time!", object.get("timestamp")); assertEquals(1, errorMeter.getCount()); } @Test public void testToElasticSearchObjectWithStreams() throws Exception { final Stream stream = mock(Stream.class); when(stream.getId()).thenReturn("stream-id"); when(stream.getIndexSet()).thenReturn(mock(IndexSet.class)); message.addStream(stream); final Map<String, Object> object = message.toElasticSearchObject(invalidTimestampMeter); @SuppressWarnings("unchecked") final Collection<String> streams = (Collection<String>) object.get("streams"); assertThat(streams).containsOnly("stream-id"); } @Test public void testIsComplete() throws Exception { Message message = new Message("message", "source", Tools.nowUTC()); assertTrue(message.isComplete()); message = new Message("message", "", Tools.nowUTC()); assertTrue(message.isComplete()); message = new Message("message", null, Tools.nowUTC()); assertTrue(message.isComplete()); message = new Message("", "source", Tools.nowUTC()); assertFalse(message.isComplete()); message = new Message(null, "source", Tools.nowUTC()); assertFalse(message.isComplete()); } @Test public void testGetValidationErrorsWithEmptyMessage() throws Exception { final Message message = new Message("", "source", Tools.nowUTC()); assertEquals("message is empty, ", message.getValidationErrors()); } @Test public void testGetValidationErrorsWithNullMessage() throws Exception { final Message message = new Message(null, "source", Tools.nowUTC()); assertEquals("message is missing, ", message.getValidationErrors()); } @Test public void testGetFields() throws Exception { final Map<String, Object> fields = message.getFields(); assertEquals(message.getId(), fields.get("_id")); assertEquals(message.getMessage(), fields.get("message")); assertEquals(message.getSource(), fields.get("source")); assertEquals(message.getField("timestamp"), fields.get("timestamp")); } @Test(expected = UnsupportedOperationException.class) public void testGetFieldsReturnsImmutableMap() throws Exception { final Map<String, Object> fields = message.getFields(); fields.put("foo", "bar"); } @Test public void testGetFieldNames() throws Exception { assertTrue("Missing fields in set!", symmetricDifference(message.getFieldNames(), Sets.newHashSet("_id", "timestamp", "source", "message")).isEmpty()); message.addField("testfield", "testvalue"); assertTrue("Missing fields in set!", symmetricDifference(message.getFieldNames(), Sets.newHashSet("_id", "timestamp", "source", "message", "testfield")).isEmpty()); } @Test(expected = UnsupportedOperationException.class) public void testGetFieldNamesReturnsUnmodifiableSet() throws Exception { final Set<String> fieldNames = message.getFieldNames(); fieldNames.remove("_id"); } @Test public void testHasField() throws Exception { assertFalse(message.hasField("__foo__")); message.addField("__foo__", "bar"); assertTrue(message.hasField("__foo__")); } @Test public void testDateConvertedToDateTime() { final Message message = new Message("", "source", Tools.nowUTC()); final Date dateObject = DateTime.parse("2010-07-30T16:03:25Z").toDate(); message.addField(Message.FIELD_TIMESTAMP, dateObject); assertEquals(message.getTimestamp().toDate(), dateObject); assertEquals(message.getField(Message.FIELD_TIMESTAMP).getClass(), DateTime.class); } @Test public void getStreamIdsReturnsStreamsIdsIfFieldDoesNotExist() { final Message message = new Message("", "source", Tools.nowUTC()); final Stream stream = mock(Stream.class); when(stream.getId()).thenReturn("test"); message.addStream(stream); assertThat(message.getStreamIds()).containsOnly("test"); } @Test public void getStreamIdsReturnsStreamsFieldContentsIfFieldDoesExist() { final Message message = new Message("", "source", Tools.nowUTC()); final Stream stream = mock(Stream.class); when(stream.getId()).thenReturn("test1"); message.addField("streams", Collections.singletonList("test2")); message.addStream(stream); assertThat(message.getStreamIds()).containsOnly("test1", "test2"); } }