Browse Source

SONAR-8353 persist deliveries in Compute Engine

Deliveries older than 1 month are purged.
tags/6.2-RC1
Simon Brandhof 7 years ago
parent
commit
34cdbe891e
13 changed files with 390 additions and 46 deletions
  1. 13
    1
      server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/Webhook.java
  2. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookCallerImpl.java
  3. 25
    13
      server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDelivery.java
  4. 80
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDeliveryStorage.java
  5. 1
    0
      server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookModule.java
  6. 12
    9
      server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTask.java
  7. 4
    4
      server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/TestWebhookCaller.java
  8. 7
    5
      server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookCallerImplTest.java
  9. 124
    0
      server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDeliveryStorageTest.java
  10. 82
    0
      server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDeliveryTest.java
  11. 14
    2
      server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTaskTest.java
  12. 1
    1
      sonar-db/src/main/java/org/sonar/db/webhook/WebhookDeliveryDto.java
  13. 25
    9
      sonar-db/src/test/java/org/sonar/db/webhook/WebhookDeliveryDaoTest.java

+ 13
- 1
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/Webhook.java View File

@@ -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;
}

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookCallerImpl.java View File

@@ -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();
}

+ 25
- 13
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDelivery.java View File

@@ -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;
}


+ 80
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDeliveryStorage.java View File

@@ -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;
}
}

+ 1
- 0
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookModule.java View File

@@ -26,6 +26,7 @@ public class WebhookModule extends Module {
protected void configureModule() {
add(
WebhookCallerImpl.class,
WebhookDeliveryStorage.class,
WebhookPostTask.class);
}
}

+ 12
- 9
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTask.java View File

@@ -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));
}
}
}

+ 4
- 4
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/TestWebhookCaller.java View File

@@ -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;

+ 7
- 5
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookCallerImplTest.java View File

@@ -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);

+ 124
- 0
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDeliveryStorageTest.java View File

@@ -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);
}
}

+ 82
- 0
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookDeliveryTest.java View File

@@ -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);
}
}

+ 14
- 2
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTaskTest.java View File

@@ -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())

+ 1
- 1
sonar-db/src/main/java/org/sonar/db/webhook/WebhookDeliveryDto.java View File

@@ -59,8 +59,8 @@ public class WebhookDeliveryDto extends WebhookDeliveryLiteDto<WebhookDeliveryDt
.append("httpStatus", httpStatus)
.append("durationMs", durationMs)
.append("url", url)
.append("createdAt", createdAt)
.append("errorStacktrace", errorStacktrace)
.append("createdAt", createdAt)
.toString();
}
}

+ 25
- 9
sonar-db/src/test/java/org/sonar/db/webhook/WebhookDeliveryDaoTest.java View File

@@ -20,8 +20,8 @@
package org.sonar.db.webhook;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -32,11 +32,10 @@ import org.sonar.db.DbTester;

import static org.assertj.core.api.Assertions.assertThat;


public class WebhookDeliveryDaoTest {

private static final long DATE_1 = 1_999_000L;
@Rule
public final DbTester dbTester = DbTester.create(System2.INSTANCE).setDisableDefaultOrganization(true);

@@ -47,6 +46,11 @@ public class WebhookDeliveryDaoTest {
private final DbSession dbSession = dbTester.getSession();
private final WebhookDeliveryDao underTest = dbClient.webhookDeliveryDao();

@Test
public void selectByUuid_returns_empty_if_uuid_does_not_exist() {
assertThat(underTest.selectByUuid(dbSession, "missing")).isEmpty();
}

@Test
public void insert_row_with_only_mandatory_columns() {
WebhookDeliveryDto dto = newDto("DELIVERY_1", "COMPONENT_1", "TASK_1");
@@ -80,7 +84,7 @@ public class WebhookDeliveryDaoTest {
}

@Test
public void delete_rows_before_date() {
public void deleteComponentBeforeDate_deletes_rows_before_date() {
underTest.insert(dbSession, newDto("DELIVERY_1", "COMPONENT_1", "TASK_1").setCreatedAt(1_000_000L));
underTest.insert(dbSession, newDto("DELIVERY_2", "COMPONENT_1", "TASK_2").setCreatedAt(2_000_000L));
underTest.insert(dbSession, newDto("DELIVERY_3", "COMPONENT_2", "TASK_3").setCreatedAt(1_000_000L));
@@ -88,12 +92,24 @@ public class WebhookDeliveryDaoTest {
// should delete the old delivery on COMPONENT_1 and keep the one of COMPONENT_2
underTest.deleteComponentBeforeDate(dbSession, "COMPONENT_1", 1_500_000L);

List<Object> uuids = dbTester.select(dbSession, "select uuid as \"uuid\" from webhook_deliveries")
.stream()
.map(columns -> columns.get("uuid"))
.collect(Collectors.toList());
assertThat(uuids).containsOnly("DELIVERY_2", "DELIVERY_3");
List<Map<String, Object>> uuids = dbTester.select(dbSession, "select uuid as \"uuid\" from webhook_deliveries");
assertThat(uuids).extracting(column -> column.get("uuid")).containsOnly("DELIVERY_2", "DELIVERY_3");
}

@Test
public void deleteComponentBeforeDate_does_nothing_on_empty_table() {
underTest.deleteComponentBeforeDate(dbSession, "COMPONENT_1", 1_500_000L);

assertThat(dbTester.countRowsOfTable(dbSession, "webhook_deliveries")).isEqualTo(0);
}

@Test
public void deleteComponentBeforeDate_does_nothing_on_invalid_uuid() {
underTest.insert(dbSession, newDto("DELIVERY_1", "COMPONENT_1", "TASK_1").setCreatedAt(1_000_000L));

underTest.deleteComponentBeforeDate(dbSession, "COMPONENT_2", 1_500_000L);

assertThat(dbTester.countRowsOfTable(dbSession, "webhook_deliveries")).isEqualTo(1);
}

private void verifyMandatoryFields(WebhookDeliveryDto expected, WebhookDeliveryDto actual) {

Loading…
Cancel
Save