3 * Copyright (C) 2009-2017 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.server.computation.task.projectanalysis.webhook;
22 import okhttp3.HttpUrl;
23 import okhttp3.mockwebserver.MockResponse;
24 import okhttp3.mockwebserver.MockWebServer;
25 import okhttp3.mockwebserver.RecordedRequest;
26 import org.junit.Rule;
27 import org.junit.Test;
28 import org.junit.rules.DisableOnDebug;
29 import org.junit.rules.TestRule;
30 import org.junit.rules.Timeout;
31 import org.sonar.api.SonarQubeSide;
32 import org.sonar.api.SonarRuntime;
33 import org.sonar.api.config.MapSettings;
34 import org.sonar.api.internal.SonarRuntimeImpl;
35 import org.sonar.api.utils.System2;
36 import org.sonar.api.utils.Version;
37 import org.sonar.api.utils.internal.TestSystem2;
38 import org.sonar.server.util.OkHttpClientProvider;
40 import static org.assertj.core.api.Assertions.assertThat;
42 public class WebhookCallerImplTest {
44 private static final long NOW = 1_500_000_000_000L;
45 private static final String PROJECT_UUID = "P_UUID1";
46 private static final String CE_TASK_UUID = "CE_UUID1";
47 private static final String SOME_JSON = "{\"payload\": {}}";
48 private static final WebhookPayload PAYLOAD = new WebhookPayload("P1", SOME_JSON);
51 public MockWebServer server = new MockWebServer();
54 public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60));
56 private System2 system = new TestSystem2().setNow(NOW);
59 public void send_posts_payload_to_http_server() throws Exception {
60 Webhook webhook = new Webhook(PROJECT_UUID, CE_TASK_UUID, "my-webhook", server.url("/ping").toString());
62 server.enqueue(new MockResponse().setBody("pong").setResponseCode(201));
63 WebhookDelivery delivery = newSender().call(webhook, PAYLOAD);
65 assertThat(delivery.getHttpStatus().get()).isEqualTo(201);
66 assertThat(delivery.getDurationInMs().get()).isGreaterThanOrEqualTo(0);
67 assertThat(delivery.getError()).isEmpty();
68 assertThat(delivery.getAt()).isEqualTo(NOW);
69 assertThat(delivery.getWebhook()).isSameAs(webhook);
70 assertThat(delivery.getPayload()).isSameAs(PAYLOAD);
72 RecordedRequest recordedRequest = server.takeRequest();
73 assertThat(recordedRequest.getMethod()).isEqualTo("POST");
74 assertThat(recordedRequest.getPath()).isEqualTo("/ping");
75 assertThat(recordedRequest.getBody().readUtf8()).isEqualTo(PAYLOAD.getJson());
76 assertThat(recordedRequest.getHeader("User-Agent")).isEqualTo("SonarQube/6.2");
77 assertThat(recordedRequest.getHeader("Content-Type")).isEqualTo("application/json; charset=utf-8");
78 assertThat(recordedRequest.getHeader("X-SonarQube-Project")).isEqualTo(PAYLOAD.getProjectKey());
82 public void silently_catch_error_when_external_server_does_not_answer() throws Exception {
83 Webhook webhook = new Webhook(PROJECT_UUID, CE_TASK_UUID, "my-webhook", server.url("/ping").toString());
86 WebhookDelivery delivery = newSender().call(webhook, PAYLOAD);
88 assertThat(delivery.getHttpStatus()).isEmpty();
89 assertThat(delivery.getDurationInMs()).isEmpty();
90 // message can be "Connection refused" or "connect timed out"
91 assertThat(delivery.getErrorMessage().get()).matches("(.*Connection refused.*)|(.*connect timed out.*)");
92 assertThat(delivery.getAt()).isEqualTo(NOW);
93 assertThat(delivery.getWebhook()).isSameAs(webhook);
94 assertThat(delivery.getPayload()).isSameAs(PAYLOAD);
98 public void silently_catch_error_when_url_is_incorrect() throws Exception {
99 Webhook webhook = new Webhook(PROJECT_UUID, CE_TASK_UUID, "my-webhook", "this_is_not_an_url");
101 WebhookDelivery delivery = newSender().call(webhook, PAYLOAD);
103 assertThat(delivery.getHttpStatus()).isEmpty();
104 assertThat(delivery.getDurationInMs()).isEmpty();
105 assertThat(delivery.getError().get()).isInstanceOf(IllegalArgumentException.class);
106 assertThat(delivery.getErrorMessage().get()).isEqualTo("unexpected url: this_is_not_an_url");
107 assertThat(delivery.getAt()).isEqualTo(NOW);
108 assertThat(delivery.getWebhook()).isSameAs(webhook);
109 assertThat(delivery.getPayload()).isSameAs(PAYLOAD);
116 public void redirects_should_be_followed_with_POST_method() throws Exception {
117 Webhook webhook = new Webhook(PROJECT_UUID, CE_TASK_UUID, "my-webhook", server.url("/redirect").toString());
119 // /redirect redirects to /target
120 server.enqueue(new MockResponse().setResponseCode(307).setHeader("Location", server.url("target")));
121 server.enqueue(new MockResponse().setResponseCode(200));
123 WebhookDelivery delivery = newSender().call(webhook, PAYLOAD);
125 assertThat(delivery.getHttpStatus().get()).isEqualTo(200);
126 assertThat(delivery.getDurationInMs().get()).isGreaterThanOrEqualTo(0);
127 assertThat(delivery.getError()).isEmpty();
128 assertThat(delivery.getAt()).isEqualTo(NOW);
129 assertThat(delivery.getWebhook()).isSameAs(webhook);
130 assertThat(delivery.getPayload()).isSameAs(PAYLOAD);
132 takeAndVerifyPostRequest("/redirect");
133 takeAndVerifyPostRequest("/target");
137 public void redirects_throws_ISE_if_header_Location_is_missing() throws Exception {
138 HttpUrl url = server.url("/redirect");
139 Webhook webhook = new Webhook(PROJECT_UUID, CE_TASK_UUID, "my-webhook", url.toString());
141 server.enqueue(new MockResponse().setResponseCode(307));
143 WebhookDelivery delivery = newSender().call(webhook, PAYLOAD);
145 Throwable error = delivery.getError().get();
147 .isInstanceOf(IllegalStateException.class)
148 .hasMessage("Missing HTTP header 'Location' in redirect of " + url);
152 public void redirects_throws_ISE_if_header_Location_does_not_relate_to_a_supported_protocol() throws Exception {
153 HttpUrl url = server.url("/redirect");
154 Webhook webhook = new Webhook(PROJECT_UUID, CE_TASK_UUID, "my-webhook", url.toString());
156 server.enqueue(new MockResponse().setResponseCode(307).setHeader("Location", "ftp://foo"));
158 WebhookDelivery delivery = newSender().call(webhook, PAYLOAD);
160 Throwable error = delivery.getError().get();
162 .isInstanceOf(IllegalStateException.class)
163 .hasMessage("Unsupported protocol in redirect of " + url + " to ftp://foo");
166 private void takeAndVerifyPostRequest(String expectedPath) throws Exception {
167 RecordedRequest redirectedRequest = server.takeRequest();
169 assertThat(redirectedRequest.getMethod()).isEqualTo("POST");
170 assertThat(redirectedRequest.getPath()).isEqualTo(expectedPath);
173 private WebhookCaller newSender() {
174 SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.parse("6.2"), SonarQubeSide.SERVER);
175 return new WebhookCallerImpl(system, new OkHttpClientProvider().provide(new MapSettings(), runtime));