aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuillaume Jambet <guillaume.jambet@sonarsource.com>2018-02-07 11:35:25 +0100
committerGuillaume Jambet <guillaume.jambet@gmail.com>2018-03-01 15:21:05 +0100
commit67ac2e4220a7ce34533d1c14c8f5f9652c37e08f (patch)
tree8a4426be4a50be44f7d5cfec6a697cc4392dad60
parentfd1ca855caea58c6310c7f19710f223987630e6a (diff)
downloadsonarqube-67ac2e4220a7ce34533d1c14c8f5f9652c37e08f.tar.gz
sonarqube-67ac2e4220a7ce34533d1c14c8f5f9652c37e08f.zip
SONAR-10345 Add Webhooks creation ws
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookTesting.java49
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/DbTester.java7
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDbTester.java48
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/webhook/ws/CreateAction.java31
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhooksWsModule.java1
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhooksWsParameters.java13
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java3
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/ws/WsUtils.java8
-rw-r--r--server/sonar-server/src/main/resources/org/sonar/server/webhook/ws/example-webhook-create.json7
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/webhook/ws/CreateActionTest.java362
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/webhook/ws/WebhooksWsModuleTest.java2
-rw-r--r--server/sonar-web/src/main/js/components/icons-components/AlertSuccessIcon.tsx2
-rw-r--r--sonar-ws/src/main/protobuf/ws-webhooks.proto10
13 files changed, 525 insertions, 18 deletions
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookTesting.java b/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookTesting.java
new file mode 100644
index 00000000000..0eb167cf44a
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookTesting.java
@@ -0,0 +1,49 @@
+/*
+ * 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.sonar.db.webhook;
+
+import java.util.Calendar;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+
+public class WebhookTesting {
+
+ private WebhookTesting() {
+ // only statics
+ }
+
+ public static WebhookDto newWebhookDtoForProject(String projectUuid) {
+ return getWebhookDto()
+ .setProjectUuid(projectUuid);
+ }
+
+ public static WebhookDto newWebhookDtoForOrganization(String organizationUuid) {
+ return getWebhookDto()
+ .setOrganizationUuid(organizationUuid);
+ }
+
+ private static WebhookDto getWebhookDto() {
+ return new WebhookDto()
+ .setUuid(randomAlphanumeric(40))
+ .setName(randomAlphanumeric(64))
+ .setUrl("https://www.random-site/" + randomAlphanumeric(256))
+ .setCreatedAt(Calendar.getInstance().getTimeInMillis());
+ }
+}
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/DbTester.java b/server/sonar-db-dao/src/test/java/org/sonar/db/DbTester.java
index f4d7d81ffd7..19b4b44a15e 100644
--- a/server/sonar-db-dao/src/test/java/org/sonar/db/DbTester.java
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/DbTester.java
@@ -48,6 +48,7 @@ import org.sonar.db.rule.RuleDbTester;
import org.sonar.db.source.FileSourceTester;
import org.sonar.db.user.RootFlagAssertions;
import org.sonar.db.user.UserDbTester;
+import org.sonar.db.webhook.WebhookDbTester;
import static com.google.common.base.Preconditions.checkState;
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
@@ -83,6 +84,7 @@ public class DbTester extends AbstractDbTester<TestDb> {
private final MeasureDbTester measureDbTester;
private final FileSourceTester fileSourceTester;
private final PluginDbTester pluginDbTester;
+ private final WebhookDbTester webhookDbTester;
public DbTester(System2 system2, @Nullable String schemaPath) {
super(TestDb.create(schemaPath));
@@ -106,6 +108,7 @@ public class DbTester extends AbstractDbTester<TestDb> {
this.measureDbTester = new MeasureDbTester(this);
this.fileSourceTester = new FileSourceTester(this);
this.pluginDbTester = new PluginDbTester(this);
+ this.webhookDbTester = new WebhookDbTester(this);
}
public static DbTester create() {
@@ -248,6 +251,10 @@ public class DbTester extends AbstractDbTester<TestDb> {
return pluginDbTester;
}
+ public WebhookDbTester webhooks(){
+ return webhookDbTester;
+ }
+
@Override
protected void after() {
if (session != null) {
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDbTester.java b/server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDbTester.java
new file mode 100644
index 00000000000..a0fcc49a0f3
--- /dev/null
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDbTester.java
@@ -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.sonar.db.webhook;
+
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+
+public class WebhookDbTester {
+
+ private final DbTester dbTester;
+
+ public WebhookDbTester(DbTester dbTester) {
+ this.dbTester = dbTester;
+ }
+
+ public WebhookDto insertForOrganizationUuid(String organizationUuid) {
+ return insert(WebhookTesting.newWebhookDtoForOrganization(organizationUuid));
+ }
+
+ public WebhookDto insertWebhookForProjectUuid(String projectUuid) {
+ return insert(WebhookTesting.newWebhookDtoForProject(projectUuid));
+ }
+
+ public WebhookDto insert(WebhookDto dto) {
+ DbSession dbSession = dbTester.getSession();
+ dbTester.getDbClient().webhookDao().insert(dbSession, dto);
+ dbSession.commit();
+ return dto;
+ }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/CreateAction.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/CreateAction.java
index 029b30666ad..b5962eb3814 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/CreateAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/CreateAction.java
@@ -24,7 +24,6 @@ import javax.annotation.Nullable;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
-import org.sonar.api.utils.System2;
import org.sonar.core.util.UuidFactory;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
@@ -56,6 +55,7 @@ import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.NAME_WEBHOOK_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.URL_WEBHOOK_EXAMPLE_001;
import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
+import static org.sonar.server.ws.WsUtils.checkStateWithOptional;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonarqube.ws.Webhooks.CreateWsResponse.Webhook;
import static org.sonarqube.ws.Webhooks.CreateWsResponse.newBuilder;
@@ -68,14 +68,12 @@ public class CreateAction implements WebhooksWsAction {
private final UserSession userSession;
private final DefaultOrganizationProvider defaultOrganizationProvider;
private final UuidFactory uuidFactory;
- private final System2 system;
- public CreateAction(DbClient dbClient, UserSession userSession, DefaultOrganizationProvider defaultOrganizationProvider, UuidFactory uuidFactory, System2 system) {
+ public CreateAction(DbClient dbClient, UserSession userSession, DefaultOrganizationProvider defaultOrganizationProvider, UuidFactory uuidFactory) {
this.dbClient = dbClient;
this.userSession = userSession;
this.defaultOrganizationProvider = defaultOrganizationProvider;
this.uuidFactory = uuidFactory;
- this.system = system;
}
@Override
@@ -126,7 +124,7 @@ public class CreateAction implements WebhooksWsAction {
String projectKey = request.param(PROJECT_KEY_PARAM);
String organizationKey = request.param(ORGANIZATION_KEY_PARAM);
- try (DbSession dbSession = dbClient.openSession(true)) {
+ try (DbSession dbSession = dbClient.openSession(false)) {
OrganizationDto organizationDto;
if (isNotBlank(organizationKey)) {
@@ -138,18 +136,24 @@ public class CreateAction implements WebhooksWsAction {
ComponentDto projectDto = null;
if (isNotBlank(projectKey)) {
- com.google.common.base.Optional<ComponentDto> dtoOptional = dbClient.componentDao().selectByKey(dbSession, projectKey);
- checkFoundWithOptional(dtoOptional, "No project with key '%s'", projectKey);
- checkThatProjectBelongsToOrganization(dtoOptional.get(), organizationDto, "Project '%s' does not belong to organisation '%s'", projectKey, organizationKey);
- checkUserPermissionOn(dtoOptional.get());
- projectDto = dtoOptional.get();
+ Optional<ComponentDto> dtoOptional = Optional.ofNullable(dbClient.componentDao().selectByKey(dbSession, projectKey).orNull());
+ ComponentDto componentDto = checkFoundWithOptional(dtoOptional, "No project with key '%s'", projectKey);
+ checkThatProjectBelongsToOrganization(componentDto, organizationDto, "Project '%s' does not belong to organisation '%s'", projectKey, organizationKey);
+ checkUserPermissionOn(componentDto);
+ projectDto = componentDto;
} else {
checkUserPermissionOn(organizationDto);
}
checkUrlPattern(url, "Url parameter with value '%s' is not a valid url", url);
- writeResponse(request, response, doHandle(dbSession, organizationDto, projectDto, name, url));
+ WebhookDto webhookDto = doHandle(dbSession, organizationDto, projectDto, name, url);
+
+ dbClient.webhookDao().insert(dbSession, webhookDto);
+
+ dbSession.commit();
+
+ writeResponse(request, response, webhookDto);
}
}
@@ -172,8 +176,6 @@ public class CreateAction implements WebhooksWsAction {
dto.setOrganizationUuid(organization.getUuid());
}
- dbClient.webhookDao().insert(dbSession, dto);
-
return dto;
}
@@ -227,6 +229,7 @@ public class CreateAction implements WebhooksWsAction {
private OrganizationDto defaultOrganizationDto(DbSession dbSession) {
String uuid = defaultOrganizationProvider.get().getUuid();
- return dbClient.organizationDao().selectByUuid(dbSession, uuid).get();
+ Optional<OrganizationDto> organizationDto = dbClient.organizationDao().selectByUuid(dbSession, uuid);
+ return checkStateWithOptional(organizationDto, "the default organization '%s' was not found", uuid);
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhooksWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhooksWsModule.java
index 3f589d19b60..92334805107 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhooksWsModule.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhooksWsModule.java
@@ -27,6 +27,7 @@ public class WebhooksWsModule extends Module {
add(
WebhooksWs.class,
SearchAction.class,
+ CreateAction.class,
WebhookDeliveryAction.class,
WebhookDeliveriesAction.class);
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhooksWsParameters.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhooksWsParameters.java
index 7430e3f2f1d..0d800f476c6 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhooksWsParameters.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhooksWsParameters.java
@@ -23,13 +23,24 @@ class WebhooksWsParameters {
static final String WEBHOOKS_CONTROLLER = "api/webhooks";
+
+ static final String ACTION_CREATE = "create";
static final String SEARCH_ACTION = "search";
+
static final String ORGANIZATION_KEY_PARAM = "organization";
+ static final int ORGANIZATION_KEY_PARAM_MAXIMUM_LENGTH = 255;
static final String PROJECT_KEY_PARAM = "project";
+ static final int PROJECT_KEY_PARAM_MAXIMUN_LENGTH = 100;
+ static final String NAME_PARAM = "name";
+ static final int NAME_PARAM_MAXIMUM_LENGTH = 100;
+ static final String URL_PARAM = "url";
+ static final int URL_PARAM_MAXIMUM_LENGTH = 512;
+ static final String COMPONENT_KEY_PARAM = "component";
+ static final int COMPONENT_KEY_PARAM_MAXIMUM_LENGTH = 255;
private WebhooksWsParameters() {
- // hiding constructor
+ // prevent instantiation
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java b/server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java
index b24ac11fa18..959ec946aa0 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java
@@ -31,6 +31,9 @@ public class KeyExamples {
public static final String KEY_BRANCH_EXAMPLE_001 = "feature/my_branch";
+ public static final String NAME_WEBHOOK_EXAMPLE_001 = "my-webhook";
+ public static final String URL_WEBHOOK_EXAMPLE_001 = "https://www.my-webhook-listener.com/sonar";
+
private KeyExamples() {
// prevent instantiation
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/ws/WsUtils.java b/server/sonar-server/src/main/java/org/sonar/server/ws/WsUtils.java
index d028f4879fa..73c837cc155 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/ws/WsUtils.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/ws/WsUtils.java
@@ -109,4 +109,12 @@ public class WsUtils {
return value.get();
}
+
+ public static <T> T checkStateWithOptional(java.util.Optional<T> value, String message, Object... messageArguments) {
+ if (!value.isPresent()) {
+ throw new IllegalStateException(format(message, messageArguments));
+ }
+
+ return value.get();
+ }
}
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/webhook/ws/example-webhook-create.json b/server/sonar-server/src/main/resources/org/sonar/server/webhook/ws/example-webhook-create.json
new file mode 100644
index 00000000000..4ef726a32e0
--- /dev/null
+++ b/server/sonar-server/src/main/resources/org/sonar/server/webhook/ws/example-webhook-create.json
@@ -0,0 +1,7 @@
+{
+ "webhook": {
+ "key": "uuid",
+ "name": "my-webhook",
+ "url": "https://www.my-webhook-listener.com/sonar"
+ }
+} \ No newline at end of file
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/CreateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/CreateActionTest.java
new file mode 100644
index 00000000000..3ffbf303afa
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/CreateActionTest.java
@@ -0,0 +1,362 @@
+/*
+ * 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.sonar.server.webhook.ws;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.core.util.UuidFactoryFast;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.organization.OrganizationDbTester;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.webhook.WebhookDbTester;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.server.organization.DefaultOrganizationProvider;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.WsActionTester;
+import org.sonarqube.ws.Webhooks.CreateWsResponse;
+
+import static java.lang.String.format;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.AssertionsForClassTypes.tuple;
+import static org.junit.rules.ExpectedException.none;
+import static org.sonar.api.web.UserRole.ADMIN;
+import static org.sonar.db.DbTester.create;
+import static org.sonar.db.permission.OrganizationPermission.ADMINISTER;
+import static org.sonar.server.organization.TestDefaultOrganizationProvider.from;
+import static org.sonar.server.tester.UserSessionRule.standalone;
+import static org.sonar.server.webhook.ws.WebhooksWsParameters.NAME_PARAM;
+import static org.sonar.server.webhook.ws.WebhooksWsParameters.ORGANIZATION_KEY_PARAM;
+import static org.sonar.server.webhook.ws.WebhooksWsParameters.PROJECT_KEY_PARAM;
+import static org.sonar.server.webhook.ws.WebhooksWsParameters.URL_PARAM;
+import static org.sonar.server.ws.KeyExamples.NAME_WEBHOOK_EXAMPLE_001;
+import static org.sonar.server.ws.KeyExamples.URL_WEBHOOK_EXAMPLE_001;
+
+public class CreateActionTest {
+
+ @Rule
+ public ExpectedException expectedException = none();
+
+ @Rule
+ public UserSessionRule userSession = standalone();
+
+ @Rule
+ public DbTester db = create();
+ private DbClient dbClient = db.getDbClient();
+ private WebhookDbTester webhookDbTester = db.webhooks();
+ private OrganizationDbTester organizationDbTester = db.organizations();
+ private ComponentDbTester componentDbTester = db.components();
+
+ private DefaultOrganizationProvider defaultOrganizationProvider = from(db);
+ private UuidFactory uuidFactory = UuidFactoryFast.getInstance();
+
+ private org.sonar.server.webhook.ws.CreateAction underTest = new CreateAction(dbClient, userSession, defaultOrganizationProvider, uuidFactory);
+ private WsActionTester wsActionTester = new WsActionTester(underTest);
+
+ @Test
+ public void test_ws_definition() {
+
+ WebService.Action action = wsActionTester.getDef();
+ assertThat(action).isNotNull();
+ assertThat(action.isInternal()).isFalse();
+ assertThat(action.isPost()).isTrue();
+ assertThat(action.responseExampleAsString()).isNotEmpty();
+
+ assertThat(action.params())
+ .extracting(WebService.Param::key, WebService.Param::isRequired)
+ .containsExactlyInAnyOrder(
+ tuple("organization", false),
+ tuple("project", false),
+ tuple("name", true),
+ tuple("url", true));
+
+ }
+
+ @Test
+ public void create_a_webhook_on_default_organization() {
+
+ userSession.logIn().addPermission(ADMINISTER, defaultOrganizationProvider.get().getUuid());
+
+ CreateWsResponse response = wsActionTester.newRequest()
+ .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001)
+ .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001)
+ .executeProtobuf(CreateWsResponse.class);
+
+ assertThat(response.getWebhook()).isNotNull();
+ assertThat(response.getWebhook().getKey()).isNotNull();
+ assertThat(response.getWebhook().getName()).isEqualTo(NAME_WEBHOOK_EXAMPLE_001);
+ assertThat(response.getWebhook().getUrl()).isEqualTo(URL_WEBHOOK_EXAMPLE_001);
+
+ }
+
+ @Test
+ public void create_a_webhook_on_specific_organization() {
+
+ OrganizationDto organization = organizationDbTester.insert();
+
+ userSession.logIn().addPermission(ADMINISTER, organization.getUuid());
+
+ CreateWsResponse response = wsActionTester.newRequest()
+ .setParam(ORGANIZATION_KEY_PARAM, organization.getKey())
+ .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001)
+ .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001)
+ .executeProtobuf(CreateWsResponse.class);
+
+ assertThat(response.getWebhook()).isNotNull();
+ assertThat(response.getWebhook().getKey()).isNotNull();
+ assertThat(response.getWebhook().getName()).isEqualTo(NAME_WEBHOOK_EXAMPLE_001);
+ assertThat(response.getWebhook().getUrl()).isEqualTo(URL_WEBHOOK_EXAMPLE_001);
+
+ }
+
+ @Test
+ public void create_a_webhook_on_project() {
+
+ ComponentDto project = componentDbTester.insertPrivateProject();
+
+ userSession.logIn().addProjectPermission(ADMIN, project);
+
+ CreateWsResponse response = wsActionTester.newRequest()
+ .setParam(PROJECT_KEY_PARAM, project.getKey())
+ .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001)
+ .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001)
+ .executeProtobuf(CreateWsResponse.class);
+
+ assertThat(response.getWebhook()).isNotNull();
+ assertThat(response.getWebhook().getKey()).isNotNull();
+ assertThat(response.getWebhook().getName()).isEqualTo(NAME_WEBHOOK_EXAMPLE_001);
+ assertThat(response.getWebhook().getUrl()).isEqualTo(URL_WEBHOOK_EXAMPLE_001);
+
+ }
+
+ @Test
+ public void create_a_webhook_on_a_project_belonging_to_an_organization() {
+
+ OrganizationDto organization = organizationDbTester.insert();
+ ComponentDto project = componentDbTester.insertPrivateProject(organization);
+
+ userSession.logIn().addProjectPermission(ADMIN, project);
+
+ CreateWsResponse response = wsActionTester.newRequest()
+ .setParam(ORGANIZATION_KEY_PARAM, organization.getKey())
+ .setParam(PROJECT_KEY_PARAM, project.getKey())
+ .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001)
+ .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001)
+ .executeProtobuf(CreateWsResponse.class);
+
+ assertThat(response.getWebhook()).isNotNull();
+ assertThat(response.getWebhook().getKey()).isNotNull();
+ assertThat(response.getWebhook().getName()).isEqualTo(NAME_WEBHOOK_EXAMPLE_001);
+ assertThat(response.getWebhook().getUrl()).isEqualTo(URL_WEBHOOK_EXAMPLE_001);
+
+ }
+
+ @Test
+ public void fail_if_project_does_not_belong_to_requested_organization() {
+
+ OrganizationDto organization = organizationDbTester.insert();
+ ComponentDto project = componentDbTester.insertPrivateProject();
+
+ expectedException.expect(NotFoundException.class);
+ expectedException.expectMessage(format("Project '%s' does not belong to organisation '%s'", project.getKey(), organization.getKey()));
+
+ userSession.logIn().addProjectPermission(ADMIN, project);
+
+ wsActionTester.newRequest()
+ .setParam(ORGANIZATION_KEY_PARAM, organization.getKey())
+ .setParam(PROJECT_KEY_PARAM, project.getKey())
+ .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001)
+ .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001)
+ .execute();
+
+ }
+
+ @Test
+ public void fail_if_project_does_not_exists() {
+
+ expectedException.expect(NotFoundException.class);
+ expectedException.expectMessage("No project with key 'inexistent-project-uuid'");
+
+ userSession.logIn();
+
+ wsActionTester.newRequest()
+ .setParam(PROJECT_KEY_PARAM, "inexistent-project-uuid")
+ .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001)
+ .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001)
+ .execute();
+
+ }
+
+ @Test
+ public void fail_if_crossing_maximum_quantity_of_webhooks_on_this_project() {
+
+ ComponentDto project = componentDbTester.insertPrivateProject();
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage(format("Maximum number of webhook reached for project '%s'", project.getKey()));
+
+ for (int i = 0; i < 10; i++) {
+ webhookDbTester.insertWebhookForProjectUuid(project.uuid());
+ }
+ userSession.logIn().addProjectPermission(ADMIN, project);
+
+ wsActionTester.newRequest()
+ .setParam(PROJECT_KEY_PARAM, project.getKey())
+ .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001)
+ .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001)
+ .execute();
+
+ }
+
+ @Test
+ public void fail_if_crossing_maximum_quantity_of_webhooks_on_this_organization() {
+
+ OrganizationDto organization = organizationDbTester.insert();
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage(format("Maximum number of webhook reached for organization '%s'", organization.getKey()));
+
+ for (int i = 0; i < 10; i++) {
+ webhookDbTester.insertForOrganizationUuid(organization.getUuid());
+ }
+ userSession.logIn().addPermission(ADMINISTER, organization.getUuid());
+
+ wsActionTester.newRequest()
+ .setParam(ORGANIZATION_KEY_PARAM, organization.getKey())
+ .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001)
+ .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001)
+ .execute();
+ }
+
+ @Test
+ public void fail_if_organization_does_not_exists() {
+
+ expectedException.expect(NotFoundException.class);
+ expectedException.expectMessage("No organization with key 'inexistent-organization-uuid'");
+
+ userSession.logIn();
+
+ wsActionTester.newRequest()
+ .setParam(ORGANIZATION_KEY_PARAM, "inexistent-organization-uuid")
+ .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001)
+ .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001)
+ .execute();
+
+ }
+
+ @Test
+ public void fail_if_url_is_not_valid() throws Exception {
+
+ userSession.logIn().addPermission(ADMINISTER, defaultOrganizationProvider.get().getUuid());
+
+ expectedException.expect(IllegalArgumentException.class);
+
+ wsActionTester.newRequest()
+ .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001)
+ .setParam(URL_PARAM, "htp://www.wrong-protocol.com/")
+ .execute();
+
+ }
+
+ @Test
+ public void fail_if_credential_in_url_is_have_a_wrong_format() throws Exception {
+
+ userSession.logIn().addPermission(ADMINISTER, defaultOrganizationProvider.get().getUuid());
+
+ expectedException.expect(IllegalArgumentException.class);
+
+ wsActionTester.newRequest()
+ .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001)
+ .setParam(URL_PARAM, "http://:www.wrong-protocol.com/")
+ .execute();
+
+ }
+
+ @Test
+ public void return_UnauthorizedException_if_not_logged_in() throws Exception {
+
+ userSession.anonymous();
+ expectedException.expect(UnauthorizedException.class);
+
+ wsActionTester.newRequest()
+ .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001)
+ .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001)
+ .execute();
+
+ }
+
+ @Test
+ public void throw_ForbiddenException_if_no_organization_provided_and_user_is_not_system_administrator() {
+
+ userSession.logIn();
+
+ expectedException.expect(ForbiddenException.class);
+ expectedException.expectMessage("Insufficient privileges");
+
+ wsActionTester.newRequest()
+ .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001)
+ .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001)
+ .execute();
+ }
+
+ @Test
+ public void throw_ForbiddenException_if_organization_provided_but_user_is_not_organization_administrator() {
+
+ OrganizationDto organization = organizationDbTester.insert();
+
+ userSession.logIn();
+
+ expectedException.expect(ForbiddenException.class);
+ expectedException.expectMessage("Insufficient privileges");
+
+ wsActionTester.newRequest()
+ .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001)
+ .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001)
+ .setParam(ORGANIZATION_KEY_PARAM, organization.getKey())
+ .execute();
+
+ }
+
+ @Test
+ public void throw_ForbiddenException_if_not_project_administrator() {
+
+ ComponentDto project = componentDbTester.insertPrivateProject();
+
+ userSession.logIn();
+
+ expectedException.expect(ForbiddenException.class);
+ expectedException.expectMessage("Insufficient privileges");
+
+ wsActionTester.newRequest()
+ .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001)
+ .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001)
+ .setParam(PROJECT_KEY_PARAM, project.getKey())
+ .execute();
+
+ }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/WebhooksWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/WebhooksWsModuleTest.java
index a361235f99c..96aae56460a 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/WebhooksWsModuleTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/WebhooksWsModuleTest.java
@@ -32,7 +32,7 @@ public class WebhooksWsModuleTest {
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
underTest.configure(container);
- assertThat(container.size()).isEqualTo(3 + 3);
+ assertThat(container.size()).isEqualTo(3 + 4);
}
}
diff --git a/server/sonar-web/src/main/js/components/icons-components/AlertSuccessIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/AlertSuccessIcon.tsx
index 432461ad528..c0eadf86144 100644
--- a/server/sonar-web/src/main/js/components/icons-components/AlertSuccessIcon.tsx
+++ b/server/sonar-web/src/main/js/components/icons-components/AlertSuccessIcon.tsx
@@ -18,8 +18,8 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import * as theme from '../../app/theme';
import { IconProps } from './types';
+import * as theme from '../../app/theme';
export default function AlertSuccessIcon({ className, fill = theme.green, size = 16 }: IconProps) {
return (
diff --git a/sonar-ws/src/main/protobuf/ws-webhooks.proto b/sonar-ws/src/main/protobuf/ws-webhooks.proto
index 357bc403028..b8dc2a859b9 100644
--- a/sonar-ws/src/main/protobuf/ws-webhooks.proto
+++ b/sonar-ws/src/main/protobuf/ws-webhooks.proto
@@ -44,8 +44,16 @@ message SearchWsResponse {
}
}
+// POST api/webhooks/create
+message CreateWsResponse {
+ optional Webhook webhook = 1;
-
+ message Webhook {
+ optional string key = 1;
+ optional string name = 2;
+ optional string url = 3;
+ }
+}
// WS api/webhooks/deliveries
message DeliveriesWsResponse {