diff options
author | Simon Brandhof <simon.brandhof@sonarsource.com> | 2016-11-10 11:46:04 +0100 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@sonarsource.com> | 2016-11-14 12:18:50 +0100 |
commit | 34cdbe891e9b45f42d35ee6deb2776cdfe50b431 (patch) | |
tree | 8078d1cbff577fa65439fb1a44523f02b41508f0 /server | |
parent | ba7e66a7f6e96c685a25dfc2def404841b7741f6 (diff) | |
download | sonarqube-34cdbe891e9b45f42d35ee6deb2776cdfe50b431.tar.gz sonarqube-34cdbe891e9b45f42d35ee6deb2776cdfe50b431.zip |
SONAR-8353 persist deliveries in Compute Engine
Deliveries older than 1 month are purged.
Diffstat (limited to 'server')
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()) |