/** * 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.alarmcallbacks; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import okhttp3.OkHttpClient; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; import org.graylog2.alerts.AbstractAlertCondition; import org.graylog2.alerts.types.DummyAlertCondition; import org.graylog2.plugin.Message; import org.graylog2.plugin.MessageSummary; import org.graylog2.plugin.alarms.AlertCondition; import org.graylog2.plugin.alarms.callbacks.AlarmCallbackException; import org.graylog2.plugin.configuration.Configuration; import org.graylog2.plugin.configuration.ConfigurationException; import org.graylog2.plugin.configuration.ConfigurationRequest; import org.graylog2.plugin.configuration.fields.ConfigurationField; import org.graylog2.plugin.streams.Stream; import org.graylog2.shared.bindings.providers.ObjectMapperProvider; import org.graylog2.streams.StreamMock; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; public class HTTPAlarmCallbackTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Rule public ExpectedException expectedException = ExpectedException.none(); private OkHttpClient httpClient; private ObjectMapper objectMapper; private HTTPAlarmCallback alarmCallback; private MockWebServer server; @Before public void setUp() throws Exception { httpClient = new OkHttpClient(); objectMapper = new ObjectMapperProvider().get(); alarmCallback = new HTTPAlarmCallback(httpClient, objectMapper); server = new MockWebServer(); } @After public void shutDown() throws IOException { if (server != null) { server.shutdown(); } } @Test public void initializeStoresConfiguration() throws Exception { final Map<String, Object> configMap = ImmutableMap.of("url", "http://example.com/"); final Configuration configuration = new Configuration(configMap); alarmCallback.initialize(configuration); assertThat(alarmCallback.getAttributes()).isEqualTo(configMap); } @Test public void callSucceedsIfRemoteRequestSucceeds() throws Exception { server.enqueue(new MockResponse().setResponseCode(200)); server.start(); final Configuration configuration = new Configuration(ImmutableMap.of("url", server.url("/").toString())); alarmCallback.initialize(configuration); alarmCallback.checkConfiguration(); final Stream stream = new StreamMock( ImmutableMap.of( "_id", "stream-id", "title", "Stream Title", "description", "Stream Description"), ImmutableList.of() ); final AlertCondition alertCondition = new DummyAlertCondition( stream, "condition-id", new DateTime(2016, 9, 6, 17, 0, DateTimeZone.UTC), "user", ImmutableMap.of(), "Alert Condition Title" ); final List<MessageSummary> messageSummaries = ImmutableList.of( new MessageSummary("graylog_1", new Message("Test message 1", "source1", new DateTime(2016, 9, 6, 17, 0, DateTimeZone.UTC))), new MessageSummary("graylog_2", new Message("Test message 2", "source2", new DateTime(2016, 9, 6, 17, 0, DateTimeZone.UTC))) ); final AlertCondition.CheckResult checkResult = new AbstractAlertCondition.CheckResult( true, alertCondition, "Result Description", new DateTime(2016, 9, 6, 17, 0, DateTimeZone.UTC), messageSummaries ); alarmCallback.call(stream, checkResult); final RecordedRequest request = server.takeRequest(); assertThat(request.getPath()).isEqualTo("/"); assertThat(request.getHeader("Content-Type")).isEqualTo("application/json"); assertThat(request.getBodySize()).isPositive(); final String requestBody = request.getBody().readUtf8(); final JsonNode jsonNode = objectMapper.readTree(requestBody); assertThat(jsonNode.get("check_result").get("matching_messages").size()).isEqualTo(2); assertThat(jsonNode.get("check_result").get("triggered").asBoolean()).isTrue(); assertThat(jsonNode.get("check_result").get("triggered_at").asText()).isEqualTo("2016-09-06T17:00:00.000Z"); assertThat(jsonNode.get("stream").get("id").asText()).isEqualTo("stream-id"); } @Test public void callThrowsAlarmCallbackExceptionIfRemoteServerReturnsError() throws Exception { server.enqueue(new MockResponse().setResponseCode(500)); server.start(); final Configuration configuration = new Configuration(ImmutableMap.of("url", server.url("/").toString())); alarmCallback.initialize(configuration); alarmCallback.checkConfiguration(); final Stream stream = new StreamMock(Collections.singletonMap("_id", "stream-id")); final AlertCondition alertCondition = new DummyAlertCondition( stream, "alert-id", new DateTime(2017, 3, 29, 12, 0, DateTimeZone.UTC), "user", Collections.emptyMap(), "title" ); final AlertCondition.CheckResult checkResult = new AbstractAlertCondition.CheckResult( true, alertCondition, "Result Description", new DateTime(2016, 9, 6, 17, 0, DateTimeZone.UTC), Collections.emptyList() ); expectedException.expect(AlarmCallbackException.class); expectedException.expectMessage("Expected successful HTTP response [2xx] but got [500]."); alarmCallback.call(stream, checkResult); final RecordedRequest request = server.takeRequest(); assertThat(request.getPath()).isEqualTo("/"); assertThat(request.getHeader("Content-Type")).isEqualTo("application/json"); assertThat(request.getBodySize()).isPositive(); } @Test public void callThrowsAlarmCallbackExceptionIfURLIsMalformed() throws Exception { final Configuration configuration = new Configuration(ImmutableMap.of("url", "!FOOBAR")); alarmCallback.initialize(configuration); final Stream stream = new StreamMock(Collections.singletonMap("_id", "stream-id")); final AlertCondition alertCondition = new DummyAlertCondition( stream, "alert-id", new DateTime(2017, 3, 29, 12, 0, DateTimeZone.UTC), "user", Collections.emptyMap(), "title" ); final AlertCondition.CheckResult checkResult = new AbstractAlertCondition.CheckResult( true, alertCondition, "Result Description", new DateTime(2016, 9, 6, 17, 0, DateTimeZone.UTC), Collections.emptyList() ); expectedException.expect(AlarmCallbackException.class); expectedException.expectMessage("Malformed URL: !FOOBAR"); alarmCallback.call(stream, checkResult); } @Test public void callThrowsAlarmCallbackExceptionIfRequestBodyCanNotBeBuilt() throws Exception { final Configuration configuration = new Configuration(ImmutableMap.of("url", "http://example.org")); alarmCallback.initialize(configuration); final Stream stream = mock(Stream.class); final AlertCondition alertCondition = mock(AlertCondition.class); final List<MessageSummary> messageSummaries = ImmutableList.of(); final AlertCondition.CheckResult checkResult = new AbstractAlertCondition.CheckResult( true, alertCondition, "Result Description", new DateTime(2016, 9, 6, 17, 0, DateTimeZone.UTC), messageSummaries ) { @Override public String getResultDescription() { throw new RuntimeException("Boom"); } }; expectedException.expect(AlarmCallbackException.class); expectedException.expectMessage("Unable to serialize alarm"); alarmCallback.call(stream, checkResult); } @Test public void getRequestedConfigurationContainsURLField() throws Exception { final ConfigurationRequest requestedConfiguration = alarmCallback.getRequestedConfiguration(); final ConfigurationField field = requestedConfiguration.getField("url"); assertThat(field).isNotNull(); assertThat(field.getHumanName()).isEqualTo("URL"); assertThat(field.isOptional()).isEqualTo(ConfigurationField.Optional.NOT_OPTIONAL); } @Test public void getNameReturnsNameOfHTTPAlarmCallback() throws Exception { assertThat(alarmCallback.getName()).isEqualTo("HTTP Alarm Callback"); } @Test public void checkConfigurationSucceedsWithValidConfiguration() throws Exception { final Map<String, Object> configMap = ImmutableMap.of("url", "http://example.com/"); final Configuration configuration = new Configuration(configMap); alarmCallback.initialize(configuration); alarmCallback.checkConfiguration(); } @Test public void checkConfigurationFailsWithMissingURL() throws Exception { final Map<String, Object> configMap = ImmutableMap.of(); final Configuration configuration = new Configuration(configMap); alarmCallback.initialize(configuration); expectedException.expect(ConfigurationException.class); expectedException.expectMessage("URL parameter is missing."); alarmCallback.checkConfiguration(); } @Test public void checkConfigurationFailsWithEmptyURL() throws Exception { final Map<String, Object> configMap = ImmutableMap.of("url", ""); final Configuration configuration = new Configuration(configMap); alarmCallback.initialize(configuration); expectedException.expect(ConfigurationException.class); expectedException.expectMessage("URL parameter is missing."); alarmCallback.checkConfiguration(); } @Test public void checkConfigurationFailsWithInvalidURL() throws Exception { final Map<String, Object> configMap = ImmutableMap.of("url", "!Foobar!"); final Configuration configuration = new Configuration(configMap); alarmCallback.initialize(configuration); expectedException.expect(ConfigurationException.class); expectedException.expectMessage("Malformed URL"); alarmCallback.checkConfiguration(); } }