aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@sonarsource.com>2016-11-10 11:46:04 +0100
committerSimon Brandhof <simon.brandhof@sonarsource.com>2016-11-14 12:18:50 +0100
commit34cdbe891e9b45f42d35ee6deb2776cdfe50b431 (patch)
tree8078d1cbff577fa65439fb1a44523f02b41508f0 /server
parentba7e66a7f6e96c685a25dfc2def404841b7741f6 (diff)
downloadsonarqube-34cdbe891e9b45f42d35ee6deb2776cdfe50b431.tar.gz
sonarqube-34cdbe891e9b45f42d35ee6deb2776cdfe50b431.zip
SONAR-8353 persist deliveries in Compute Engine
Deliveries older than 1 month are purged.
Diffstat (limited to 'server')
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/Webhook.java14
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookCallerImpl.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDelivery.java38
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDeliveryStorage.java80
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookModule.java1
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTask.java21
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/TestWebhookCaller.java8
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookCallerImplTest.java12
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDeliveryStorageTest.java124
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDeliveryTest.java82
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTaskTest.java16
11 files changed, 364 insertions, 36 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/Webhook.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/Webhook.java
index 18c1de9ff67..580768cc79d 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/Webhook.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/Webhook.java
@@ -26,14 +26,26 @@ import static java.util.Objects.requireNonNull;
@Immutable
public class Webhook {
+ private final String componentUuid;
+ private final String ceTaskUuid;
private final String name;
private final String url;
- public Webhook(String name, String url) {
+ public Webhook(String componentUuid, String ceTaskUuid, String name, String url) {
+ this.componentUuid = requireNonNull(componentUuid);
+ this.ceTaskUuid = requireNonNull(ceTaskUuid);
this.name = requireNonNull(name);
this.url = requireNonNull(url);
}
+ public String getComponentUuid() {
+ return componentUuid;
+ }
+
+ public String getCeTaskUuid() {
+ return ceTaskUuid;
+ }
+
public String getName() {
return name;
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookCallerImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookCallerImpl.java
index 26f618bb2bd..60a84bd7330 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookCallerImpl.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookCallerImpl.java
@@ -59,9 +59,9 @@ public class WebhookCallerImpl implements WebhookCaller {
try {
Response response = okHttpClient.newCall(request.build()).execute();
builder.setHttpStatus(response.code());
- builder.setDurationInMs(system.now() - startedAt);
+ builder.setDurationInMs((int)(system.now() - startedAt));
} catch (IOException e) {
- builder.setThrowable(e);
+ builder.setError(e);
}
return builder.build();
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDelivery.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDelivery.java
index a2623450e82..ea707c581b0 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDelivery.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDelivery.java
@@ -23,6 +23,7 @@ import java.util.Optional;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
+import static com.google.common.base.Throwables.getRootCause;
import static java.util.Objects.requireNonNull;
/**
@@ -34,9 +35,9 @@ public class WebhookDelivery {
private final Webhook webhook;
private final WebhookPayload payload;
private final Integer httpStatus;
- private final Long durationInMs;
+ private final Integer durationInMs;
private final long at;
- private final Throwable throwable;
+ private final Throwable error;
private WebhookDelivery(Builder builder) {
this.webhook = requireNonNull(builder.webhook);
@@ -44,7 +45,7 @@ public class WebhookDelivery {
this.httpStatus = builder.httpStatus;
this.durationInMs = builder.durationInMs;
this.at = builder.at;
- this.throwable = builder.throwable;
+ this.error = builder.error;
}
public Webhook getWebhook() {
@@ -56,7 +57,7 @@ public class WebhookDelivery {
}
/**
- * @return the HTTP status if {@link #getThrowable()} is empty, else returns
+ * @return the HTTP status if {@link #getError()} is empty, else returns
* {@link Optional#empty()}
*/
public Optional<Integer> getHttpStatus() {
@@ -64,10 +65,10 @@ public class WebhookDelivery {
}
/**
- * @return the duration in milliseconds if {@link #getThrowable()} is empty,
+ * @return the duration in milliseconds if {@link #getError()} is empty,
* else returns {@link Optional#empty()}
*/
- public Optional<Long> getDurationInMs() {
+ public Optional<Integer> getDurationInMs() {
return Optional.ofNullable(durationInMs);
}
@@ -82,17 +83,28 @@ public class WebhookDelivery {
* @return the error raised if the request could not be executed due to a connectivity
* problem or timeout
*/
- public Optional<Throwable> getThrowable() {
- return Optional.ofNullable(throwable);
+ public Optional<Throwable> getError() {
+ return Optional.ofNullable(error);
+ }
+
+ /**
+ * @return the cause message of {@link #getError()}, Optional.empty() is error is not set.
+ */
+ public Optional<String> getErrorMessage() {
+ return error != null ? Optional.ofNullable(getRootCause(error).getMessage()) : Optional.empty();
+ }
+
+ public boolean isSuccess() {
+ return httpStatus != null && httpStatus >= 200 && httpStatus < 300;
}
public static class Builder {
private Webhook webhook;
private WebhookPayload payload;
private Integer httpStatus;
- private Long durationInMs;
+ private Integer durationInMs;
private long at;
- private Throwable throwable;
+ private Throwable error;
public Builder setWebhook(Webhook w) {
this.webhook = w;
@@ -109,7 +121,7 @@ public class WebhookDelivery {
return this;
}
- public Builder setDurationInMs(@Nullable Long durationInMs) {
+ public Builder setDurationInMs(@Nullable Integer durationInMs) {
this.durationInMs = durationInMs;
return this;
}
@@ -119,8 +131,8 @@ public class WebhookDelivery {
return this;
}
- public Builder setThrowable(@Nullable Throwable t) {
- this.throwable = t;
+ public Builder setError(@Nullable Throwable t) {
+ this.error = t;
return this;
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDeliveryStorage.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDeliveryStorage.java
new file mode 100644
index 00000000000..c9ea30bd335
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDeliveryStorage.java
@@ -0,0 +1,80 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.task.projectanalysis.webhook;
+
+import com.google.common.base.Throwables;
+import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.webhook.WebhookDeliveryDao;
+import org.sonar.db.webhook.WebhookDeliveryDto;
+
+/**
+ * Persist and purge {@link WebhookDelivery} into database
+ */
+@ComputeEngineSide
+public class WebhookDeliveryStorage {
+
+ private static final long ALIVE_DELAY_MS = 30L * 24 * 60 * 60 * 1000;
+
+ private final DbClient dbClient;
+ private final System2 system;
+ private final UuidFactory uuidFactory;
+
+ public WebhookDeliveryStorage(DbClient dbClient, System2 system, UuidFactory uuidFactory) {
+ this.dbClient = dbClient;
+ this.system = system;
+ this.uuidFactory = uuidFactory;
+ }
+
+ public void persist(WebhookDelivery delivery) {
+ WebhookDeliveryDao dao = dbClient.webhookDeliveryDao();
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ dao.insert(dbSession, toDto(delivery));
+ dbSession.commit();
+ }
+ }
+
+ public void purge(String componentUuid) {
+ long beforeDate = system.now() - ALIVE_DELAY_MS;
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ dbClient.webhookDeliveryDao().deleteComponentBeforeDate(dbSession, componentUuid, beforeDate);
+ dbSession.commit();
+ }
+ }
+
+ private WebhookDeliveryDto toDto(WebhookDelivery delivery) {
+ WebhookDeliveryDto dto = new WebhookDeliveryDto();
+ dto.setUuid(uuidFactory.create());
+ dto.setComponentUuid(delivery.getWebhook().getComponentUuid());
+ dto.setCeTaskUuid(delivery.getWebhook().getCeTaskUuid());
+ dto.setName(delivery.getWebhook().getName());
+ dto.setUrl(delivery.getWebhook().getUrl());
+ dto.setSuccess(delivery.isSuccess());
+ dto.setHttpStatus(delivery.getHttpStatus().orElse(null));
+ dto.setDurationMs(delivery.getDurationInMs().orElse(null));
+ dto.setErrorStacktrace(delivery.getError().map(Throwables::getStackTraceAsString).orElse(null));
+ dto.setPayload(delivery.getPayload().toJson());
+ dto.setCreatedAt(delivery.getAt());
+ return dto;
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookModule.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookModule.java
index 1bc200acdc6..ad88d541e82 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookModule.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookModule.java
@@ -26,6 +26,7 @@ public class WebhookModule extends Module {
protected void configureModule() {
add(
WebhookCallerImpl.class,
+ WebhookDeliveryStorage.class,
WebhookPostTask.class);
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTask.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTask.java
index 8ec33dd1eac..81406a23203 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTask.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTask.java
@@ -32,7 +32,6 @@ import org.sonar.core.util.stream.Collectors;
import org.sonar.server.computation.task.projectanalysis.component.SettingsRepository;
import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolder;
-import static com.google.common.base.Throwables.getRootCause;
import static java.lang.String.format;
public class WebhookPostTask implements PostProjectAnalysisTask {
@@ -42,11 +41,14 @@ public class WebhookPostTask implements PostProjectAnalysisTask {
private final TreeRootHolder rootHolder;
private final SettingsRepository settingsRepository;
private final WebhookCaller caller;
+ private final WebhookDeliveryStorage deliveryStorage;
- public WebhookPostTask(TreeRootHolder rootHolder, SettingsRepository settingsRepository, WebhookCaller caller) {
+ public WebhookPostTask(TreeRootHolder rootHolder, SettingsRepository settingsRepository, WebhookCaller caller,
+ WebhookDeliveryStorage deliveryStorage) {
this.rootHolder = rootHolder;
this.settingsRepository = settingsRepository;
this.caller = caller;
+ this.deliveryStorage = deliveryStorage;
}
@Override
@@ -55,10 +57,10 @@ public class WebhookPostTask implements PostProjectAnalysisTask {
Iterable<String> webhookProps = Iterables.concat(
getWebhookProperties(settings, WebhookProperties.GLOBAL_KEY),
- getWebhookProperties(settings, WebhookProperties.PROJECT_KEY)
- );
+ getWebhookProperties(settings, WebhookProperties.PROJECT_KEY));
if (!Iterables.isEmpty(webhookProps)) {
process(settings, analysis, webhookProps);
+ deliveryStorage.purge(analysis.getProject().getUuid());
}
}
@@ -76,21 +78,22 @@ public class WebhookPostTask implements PostProjectAnalysisTask {
String url = settings.getString(format("%s.%s", webhookProp, WebhookProperties.URL_FIELD));
// as webhooks are defined as property sets, we can't ensure validity of fields on creation.
if (name != null && url != null) {
- Webhook webhook = new Webhook(name, url);
+ Webhook webhook = new Webhook(analysis.getProject().getUuid(), analysis.getCeTask().getId(), name, url);
WebhookDelivery delivery = caller.call(webhook, payload);
log(delivery);
+ deliveryStorage.persist(delivery);
}
}
}
private static void log(WebhookDelivery delivery) {
- Optional<Throwable> throwable = delivery.getThrowable();
- if (throwable.isPresent()) {
+ Optional<String> error = delivery.getErrorMessage();
+ if (error.isPresent()) {
LOGGER.debug("Failed to send webhook '{}' | url={} | message={}",
- delivery.getWebhook().getName(), delivery.getWebhook().getUrl(), getRootCause(throwable.get()).getMessage());
+ delivery.getWebhook().getName(), delivery.getWebhook().getUrl(), error.get());
} else {
LOGGER.debug("Sent webhook '{}' | url={} | time={}ms | status={}",
- delivery.getWebhook().getName(), delivery.getWebhook().getUrl(), delivery.getDurationInMs().orElse(-1L), delivery.getHttpStatus().orElse(-1));
+ delivery.getWebhook().getName(), delivery.getWebhook().getUrl(), delivery.getDurationInMs().orElse(-1), delivery.getHttpStatus().orElse(-1));
}
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/TestWebhookCaller.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/TestWebhookCaller.java
index 1e82369282d..dee4f72ee9e 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/TestWebhookCaller.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/TestWebhookCaller.java
@@ -31,7 +31,7 @@ public class TestWebhookCaller implements WebhookCaller {
private final Queue<Item> deliveries = new LinkedList<>();
private final AtomicInteger countSent = new AtomicInteger(0);
- public TestWebhookCaller enqueueSuccess(long at, int httpCode, long durationMs) {
+ public TestWebhookCaller enqueueSuccess(long at, int httpCode, int durationMs) {
deliveries.add(new Item(at, httpCode, durationMs, null));
return this;
}
@@ -49,7 +49,7 @@ public class TestWebhookCaller implements WebhookCaller {
.setAt(item.at)
.setHttpStatus(item.httpCode)
.setDurationInMs(item.durationMs)
- .setThrowable(item.throwable)
+ .setError(item.throwable)
.setPayload(payload)
.setWebhook(webhook)
.build();
@@ -62,10 +62,10 @@ public class TestWebhookCaller implements WebhookCaller {
private static class Item {
final long at;
final Integer httpCode;
- final Long durationMs;
+ final Integer durationMs;
final Throwable throwable;
- Item(long at, @Nullable Integer httpCode, @Nullable Long durationMs, @Nullable Throwable throwable) {
+ Item(long at, @Nullable Integer httpCode, @Nullable Integer durationMs, @Nullable Throwable throwable) {
this.at = at;
this.httpCode = httpCode;
this.durationMs = durationMs;
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookCallerImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookCallerImplTest.java
index 474974fa5d1..ccaa5885ecd 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookCallerImplTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookCallerImplTest.java
@@ -39,6 +39,8 @@ import static org.assertj.core.api.Assertions.assertThat;
public class WebhookCallerImplTest {
private static final long NOW = 1_500_000_000_000L;
+ private static final String PROJECT_UUID = "P_UUID1";
+ private static final String CE_TASK_UUID = "CE_UUID1";
@Rule
public MockWebServer server = new MockWebServer();
@@ -47,15 +49,15 @@ public class WebhookCallerImplTest {
@Test
public void send_posts_payload_to_http_server() throws Exception {
- Webhook webhook = new Webhook("my-webhook", server.url("/ping").toString());
+ Webhook webhook = new Webhook(PROJECT_UUID, CE_TASK_UUID, "my-webhook", server.url("/ping").toString());
WebhookPayload payload = new WebhookPayload("P1", "{the payload}");
server.enqueue(new MockResponse().setBody("pong").setResponseCode(201));
WebhookDelivery delivery = newSender().call(webhook, payload);
assertThat(delivery.getHttpStatus().get()).isEqualTo(201);
- assertThat(delivery.getDurationInMs().get()).isGreaterThanOrEqualTo(0L);
- assertThat(delivery.getThrowable()).isEmpty();
+ assertThat(delivery.getDurationInMs().get()).isGreaterThanOrEqualTo(0);
+ assertThat(delivery.getError()).isEmpty();
assertThat(delivery.getAt()).isEqualTo(NOW);
assertThat(delivery.getWebhook()).isSameAs(webhook);
assertThat(delivery.getPayload()).isSameAs(payload);
@@ -71,7 +73,7 @@ public class WebhookCallerImplTest {
@Test
public void send_does_not_throw_exception_on_errors() throws Exception {
- Webhook webhook = new Webhook("my-webhook", server.url("/ping").toString());
+ Webhook webhook = new Webhook(PROJECT_UUID, CE_TASK_UUID, "my-webhook", server.url("/ping").toString());
WebhookPayload payload = new WebhookPayload("P1", "{the payload}");
server.shutdown();
@@ -79,7 +81,7 @@ public class WebhookCallerImplTest {
assertThat(delivery.getHttpStatus()).isEmpty();
assertThat(delivery.getDurationInMs()).isEmpty();
- assertThat(delivery.getThrowable().get().getMessage()).startsWith("Failed to connect to");
+ assertThat(delivery.getErrorMessage().get()).startsWith("Failed to connect");
assertThat(delivery.getAt()).isEqualTo(NOW);
assertThat(delivery.getWebhook()).isSameAs(webhook);
assertThat(delivery.getPayload()).isSameAs(payload);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDeliveryStorageTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDeliveryStorageTest.java
new file mode 100644
index 00000000000..5b5fe6e999e
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDeliveryStorageTest.java
@@ -0,0 +1,124 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.task.projectanalysis.webhook;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.webhook.WebhookDeliveryDto;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class WebhookDeliveryStorageTest {
+
+ private static final String DELIVERY_UUID = "abcde1234";
+ private static final long NOW = 1_500_000_000_000L;
+ private static final long TWO_MONTHS_AGO = NOW - 60L * 24 * 60 * 60 * 1000;
+ private static final long TWO_WEEKS_AGO = NOW - 14L * 24 * 60 * 60 * 1000;
+
+ private final System2 system = mock(System2.class);
+
+ @Rule
+ public final DbTester dbTester = DbTester.create(system).setDisableDefaultOrganization(true);
+
+ private DbClient dbClient = dbTester.getDbClient();
+ private DbSession dbSession = dbTester.getSession();
+ private UuidFactory uuidFactory = mock(UuidFactory.class);
+ private WebhookDeliveryStorage underTest = new WebhookDeliveryStorage(dbClient, system, uuidFactory);
+
+ @Test
+ public void persist_generates_uuid_then_inserts_record() {
+ when(uuidFactory.create()).thenReturn(DELIVERY_UUID);
+ WebhookDelivery delivery = newBuilderTemplate().build();
+
+ underTest.persist(delivery);
+
+ WebhookDeliveryDto dto = dbClient.webhookDeliveryDao().selectByUuid(dbSession, DELIVERY_UUID).get();
+ assertThat(dto.getUuid()).isEqualTo(DELIVERY_UUID);
+ assertThat(dto.getComponentUuid()).isEqualTo(delivery.getWebhook().getComponentUuid());
+ assertThat(dto.getCeTaskUuid()).isEqualTo(delivery.getWebhook().getCeTaskUuid());
+ assertThat(dto.getName()).isEqualTo(delivery.getWebhook().getName());
+ assertThat(dto.getUrl()).isEqualTo(delivery.getWebhook().getUrl());
+ assertThat(dto.getCreatedAt()).isEqualTo(delivery.getAt());
+ assertThat(dto.getHttpStatus()).isEqualTo(delivery.getHttpStatus().get());
+ assertThat(dto.getDurationMs()).isEqualTo(delivery.getDurationInMs().get());
+ assertThat(dto.getPayload()).isEqualTo(delivery.getPayload().toJson());
+ assertThat(dto.getErrorStacktrace()).isNull();
+ }
+
+ @Test
+ public void persist_error_stacktrace() {
+ when(uuidFactory.create()).thenReturn(DELIVERY_UUID);
+ WebhookDelivery delivery = newBuilderTemplate()
+ .setError(new IOException("fail to connect"))
+ .build();
+
+ underTest.persist(delivery);
+
+ WebhookDeliveryDto dto = dbClient.webhookDeliveryDao().selectByUuid(dbSession, DELIVERY_UUID).get();
+ assertThat(dto.getErrorStacktrace()).contains("java.io.IOException", "fail to connect");
+ }
+
+ @Test
+ public void purge_deletes_records_older_than_one_month_on_the_project() {
+ when(system.now()).thenReturn(NOW);
+ dbClient.webhookDeliveryDao().insert(dbSession, newDto("D1", "PROJECT_1", TWO_MONTHS_AGO));
+ dbClient.webhookDeliveryDao().insert(dbSession, newDto("D2", "PROJECT_1", TWO_WEEKS_AGO));
+ dbClient.webhookDeliveryDao().insert(dbSession, newDto("D3", "PROJECT_2", TWO_MONTHS_AGO));
+ dbSession.commit();
+
+ underTest.purge("PROJECT_1");
+
+ List<Map<String, Object>> uuids = dbTester.select(dbSession, "select uuid as \"uuid\" from webhook_deliveries");
+ // do not purge another project PROJECT_2
+ assertThat(uuids).extracting(column -> column.get("uuid")).containsOnly("D2", "D3");
+ }
+
+ private static WebhookDelivery.Builder newBuilderTemplate() {
+ return new WebhookDelivery.Builder()
+ .setWebhook(new Webhook("COMPONENT1", "TASK1", "Jenkins", "http://jenkins"))
+ .setPayload(new WebhookPayload("my-project", "{json}"))
+ .setAt(1_000_000L)
+ .setHttpStatus(200)
+ .setDurationInMs(1_000);
+ }
+
+ private static WebhookDeliveryDto newDto(String uuid, String componentUuid, long at) {
+ return new WebhookDeliveryDto()
+ .setUuid(uuid)
+ .setComponentUuid(componentUuid)
+ .setCeTaskUuid(randomAlphanumeric(40))
+ .setName("Jenkins")
+ .setUrl("http://jenkins")
+ .setSuccess(true)
+ .setPayload("{json}")
+ .setCreatedAt(at);
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDeliveryTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDeliveryTest.java
new file mode 100644
index 00000000000..32d6d60b3de
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDeliveryTest.java
@@ -0,0 +1,82 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.computation.task.projectanalysis.webhook;
+
+import java.io.IOException;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+
+public class WebhookDeliveryTest {
+
+ @Test
+ public void isSuccess_returns_false_if_failed_to_send_http_request() {
+ WebhookDelivery delivery = newBuilderTemplate()
+ .setError(new IOException("Fail to connect"))
+ .build();
+
+ assertThat(delivery.isSuccess()).isFalse();
+ }
+
+ @Test
+ public void isSuccess_returns_false_if_http_response_returns_error_status() {
+ WebhookDelivery delivery = newBuilderTemplate()
+ .setHttpStatus(404)
+ .build();
+
+ assertThat(delivery.isSuccess()).isFalse();
+ }
+
+ @Test
+ public void isSuccess_returns_true_if_http_response_returns_2xx_code() {
+ WebhookDelivery delivery = newBuilderTemplate()
+ .setHttpStatus(204)
+ .build();
+
+ assertThat(delivery.isSuccess()).isTrue();
+ }
+
+ @Test
+ public void getErrorMessage_returns_empty_if_no_error() {
+ WebhookDelivery delivery = newBuilderTemplate().build();
+
+ assertThat(delivery.getErrorMessage()).isEmpty();
+ }
+
+ @Test
+ public void getErrorMessage_returns_root_cause_message_if_error() {
+ Exception rootCause = new IOException("fail to connect");
+ Exception cause = new IOException("nested", rootCause);
+ WebhookDelivery delivery = newBuilderTemplate()
+ .setError(cause)
+ .build();
+
+ assertThat(delivery.getErrorMessage().get()).isEqualTo("fail to connect");
+ }
+
+ private static WebhookDelivery.Builder newBuilderTemplate() {
+ return new WebhookDelivery.Builder()
+ .setWebhook(mock(Webhook.class))
+ .setPayload(mock(WebhookPayload.class))
+ .setAt(1_000L);
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTaskTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTaskTest.java
index 9e308497d34..dcdfbc8a4eb 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTaskTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTaskTest.java
@@ -33,6 +33,11 @@ import org.sonar.server.computation.task.projectanalysis.component.TestSettingsR
import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolderRule;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.sonar.api.ce.posttask.PostProjectAnalysisTaskTester.newCeTaskBuilder;
import static org.sonar.api.ce.posttask.PostProjectAnalysisTaskTester.newProjectBuilder;
import static org.sonar.api.ce.posttask.PostProjectAnalysisTaskTester.newScannerContextBuilder;
@@ -41,6 +46,7 @@ import static org.sonar.server.computation.task.projectanalysis.component.Report
public class WebhookPostTaskTest {
private static final long NOW = 1_500_000_000_000L;
+ private static final String PROJECT_UUID = "P1_UUID";
@Rule
public LogTester logTester = new LogTester().setLevel(LoggerLevel.DEBUG);
@@ -50,6 +56,7 @@ public class WebhookPostTaskTest {
private final MapSettings settings = new MapSettings();
private final TestWebhookCaller caller = new TestWebhookCaller();
+ private final WebhookDeliveryStorage deliveryStorage = mock(WebhookDeliveryStorage.class);
@Test
public void do_nothing_if_no_webhooks() {
@@ -57,6 +64,7 @@ public class WebhookPostTaskTest {
assertThat(caller.countSent()).isEqualTo(0);
assertThat(logTester.logs(LoggerLevel.DEBUG)).isEmpty();
+ verifyZeroInteractions(deliveryStorage);
}
@Test
@@ -74,6 +82,8 @@ public class WebhookPostTaskTest {
assertThat(caller.countSent()).isEqualTo(2);
assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("Sent webhook 'First' | url=http://url1 | time=1234ms | status=200");
assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("Failed to send webhook 'Second' | url=http://url2 | message=Fail to connect");
+ verify(deliveryStorage, times(2)).persist(any(WebhookDelivery.class));
+ verify(deliveryStorage).purge(PROJECT_UUID);
}
@Test
public void send_project_webhooks() {
@@ -86,11 +96,13 @@ public class WebhookPostTaskTest {
assertThat(caller.countSent()).isEqualTo(1);
assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("Sent webhook 'First' | url=http://url1 | time=1234ms | status=200");
+ verify(deliveryStorage).persist(any(WebhookDelivery.class));
+ verify(deliveryStorage).purge(PROJECT_UUID);
}
private void execute() {
SettingsRepository settingsRepository = new TestSettingsRepository(settings);
- WebhookPostTask task = new WebhookPostTask(rootHolder, settingsRepository, caller);
+ WebhookPostTask task = new WebhookPostTask(rootHolder, settingsRepository, caller, deliveryStorage);
PostProjectAnalysisTaskTester.of(task)
.at(new Date())
@@ -99,7 +111,7 @@ public class WebhookPostTaskTest {
.setId("#1")
.build())
.withProject(newProjectBuilder()
- .setUuid("P1_UUID")
+ .setUuid(PROJECT_UUID)
.setKey("P1")
.setName("Project One")
.build())