@@ -207,6 +207,11 @@ public class Tester extends ExternalResource implements TesterSession { | |||
return rootSession.qGates(); | |||
} | |||
@Override | |||
public WebhookTester webhooks() { | |||
return rootSession.webhooks(); | |||
} | |||
private static class TesterSessionImpl implements TesterSession { | |||
private final WsClient client; | |||
@@ -262,5 +267,10 @@ public class Tester extends ExternalResource implements TesterSession { | |||
public QGateTester qGates() { | |||
return new QGateTester(this); | |||
} | |||
@Override | |||
public WebhookTester webhooks() { | |||
return new WebhookTester(this); | |||
} | |||
} | |||
} |
@@ -41,4 +41,6 @@ public interface TesterSession { | |||
QGateTester qGates(); | |||
WebhookTester webhooks(); | |||
} |
@@ -0,0 +1,120 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info 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.sonarqube.qa.util; | |||
import java.util.List; | |||
import java.util.Optional; | |||
import java.util.concurrent.atomic.AtomicInteger; | |||
import java.util.function.Consumer; | |||
import javax.annotation.Nullable; | |||
import org.sonarqube.ws.Organizations.Organization; | |||
import org.sonarqube.ws.Projects.CreateWsResponse.Project; | |||
import org.sonarqube.ws.Webhooks.CreateWsResponse.Webhook; | |||
import org.sonarqube.ws.Webhooks.Delivery; | |||
import org.sonarqube.ws.client.webhooks.CreateRequest; | |||
import org.sonarqube.ws.client.webhooks.DeleteRequest; | |||
import org.sonarqube.ws.client.webhooks.DeliveriesRequest; | |||
import org.sonarqube.ws.client.webhooks.DeliveryRequest; | |||
import org.sonarqube.ws.client.webhooks.ListRequest; | |||
import org.sonarqube.ws.client.webhooks.WebhooksService; | |||
import static java.util.Arrays.stream; | |||
import static java.util.Objects.requireNonNull; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
public class WebhookTester { | |||
private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); | |||
private final TesterSession session; | |||
WebhookTester(TesterSession session) { | |||
this.session = session; | |||
} | |||
public WebhooksService service() { | |||
return session.wsClient().webhooks(); | |||
} | |||
public Webhook generate(Consumer<CreateRequest>... populators) { | |||
return generate(null, null, populators); | |||
} | |||
public Webhook generate(Organization organization, Consumer<CreateRequest>... populators) { | |||
return generate(organization, null, populators); | |||
} | |||
public Webhook generate(Project project, Consumer<CreateRequest>... populators) { | |||
return generate(null, project, populators); | |||
} | |||
@SafeVarargs | |||
public final Webhook generate( | |||
@Nullable Organization organization, | |||
@Nullable Project project, | |||
Consumer<CreateRequest>... populators | |||
) { | |||
int id = ID_GENERATOR.getAndIncrement(); | |||
CreateRequest request = new CreateRequest() | |||
.setName("Webhook " + id) | |||
.setUrl("https://webhook-" + id) | |||
.setProject(project != null ? project.getKey(): null) | |||
.setOrganization(organization != null ? organization.getKey() : null); | |||
stream(populators).forEach(p -> p.accept(request)); | |||
return service().create(request).getWebhook(); | |||
} | |||
public void deleteAllGlobal() { | |||
service().list(new ListRequest()).getWebhooksList().forEach(p -> | |||
service().delete(new DeleteRequest().setWebhook(p.getKey())) | |||
); | |||
} | |||
public List<Delivery> getPersistedDeliveries(Project project) { | |||
DeliveriesRequest deliveriesReq = new DeliveriesRequest().setComponentKey(project.getKey()); | |||
return service().deliveries(deliveriesReq).getDeliveriesList(); | |||
} | |||
public Delivery getPersistedDeliveryByName(Project project, String webhookName) { | |||
List<Delivery> deliveries = getPersistedDeliveries(project); | |||
Optional<Delivery> delivery = deliveries.stream().filter(d -> d.getName().equals(webhookName)).findFirst(); | |||
assertThat(delivery).isPresent(); | |||
return delivery.get(); | |||
} | |||
public Delivery getDetailOfPersistedDelivery(Delivery delivery) { | |||
Delivery detail = service().delivery(new DeliveryRequest().setDeliveryId(delivery.getId())).getDelivery(); | |||
return requireNonNull(detail); | |||
} | |||
public void assertThatPersistedDeliveryIsValid(Delivery delivery, @Nullable Project project, @Nullable String url) { | |||
assertThat(delivery.getId()).isNotEmpty(); | |||
assertThat(delivery.getName()).isNotEmpty(); | |||
assertThat(delivery.hasSuccess()).isTrue(); | |||
assertThat(delivery.getHttpStatus()).isGreaterThanOrEqualTo(200); | |||
assertThat(delivery.getDurationMs()).isGreaterThanOrEqualTo(0); | |||
assertThat(delivery.getAt()).isNotEmpty(); | |||
if (project != null) { | |||
assertThat(delivery.getComponentKey()).isEqualTo(project.getKey()); | |||
} | |||
if (url != null) { | |||
assertThat(delivery.getUrl()).startsWith(url); | |||
} | |||
} | |||
} |
@@ -20,9 +20,13 @@ | |||
package org.sonarqube.qa.util.pageobjects; | |||
import com.codeborne.selenide.ElementsCollection; | |||
import com.codeborne.selenide.SelenideElement; | |||
import static com.codeborne.selenide.Condition.cssClass; | |||
import static com.codeborne.selenide.Condition.enabled; | |||
import static com.codeborne.selenide.Condition.exist; | |||
import static com.codeborne.selenide.Condition.text; | |||
import static com.codeborne.selenide.Condition.visible; | |||
import static com.codeborne.selenide.Selenide.$; | |||
import static com.codeborne.selenide.Selenide.$$; | |||
@@ -37,12 +41,55 @@ public class WebhooksPage { | |||
return this; | |||
} | |||
public WebhooksPage hasNoWebhooks() { | |||
$(".boxed-group").shouldHave(text("No webhook defined")); | |||
return this; | |||
} | |||
public WebhooksPage countWebhooks(Integer number) { | |||
getWebhooks().shouldHaveSize(number); | |||
return this; | |||
} | |||
public WebhooksPage createWebhook(String name, String url) { | |||
$(".js-webhook-create").shouldBe(visible).shouldBe(enabled).shouldNotHave(cssClass("disabled")).click(); | |||
modalShouldBeOpen("Create Webhook"); | |||
$("#webhook-name").shouldBe(visible).sendKeys(name); | |||
$("#webhook-url").shouldBe(visible).sendKeys(url); | |||
$("button[type='submit']").shouldBe(visible).click(); | |||
modalShouldBeClosed(); | |||
return this; | |||
} | |||
public WebhooksPage createIsDisabled() { | |||
$(".js-webhook-create").shouldBe(visible).shouldHave(cssClass("disabled")).click(); | |||
modalShouldBeClosed(); | |||
return this; | |||
} | |||
public WebhooksPage deleteWebhook(String webhookName) { | |||
SelenideElement webhook = getWebhook(webhookName); | |||
webhook.$(".dropdown-toggle").shouldBe(visible).click(); | |||
webhook.$(".js-webhook-delete").shouldBe(visible).click(); | |||
modalShouldBeOpen("Delete Webhook"); | |||
$("button.button-red").shouldBe(visible).click(); | |||
modalShouldBeClosed(); | |||
return this; | |||
} | |||
private static SelenideElement getWebhook(String webhookName) { | |||
return getWebhooks().find(text(webhookName)).should(exist); | |||
} | |||
private static ElementsCollection getWebhooks() { | |||
return $$(".boxed-group tbody tr"); | |||
} | |||
private static void modalShouldBeOpen(String title) { | |||
$(".modal-head").shouldBe(visible).shouldHave(text(title)); | |||
} | |||
private static void modalShouldBeClosed() { | |||
$(".modal-head").shouldNot(exist); | |||
} | |||
} |
@@ -0,0 +1,89 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info 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.sonarqube.ws.client.webhooks; | |||
import java.util.List; | |||
import javax.annotation.Generated; | |||
/** | |||
* This is part of the internal API. | |||
* This is a POST request. | |||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/webhooks/create">Further information about this action online (including a response example)</a> | |||
* @since 7.1 | |||
*/ | |||
@Generated("sonar-ws-generator") | |||
public class CreateRequest { | |||
private String name; | |||
private String organization; | |||
private String project; | |||
private String url; | |||
/** | |||
* This is a mandatory parameter. | |||
* Example value: "My Webhook" | |||
*/ | |||
public CreateRequest setName(String name) { | |||
this.name = name; | |||
return this; | |||
} | |||
public String getName() { | |||
return name; | |||
} | |||
/** | |||
* This is part of the internal API. | |||
* Example value: "my-org" | |||
*/ | |||
public CreateRequest setOrganization(String organization) { | |||
this.organization = organization; | |||
return this; | |||
} | |||
public String getOrganization() { | |||
return organization; | |||
} | |||
/** | |||
* Example value: "my_project" | |||
*/ | |||
public CreateRequest setProject(String project) { | |||
this.project = project; | |||
return this; | |||
} | |||
public String getProject() { | |||
return project; | |||
} | |||
/** | |||
* This is a mandatory parameter. | |||
* Example value: "https://www.my-webhook-listener.com/sonar" | |||
*/ | |||
public CreateRequest setUrl(String url) { | |||
this.url = url; | |||
return this; | |||
} | |||
public String getUrl() { | |||
return url; | |||
} | |||
} |
@@ -0,0 +1,48 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info 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.sonarqube.ws.client.webhooks; | |||
import java.util.List; | |||
import javax.annotation.Generated; | |||
/** | |||
* This is part of the internal API. | |||
* This is a POST request. | |||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/webhooks/delete">Further information about this action online (including a response example)</a> | |||
* @since 7.1 | |||
*/ | |||
@Generated("sonar-ws-generator") | |||
public class DeleteRequest { | |||
private String webhook; | |||
/** | |||
* This is a mandatory parameter. | |||
* Example value: "my_project" | |||
*/ | |||
public DeleteRequest setWebhook(String webhook) { | |||
this.webhook = webhook; | |||
return this; | |||
} | |||
public String getWebhook() { | |||
return webhook; | |||
} | |||
} |
@@ -0,0 +1,61 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info 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.sonarqube.ws.client.webhooks; | |||
import java.util.List; | |||
import javax.annotation.Generated; | |||
/** | |||
* This is part of the internal API. | |||
* This is a POST request. | |||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/webhooks/list">Further information about this action online (including a response example)</a> | |||
* @since 7.1 | |||
*/ | |||
@Generated("sonar-ws-generator") | |||
public class ListRequest { | |||
private String organization; | |||
private String project; | |||
/** | |||
* This is part of the internal API. | |||
* Example value: "my-org" | |||
*/ | |||
public ListRequest setOrganization(String organization) { | |||
this.organization = organization; | |||
return this; | |||
} | |||
public String getOrganization() { | |||
return organization; | |||
} | |||
/** | |||
* Example value: "my_project" | |||
*/ | |||
public ListRequest setProject(String project) { | |||
this.project = project; | |||
return this; | |||
} | |||
public String getProject() { | |||
return project; | |||
} | |||
} |
@@ -0,0 +1,76 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info 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.sonarqube.ws.client.webhooks; | |||
import java.util.List; | |||
import javax.annotation.Generated; | |||
/** | |||
* This is part of the internal API. | |||
* This is a POST request. | |||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/webhooks/update">Further information about this action online (including a response example)</a> | |||
* @since 7.1 | |||
*/ | |||
@Generated("sonar-ws-generator") | |||
public class UpdateRequest { | |||
private String name; | |||
private String url; | |||
private String webhook; | |||
/** | |||
* This is a mandatory parameter. | |||
* Example value: "My Webhook" | |||
*/ | |||
public UpdateRequest setName(String name) { | |||
this.name = name; | |||
return this; | |||
} | |||
public String getName() { | |||
return name; | |||
} | |||
/** | |||
* This is a mandatory parameter. | |||
* Example value: "https://www.my-webhook-listener.com/sonar" | |||
*/ | |||
public UpdateRequest setUrl(String url) { | |||
this.url = url; | |||
return this; | |||
} | |||
public String getUrl() { | |||
return url; | |||
} | |||
/** | |||
* This is a mandatory parameter. | |||
* Example value: "my_project" | |||
*/ | |||
public UpdateRequest setWebhook(String webhook) { | |||
this.webhook = webhook; | |||
return this; | |||
} | |||
public String getWebhook() { | |||
return webhook; | |||
} | |||
} |
@@ -26,8 +26,10 @@ import org.sonarqube.ws.client.BaseService; | |||
import org.sonarqube.ws.client.GetRequest; | |||
import org.sonarqube.ws.client.PostRequest; | |||
import org.sonarqube.ws.client.WsConnector; | |||
import org.sonarqube.ws.Webhooks.CreateWsResponse; | |||
import org.sonarqube.ws.Webhooks.DeliveriesWsResponse; | |||
import org.sonarqube.ws.Webhooks.DeliveryWsResponse; | |||
import org.sonarqube.ws.Webhooks.ListWsResponse; | |||
/** | |||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/webhooks">Further information about this web service online</a> | |||
@@ -39,6 +41,38 @@ public class WebhooksService extends BaseService { | |||
super(wsConnector, "api/webhooks"); | |||
} | |||
/** | |||
* | |||
* This is part of the internal API. | |||
* This is a POST request. | |||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/webhooks/create">Further information about this action online (including a response example)</a> | |||
* @since 7.1 | |||
*/ | |||
public CreateWsResponse create(CreateRequest request) { | |||
return call( | |||
new PostRequest(path("create")) | |||
.setParam("name", request.getName()) | |||
.setParam("organization", request.getOrganization()) | |||
.setParam("project", request.getProject()) | |||
.setParam("url", request.getUrl()), | |||
CreateWsResponse.parser()); | |||
} | |||
/** | |||
* | |||
* This is part of the internal API. | |||
* This is a POST request. | |||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/webhooks/delete">Further information about this action online (including a response example)</a> | |||
* @since 7.1 | |||
*/ | |||
public void delete(DeleteRequest request) { | |||
call( | |||
new PostRequest(path("delete")) | |||
.setParam("webhook", request.getWebhook()) | |||
.setMediaType(MediaTypes.JSON) | |||
).content(); | |||
} | |||
/** | |||
* | |||
* This is part of the internal API. | |||
@@ -67,4 +101,36 @@ public class WebhooksService extends BaseService { | |||
.setParam("deliveryId", request.getDeliveryId()), | |||
DeliveryWsResponse.parser()); | |||
} | |||
/** | |||
* | |||
* This is part of the internal API. | |||
* This is a GET request. | |||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/webhooks/list">Further information about this action online (including a response example)</a> | |||
* @since 7.1 | |||
*/ | |||
public ListWsResponse list(ListRequest request) { | |||
return call( | |||
new GetRequest(path("list")) | |||
.setParam("organization", request.getOrganization()) | |||
.setParam("project", request.getProject()), | |||
ListWsResponse.parser()); | |||
} | |||
/** | |||
* | |||
* This is part of the internal API. | |||
* This is a POST request. | |||
* @see <a href="https://next.sonarqube.com/sonarqube/web_api/api/webhooks/update">Further information about this action online (including a response example)</a> | |||
* @since 7.1 | |||
*/ | |||
public void update(UpdateRequest request) { | |||
call( | |||
new PostRequest(path("update")) | |||
.setParam("name", request.getName()) | |||
.setParam("url", request.getUrl()) | |||
.setParam("webhook", request.getWebhook()) | |||
.setMediaType(MediaTypes.JSON) | |||
).content(); | |||
} | |||
} |
@@ -115,7 +115,7 @@ public class ProjectBadgesTest { | |||
} | |||
private void shouldHaveUrl(SelenideElement badgesModal, String url) { | |||
badgesModal.$(".badge-snippet pre") | |||
badgesModal.$(".code-snippet pre") | |||
.shouldBe(Condition.visible) | |||
.shouldHave(Condition.text(url)); | |||
} |
@@ -93,7 +93,18 @@ class ExternalServer extends ExternalResource { | |||
return jetty.getURI().resolve(path).toString(); | |||
} | |||
void waitUntilAllWebHooksCalled(int expectedNumberOfRequests) throws InterruptedException { | |||
// Wait up to 30 seconds max | |||
for (int i = 0; i < 60; i++) { | |||
if (getPayloadRequests().size() == expectedNumberOfRequests) { | |||
break; | |||
} | |||
Thread.sleep(500); | |||
} | |||
} | |||
void clear() { | |||
payloads.clear(); | |||
} | |||
} |
@@ -0,0 +1,139 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info 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.sonarqube.tests.webhook; | |||
import com.sonar.orchestrator.Orchestrator; | |||
import org.junit.After; | |||
import org.junit.Before; | |||
import org.junit.ClassRule; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.sonarqube.qa.util.Tester; | |||
import org.sonarqube.qa.util.pageobjects.WebhooksPage; | |||
import org.sonarqube.ws.Projects.CreateWsResponse.Project; | |||
import org.sonarqube.ws.Users.CreateWsResponse.User; | |||
import org.sonarqube.ws.Webhooks.CreateWsResponse.Webhook; | |||
import static util.ItUtils.runProjectAnalysis; | |||
public class WebhooksPageTest | |||
{ | |||
@ClassRule | |||
public static Orchestrator orchestrator = WebhooksSuite.ORCHESTRATOR; | |||
@Rule | |||
public Tester tester = new Tester(orchestrator); | |||
@Before | |||
@After | |||
public void reset() { | |||
tester.webhooks().deleteAllGlobal(); | |||
} | |||
@Test | |||
public void list_global_webhooks() { | |||
tester.webhooks().generate(); | |||
Webhook webhook = tester.webhooks().generate(); | |||
tester.wsClient().users().skipOnboardingTutorial(); | |||
WebhooksPage webhooksPage = tester.openBrowser().logIn().submitCredentials("admin").openWebhooks(); | |||
webhooksPage | |||
.countWebhooks(2) | |||
.hasWebhook(webhook.getUrl()) | |||
.hasWebhook(webhook.getName()); | |||
} | |||
@Test | |||
public void list_project_webhooks() { | |||
User user = tester.users().generateAdministratorOnDefaultOrganization(); | |||
tester.wsClient().users().skipOnboardingTutorial(); | |||
Project project = tester.projects().provision(); | |||
analyseProject(project); | |||
Webhook webhook1 = tester.webhooks().generate(project); | |||
Webhook webhook2 = tester.webhooks().generate(project); | |||
WebhooksPage webhooksPage = tester.openBrowser().logIn().submitCredentials(user.getLogin()).openProjectWebhooks(project.getKey()); | |||
webhooksPage | |||
.countWebhooks(2) | |||
.hasWebhook(webhook1.getUrl()) | |||
.hasWebhook(webhook2.getUrl()); | |||
} | |||
@Test | |||
public void create_new_webhook() { | |||
User user = tester.users().generateAdministratorOnDefaultOrganization(); | |||
tester.wsClient().users().skipOnboardingTutorial(); | |||
Project project = tester.projects().provision(); | |||
analyseProject(project); | |||
WebhooksPage webhooksPage = tester.openBrowser().logIn().submitCredentials(user.getLogin()).openProjectWebhooks(project.getKey()); | |||
webhooksPage | |||
.hasNoWebhooks() | |||
.createWebhook("my-webhook", "http://greg:pass@test.com") | |||
.countWebhooks(1) | |||
.hasWebhook("my-webhook") | |||
.hasWebhook("http://greg:pass@test.com"); | |||
} | |||
@Test | |||
public void prevent_webhook_creation() { | |||
tester.wsClient().users().skipOnboardingTutorial(); | |||
Webhook webhook = tester.webhooks().generate(); | |||
for (int i = 0; i < 9; i++) { | |||
tester.webhooks().generate(); | |||
} | |||
WebhooksPage webhooksPage = tester.openBrowser().logIn().submitCredentials("admin").openWebhooks(); | |||
webhooksPage | |||
.countWebhooks(10) | |||
.createIsDisabled() | |||
.deleteWebhook(webhook.getName()) | |||
.countWebhooks(9) | |||
.createWebhook("my-new-webhook", "http://my-new-webhook.com"); | |||
} | |||
@Test | |||
public void delete_webhook() { | |||
User user = tester.users().generateAdministratorOnDefaultOrganization(); | |||
tester.wsClient().users().skipOnboardingTutorial(); | |||
Project project = tester.projects().provision(); | |||
analyseProject(project); | |||
tester.webhooks().generate(project); | |||
tester.webhooks().generate(project); | |||
Webhook webhook = tester.webhooks().generate(project); | |||
WebhooksPage webhooksPage = tester.openBrowser().logIn().submitCredentials(user.getLogin()).openProjectWebhooks(project.getKey()); | |||
webhooksPage | |||
.countWebhooks(3) | |||
.deleteWebhook(webhook.getName()) | |||
.countWebhooks(2); | |||
} | |||
private void analyseProject(Project project) { | |||
runProjectAnalysis(orchestrator, "shared/xoo-sample", | |||
"sonar.projectKey", project.getKey(), | |||
"sonar.projectName", project.getName()); | |||
} | |||
} |
@@ -28,6 +28,7 @@ import static util.ItUtils.xooPlugin; | |||
@RunWith(Suite.class) | |||
@Suite.SuiteClasses({ | |||
WebhooksPageTest.class, | |||
WebhooksTest.class | |||
}) | |||
public class WebhooksSuite { |
@@ -20,64 +20,41 @@ | |||
package org.sonarqube.tests.webhook; | |||
import com.sonar.orchestrator.Orchestrator; | |||
import java.util.ArrayList; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.stream.Collectors; | |||
import javax.annotation.Nullable; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.junit.After; | |||
import org.junit.Before; | |||
import org.junit.ClassRule; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.sonarqube.qa.util.Tester; | |||
import org.sonarqube.qa.util.pageobjects.WebhooksPage; | |||
import org.sonarqube.ws.Issues.Issue; | |||
import org.sonarqube.ws.Organizations.Organization; | |||
import org.sonarqube.ws.Projects.CreateWsResponse.Project; | |||
import org.sonarqube.ws.Qualitygates; | |||
import org.sonarqube.ws.Qualityprofiles.CreateWsResponse.QualityProfile; | |||
import org.sonarqube.ws.Users; | |||
import org.sonarqube.ws.Users.CreateWsResponse.User; | |||
import org.sonarqube.ws.Webhooks; | |||
import org.sonarqube.ws.client.HttpException; | |||
import org.sonarqube.ws.client.WsClient; | |||
import org.sonarqube.ws.Webhooks.CreateWsResponse.Webhook; | |||
import org.sonarqube.ws.client.issues.BulkChangeRequest; | |||
import org.sonarqube.ws.client.issues.SearchRequest; | |||
import org.sonarqube.ws.client.projects.DeleteRequest; | |||
import org.sonarqube.ws.client.qualitygates.CreateConditionRequest; | |||
import org.sonarqube.ws.client.settings.ResetRequest; | |||
import org.sonarqube.ws.client.settings.SetRequest; | |||
import org.sonarqube.ws.client.webhooks.DeliveriesRequest; | |||
import org.sonarqube.ws.client.webhooks.DeliveryRequest; | |||
import util.ItUtils; | |||
import static java.util.Collections.singletonList; | |||
import static java.util.Objects.requireNonNull; | |||
import static java.util.stream.IntStream.range; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static util.ItUtils.jsonToMap; | |||
import static util.ItUtils.runProjectAnalysis; | |||
public class WebhooksTest { | |||
private static final String PROJECT_KEY = "my-project"; | |||
private static final String PROJECT_NAME = "My Project"; | |||
private static final String GLOBAL_WEBHOOK_PROPERTY = "sonar.webhooks.global"; | |||
private static final String PROJECT_WEBHOOK_PROPERTY = "sonar.webhooks.project"; | |||
@ClassRule | |||
public static Orchestrator orchestrator = WebhooksSuite.ORCHESTRATOR; | |||
@ClassRule | |||
public static ExternalServer externalServer = new ExternalServer(); | |||
@Rule | |||
public Tester tester = new Tester(orchestrator); | |||
private WsClient adminWs = ItUtils.newAdminWsClient(orchestrator); | |||
@Before | |||
public void setUp() { | |||
externalServer.clear(); | |||
@@ -86,28 +63,20 @@ public class WebhooksTest { | |||
@Before | |||
@After | |||
public void reset() { | |||
disableGlobalWebhooks(); | |||
try { | |||
// delete project and related properties/webhook deliveries | |||
adminWs.projects().delete(new DeleteRequest().setProject(PROJECT_KEY)); | |||
} catch (HttpException e) { | |||
// ignore because project may not exist | |||
} | |||
tester.webhooks().deleteAllGlobal(); | |||
} | |||
@Test | |||
public void call_multiple_global_and_project_webhooks_when_analysis_is_done() throws InterruptedException { | |||
orchestrator.getServer().provisionProject(PROJECT_KEY, PROJECT_NAME); | |||
enableGlobalWebhooks( | |||
new Webhook("Jenkins", externalServer.urlFor("/jenkins")), | |||
new Webhook("HipChat", externalServer.urlFor("/hipchat"))); | |||
enableProjectWebhooks(PROJECT_KEY, | |||
new Webhook("Burgr", externalServer.urlFor("/burgr"))); | |||
Project project = tester.projects().provision(); | |||
Webhook jenkins = tester.webhooks().generate(p -> p.setName("Jenkins").setUrl(externalServer.urlFor("/jenkins"))); | |||
Webhook hipchat = tester.webhooks().generate(p -> p.setName("HipChat").setUrl(externalServer.urlFor("/hipchat"))); | |||
Webhook burgr = tester.webhooks().generate(project, p -> p.setName("Burgr").setUrl(externalServer.urlFor("/burgr"))); | |||
analyseProject(); | |||
analyseProject(project); | |||
// the same payload has been sent to three servers | |||
waitUntilAllWebHooksCalled(3); | |||
externalServer.waitUntilAllWebHooksCalled(3); | |||
assertThat(externalServer.getPayloadRequests()).hasSize(3); | |||
PayloadRequest request = externalServer.getPayloadRequests().get(0); | |||
for (int i = 1; i < 3; i++) { | |||
@@ -116,137 +85,104 @@ public class WebhooksTest { | |||
} | |||
// verify HTTP headers | |||
assertThat(request.getHttpHeaders().get("X-SonarQube-Project")).isEqualTo(PROJECT_KEY); | |||
assertThat(request.getHttpHeaders().get("X-SonarQube-Project")).isEqualTo(project.getKey()); | |||
// verify content of payload | |||
Map<String, Object> payload = jsonToMap(request.getJson()); | |||
assertThat(payload.get("status")).isEqualTo("SUCCESS"); | |||
assertThat(payload.get("analysedAt")).isNotNull(); | |||
Map<String, String> project = (Map<String, String>) payload.get("project"); | |||
assertThat(project.get("key")).isEqualTo(PROJECT_KEY); | |||
assertThat(project.get("name")).isEqualTo(PROJECT_NAME); | |||
assertThat(project.get("url")).isEqualTo(orchestrator.getServer().getUrl() + "/dashboard?id=" + PROJECT_KEY); | |||
Map<String, String> projectPayload = (Map<String, String>) payload.get("project"); | |||
assertThat(projectPayload.get("key")).isEqualTo(project.getKey()); | |||
assertThat(projectPayload.get("name")).isEqualTo(project.getName()); | |||
assertThat(projectPayload.get("url")).isEqualTo(orchestrator.getServer().getUrl() + "/dashboard?id=" + project.getKey()); | |||
Map<String, Object> gate = (Map<String, Object>) payload.get("qualityGate"); | |||
assertThat(gate.get("name")).isEqualTo("Sonar way"); | |||
assertThat(gate.get("status")).isEqualTo("OK"); | |||
assertThat(gate.get("conditions")).isNotNull(); | |||
// verify list of persisted deliveries (api/webhooks/deliveries) | |||
List<Webhooks.Delivery> deliveries = getPersistedDeliveries(); | |||
List<Webhooks.Delivery> deliveries = tester.webhooks().getPersistedDeliveries(project); | |||
assertThat(deliveries).hasSize(3); | |||
for (Webhooks.Delivery delivery : deliveries) { | |||
assertThatPersistedDeliveryIsValid(delivery); | |||
tester.webhooks().assertThatPersistedDeliveryIsValid(delivery, project, externalServer.urlFor("/")); | |||
assertThat(delivery.getSuccess()).isTrue(); | |||
assertThat(delivery.getHttpStatus()).isEqualTo(200); | |||
assertThat(delivery.getName()).isIn("Jenkins", "HipChat", "Burgr"); | |||
assertThat(delivery.getName()).isIn(jenkins.getName(), hipchat.getName(), burgr.getName()); | |||
assertThat(delivery.hasErrorStacktrace()).isFalse(); | |||
// payload is available only in api/webhooks/delivery to avoid loading multiple DB CLOBs | |||
assertThat(delivery.hasPayload()).isFalse(); | |||
} | |||
// verify detail of persisted delivery (api/webhooks/delivery) | |||
Webhooks.Delivery detail = getDetailOfPersistedDelivery(deliveries.get(0)); | |||
assertThatPersistedDeliveryIsValid(detail); | |||
Webhooks.Delivery detail = tester.webhooks().getDetailOfPersistedDelivery(deliveries.get(0)); | |||
tester.webhooks().assertThatPersistedDeliveryIsValid(detail, project, externalServer.urlFor("/")); | |||
assertThat(detail.getPayload()).isEqualTo(request.getJson()); | |||
} | |||
@Test | |||
public void persist_delivery_as_failed_if_external_server_returns_an_error_code() { | |||
enableGlobalWebhooks( | |||
new Webhook("Fail", externalServer.urlFor("/fail")), | |||
new Webhook("HipChat", externalServer.urlFor("/hipchat"))); | |||
Project project = tester.projects().provision(); | |||
Webhook fail = tester.webhooks().generate(p -> p.setName("Fail").setUrl(externalServer.urlFor("/fail"))); | |||
Webhook hipchat = tester.webhooks().generate(p -> p.setName("HipChat").setUrl(externalServer.urlFor("/hipchat"))); | |||
analyseProject(); | |||
analyseProject(project); | |||
// all webhooks are called, even if one returns an error code | |||
assertThat(externalServer.getPayloadRequests()).hasSize(2); | |||
// verify persisted deliveries | |||
Webhooks.Delivery failedDelivery = getPersistedDeliveryByName("Fail"); | |||
assertThatPersistedDeliveryIsValid(failedDelivery); | |||
Webhooks.Delivery failedDelivery = tester.webhooks().getPersistedDeliveryByName(project, fail.getName()); | |||
tester.webhooks().assertThatPersistedDeliveryIsValid(failedDelivery, project, fail.getUrl()); | |||
assertThat(failedDelivery.getSuccess()).isFalse(); | |||
assertThat(failedDelivery.getHttpStatus()).isEqualTo(500); | |||
Webhooks.Delivery successfulDelivery = getPersistedDeliveryByName("HipChat"); | |||
assertThatPersistedDeliveryIsValid(successfulDelivery); | |||
Webhooks.Delivery successfulDelivery = tester.webhooks().getPersistedDeliveryByName(project, hipchat.getName()); | |||
tester.webhooks().assertThatPersistedDeliveryIsValid(successfulDelivery, project, hipchat.getUrl()); | |||
assertThat(successfulDelivery.getSuccess()).isTrue(); | |||
assertThat(successfulDelivery.getHttpStatus()).isEqualTo(200); | |||
} | |||
/** | |||
* Restrict calls to ten webhooks per type (global or project) | |||
*/ | |||
@Test | |||
public void do_not_become_a_denial_of_service_attacker() throws InterruptedException { | |||
orchestrator.getServer().provisionProject(PROJECT_KEY, PROJECT_NAME); | |||
List<Webhook> globalWebhooks = range(0, 15).mapToObj(i -> new Webhook("G" + i, externalServer.urlFor("/global"))).collect(Collectors.toList()); | |||
enableGlobalWebhooks(globalWebhooks.toArray(new Webhook[globalWebhooks.size()])); | |||
List<Webhook> projectWebhooks = range(0, 15).mapToObj(i -> new Webhook("P" + i, externalServer.urlFor("/project"))).collect(Collectors.toList()); | |||
enableProjectWebhooks(PROJECT_KEY, projectWebhooks.toArray(new Webhook[projectWebhooks.size()])); | |||
analyseProject(); | |||
// only the first ten global webhooks and ten project webhooks are called | |||
waitUntilAllWebHooksCalled(10 + 10); | |||
assertThat(externalServer.getPayloadRequests()).hasSize(10 + 10); | |||
assertThat(externalServer.getPayloadRequestsOnPath("/global")).hasSize(10); | |||
assertThat(externalServer.getPayloadRequestsOnPath("/project")).hasSize(10); | |||
// verify persisted deliveries | |||
assertThat(getPersistedDeliveries()).hasSize(10 + 10); | |||
} | |||
@Test | |||
public void persist_delivery_as_failed_if_webhook_url_is_malformed() { | |||
enableGlobalWebhooks(new Webhook("Jenkins", "this_is_not_an_url")); | |||
public void persist_delivery_as_failed_if_webhook_is_not_reachable() { | |||
Project project = tester.projects().provision(); | |||
Webhook badUrl = tester.webhooks().generate(p -> p.setUrl("http://does_not_exist")); | |||
analyseProject(); | |||
analyseProject(project); | |||
assertThat(externalServer.getPayloadRequests()).isEmpty(); | |||
// verify persisted deliveries | |||
Webhooks.Delivery delivery = getPersistedDeliveryByName("Jenkins"); | |||
Webhooks.Delivery detail = getDetailOfPersistedDelivery(delivery); | |||
Webhooks.Delivery delivery = tester.webhooks().getPersistedDeliveryByName(project, badUrl.getName()); | |||
Webhooks.Delivery detail = tester.webhooks().getDetailOfPersistedDelivery(delivery); | |||
assertThat(detail.getSuccess()).isFalse(); | |||
assertThat(detail.hasHttpStatus()).isFalse(); | |||
assertThat(detail.hasDurationMs()).isFalse(); | |||
assertThat(detail.getPayload()).isNotEmpty(); | |||
assertThat(detail.getErrorStacktrace()) | |||
.contains("java.lang.IllegalArgumentException") | |||
.contains("Webhook URL is not valid: this_is_not_an_url"); | |||
} | |||
@Test | |||
public void ignore_webhook_if_url_is_missing() { | |||
// property sets, as used to define webhooks, do | |||
// not allow to validate values yet | |||
enableGlobalWebhooks(new Webhook("Jenkins", null)); | |||
analyseProject(); | |||
assertThat(externalServer.getPayloadRequests()).isEmpty(); | |||
assertThat(getPersistedDeliveries()).isEmpty(); | |||
.contains("java.net.UnknownHostException") | |||
.contains("Name or service not known"); | |||
} | |||
@Test | |||
public void send_webhook_on_issue_change() throws InterruptedException { | |||
Organization defaultOrganization = tester.organizations().getDefaultOrganization(); | |||
Project wsProject = tester.projects().provision(r -> r.setProject(PROJECT_KEY).setName(PROJECT_NAME)); | |||
enableProjectWebhooks(PROJECT_KEY, new Webhook("Burgr", externalServer.urlFor("/burgr"))); | |||
Project project = tester.projects().provision(); | |||
Webhook burgr = tester.webhooks().generate(project, p -> p.setName("Burgr").setUrl(externalServer.urlFor("/burgr"))); | |||
// quality profile with one issue per line | |||
QualityProfile qualityProfile = tester.qProfiles().createXooProfile(defaultOrganization); | |||
tester.qProfiles().activateRule(qualityProfile, "xoo:OneIssuePerLine"); | |||
tester.qProfiles().assignQProfileToProject(qualityProfile, wsProject); | |||
tester.qProfiles().assignQProfileToProject(qualityProfile, project); | |||
// quality gate definition | |||
Qualitygates.CreateResponse qGate = tester.qGates().generate(); | |||
tester.qGates().service().createCondition(new CreateConditionRequest().setGateId(String.valueOf(qGate.getId())) | |||
.setMetric("reliability_rating").setOp("GT").setError("1")); | |||
tester.qGates().associateProject(qGate, wsProject); | |||
tester.qGates().associateProject(qGate, project); | |||
// analyze project and clear first webhook | |||
analyseProject(); | |||
waitUntilAllWebHooksCalled(1); | |||
analyseProject(project); | |||
externalServer.waitUntilAllWebHooksCalled(1); | |||
externalServer.clear(); | |||
// change an issue to blocker bug, QG status goes from OK to ERROR, so webhook is called | |||
@@ -255,18 +191,18 @@ public class WebhooksTest { | |||
tester.wsClient().issues().bulkChange(new BulkChangeRequest().setIssues(singletonList(firstIssue.getKey())) | |||
.setSetSeverity(singletonList("BLOCKER")) | |||
.setSetType(singletonList("BUG"))); | |||
waitUntilAllWebHooksCalled(1); | |||
externalServer.waitUntilAllWebHooksCalled(1); | |||
PayloadRequest request = externalServer.getPayloadRequests().get(0); | |||
assertThat(request.getHttpHeaders().get("X-SonarQube-Project")).isEqualTo(PROJECT_KEY); | |||
assertThat(request.getHttpHeaders().get("X-SonarQube-Project")).isEqualTo(project.getKey()); | |||
// verify content of payload | |||
Map<String, Object> payload = jsonToMap(request.getJson()); | |||
assertThat(payload.get("status")).isEqualTo("SUCCESS"); | |||
assertThat(payload.get("analysedAt")).isNotNull(); | |||
Map<String, String> project = (Map<String, String>) payload.get("project"); | |||
assertThat(project.get("key")).isEqualTo(PROJECT_KEY); | |||
assertThat(project.get("name")).isEqualTo(PROJECT_NAME); | |||
assertThat(project.get("url")).isEqualTo(orchestrator.getServer().getUrl() + "/dashboard?id=" + PROJECT_KEY); | |||
Map<String, String> projectPayload = (Map<String, String>) payload.get("project"); | |||
assertThat(projectPayload.get("key")).isEqualTo(project.getKey()); | |||
assertThat(projectPayload.get("name")).isEqualTo(project.getName()); | |||
assertThat(projectPayload.get("url")).isEqualTo(orchestrator.getServer().getUrl() + "/dashboard?id=" + project.getKey()); | |||
Map<String, Object> gate = (Map<String, Object>) payload.get("qualityGate"); | |||
assertThat(gate.get("name")).isEqualTo(qGate.getName()); | |||
assertThat(gate.get("status")).isEqualTo("ERROR"); | |||
@@ -276,127 +212,22 @@ public class WebhooksTest { | |||
// change severity of issue, won't change the QG status, so no webhook called | |||
tester.wsClient().issues().bulkChange(new BulkChangeRequest().setIssues(singletonList(firstIssue.getKey())) | |||
.setSetSeverity(singletonList("MINOR"))); | |||
waitUntilAllWebHooksCalled(1); | |||
externalServer.waitUntilAllWebHooksCalled(1); | |||
assertThat(externalServer.getPayloadRequests()).isEmpty(); | |||
// resolve issue as won't fix, QG status goes to OK, so webhook called | |||
tester.wsClient().issues().bulkChange(new BulkChangeRequest().setIssues(singletonList(firstIssue.getKey())) | |||
.setDoTransition("wontfix")); | |||
waitUntilAllWebHooksCalled(1); | |||
externalServer.waitUntilAllWebHooksCalled(1); | |||
request = externalServer.getPayloadRequests().get(0); | |||
payload = jsonToMap(request.getJson()); | |||
gate = (Map<String, Object>) payload.get("qualityGate"); | |||
assertThat(gate.get("status")).isEqualTo("OK"); | |||
} | |||
@Test | |||
public void list_global_webhooks() { | |||
enableGlobalWebhooks(new Webhook("foo", "http://foo.bar"), new Webhook("bar", "https://bar.baz/test")); | |||
tester.wsClient().users().skipOnboardingTutorial(); | |||
WebhooksPage webhooksPage = tester.openBrowser().logIn().submitCredentials("admin").openWebhooks(); | |||
webhooksPage | |||
.countWebhooks(2) | |||
.hasWebhook("http://foo.bar"); | |||
} | |||
@Test | |||
public void list_project_webhooks() { | |||
analyseProject(); | |||
enableProjectWebhooks(PROJECT_KEY, new Webhook("foo", "http://foo.bar"), new Webhook("bar", "https://bar.baz/test")); | |||
User user = tester.users().generateAdministratorOnDefaultOrganization(); | |||
tester.wsClient().users().skipOnboardingTutorial(); | |||
WebhooksPage webhooksPage = tester.openBrowser().logIn().submitCredentials(user.getLogin()).openProjectWebhooks(PROJECT_KEY); | |||
webhooksPage | |||
.countWebhooks(2) | |||
.hasWebhook("http://foo.bar"); | |||
} | |||
private void analyseProject() { | |||
private void analyseProject(Project project) { | |||
runProjectAnalysis(orchestrator, "shared/xoo-sample", | |||
"sonar.projectKey", PROJECT_KEY, | |||
"sonar.projectName", PROJECT_NAME); | |||
} | |||
private List<Webhooks.Delivery> getPersistedDeliveries() { | |||
DeliveriesRequest deliveriesReq = new DeliveriesRequest().setComponentKey(PROJECT_KEY); | |||
return adminWs.webhooks().deliveries(deliveriesReq).getDeliveriesList(); | |||
} | |||
private Webhooks.Delivery getPersistedDeliveryByName(String webhookName) { | |||
List<Webhooks.Delivery> deliveries = getPersistedDeliveries(); | |||
return deliveries.stream().filter(d -> d.getName().equals(webhookName)).findFirst().get(); | |||
} | |||
private Webhooks.Delivery getDetailOfPersistedDelivery(Webhooks.Delivery delivery) { | |||
Webhooks.Delivery detail = adminWs.webhooks().delivery(new DeliveryRequest().setDeliveryId(delivery.getId())).getDelivery(); | |||
return requireNonNull(detail); | |||
} | |||
private void assertThatPersistedDeliveryIsValid(Webhooks.Delivery delivery) { | |||
assertThat(delivery.getId()).isNotEmpty(); | |||
assertThat(delivery.getName()).isNotEmpty(); | |||
assertThat(delivery.hasSuccess()).isTrue(); | |||
assertThat(delivery.getHttpStatus()).isGreaterThanOrEqualTo(200); | |||
assertThat(delivery.getDurationMs()).isGreaterThanOrEqualTo(0); | |||
assertThat(delivery.getAt()).isNotEmpty(); | |||
assertThat(delivery.getComponentKey()).isEqualTo(PROJECT_KEY); | |||
assertThat(delivery.getUrl()).startsWith(externalServer.urlFor("/")); | |||
} | |||
private void enableGlobalWebhooks(Webhook... webhooks) { | |||
enableWebhooks(null, GLOBAL_WEBHOOK_PROPERTY, webhooks); | |||
} | |||
private void enableProjectWebhooks(String projectKey, Webhook... webhooks) { | |||
enableWebhooks(projectKey, PROJECT_WEBHOOK_PROPERTY, webhooks); | |||
} | |||
private void enableWebhooks(@Nullable String projectKey, String property, Webhook... webhooks) { | |||
List<String> webhookIds = new ArrayList<>(); | |||
for (int i = 0; i < webhooks.length; i++) { | |||
Webhook webhook = webhooks[i]; | |||
String id = String.valueOf(i + 1); | |||
webhookIds.add(id); | |||
setProperty(projectKey, property + "." + id + ".name", webhook.name); | |||
setProperty(projectKey, property + "." + id + ".url", webhook.url); | |||
} | |||
setProperty(projectKey, property, StringUtils.join(webhookIds, ",")); | |||
"sonar.projectKey", project.getKey(), | |||
"sonar.projectName", project.getName()); | |||
} | |||
private void disableGlobalWebhooks() { | |||
setProperty(null, GLOBAL_WEBHOOK_PROPERTY, null); | |||
} | |||
private void setProperty(@Nullable String componentKey, String key, @Nullable String value) { | |||
if (value == null) { | |||
ResetRequest req = new ResetRequest().setKeys(Collections.singletonList(key)).setComponent(componentKey); | |||
adminWs.settings().reset(req); | |||
} else { | |||
SetRequest req = new SetRequest().setKey(key).setValue(value).setComponent(componentKey); | |||
adminWs.settings().set(req); | |||
} | |||
} | |||
private static class Webhook { | |||
private final String name; | |||
private final String url; | |||
Webhook(@Nullable String name, @Nullable String url) { | |||
this.name = name; | |||
this.url = url; | |||
} | |||
} | |||
/** | |||
* Wait up to 30 seconds | |||
*/ | |||
private static void waitUntilAllWebHooksCalled(int expectedNumberOfRequests) throws InterruptedException { | |||
for (int i = 0; i < 60; i++) { | |||
if (externalServer.getPayloadRequests().size() == expectedNumberOfRequests) { | |||
break; | |||
} | |||
Thread.sleep(500); | |||
} | |||
} | |||
} |