]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10345 Add Webhooks creation ws
authorGuillaume Jambet <guillaume.jambet@sonarsource.com>
Wed, 7 Feb 2018 10:35:25 +0000 (11:35 +0100)
committerGuillaume Jambet <guillaume.jambet@gmail.com>
Thu, 1 Mar 2018 14:21:05 +0000 (15:21 +0100)
13 files changed:
server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookTesting.java [new file with mode: 0644]
server/sonar-db-dao/src/test/java/org/sonar/db/DbTester.java
server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDbTester.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/webhook/ws/CreateAction.java
server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhooksWsModule.java
server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhooksWsParameters.java
server/sonar-server/src/main/java/org/sonar/server/ws/KeyExamples.java
server/sonar-server/src/main/java/org/sonar/server/ws/WsUtils.java
server/sonar-server/src/main/resources/org/sonar/server/webhook/ws/example-webhook-create.json [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/webhook/ws/CreateActionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/webhook/ws/WebhooksWsModuleTest.java
server/sonar-web/src/main/js/components/icons-components/AlertSuccessIcon.tsx
sonar-ws/src/main/protobuf/ws-webhooks.proto

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 (file)
index 0000000..0eb167c
--- /dev/null
@@ -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());
+  }
+}
index f4d7d81ffd73e1b6874944d5a93ee928c5dc8506..19b4b44a15e68c7c3d23322e1ee297d8bdcb86c5 100644 (file)
@@ -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 (file)
index 0000000..a0fcc49
--- /dev/null
@@ -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;
+  }
+
+}
index 029b30666ad258a0a06da82c7f43858f46959709..b5962eb3814dc24cd83ef0741f1dd7c78bf55d02 100644 (file)
@@ -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);
   }
 }
index 3f589d19b6058a1a428da02489ff771bff214d6c..92334805107e9123aca3edeef01c97a4f95c2f7b 100644 (file)
@@ -27,6 +27,7 @@ public class WebhooksWsModule extends Module {
     add(
       WebhooksWs.class,
       SearchAction.class,
+      CreateAction.class,
       WebhookDeliveryAction.class,
       WebhookDeliveriesAction.class);
   }
index 7430e3f2f1dea556c91cd2570501fd1602a50548..0d800f476c64e844e1762dcc5e2508c10d8c47ce 100644 (file)
@@ -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
   }
 
 }
index b24ac11fa18baf3b8f9aeaa12daf42263a0dd12a..959ec946aa01e152cfb75c831d5e3fa420bcb9de 100644 (file)
@@ -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
   }
index d028f4879fa039f22523ee06a6e46793db6af583..73c837cc15569c8609b91a331961f1d3cfaa7ee2 100644 (file)
@@ -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 (file)
index 0000000..4ef726a
--- /dev/null
@@ -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 (file)
index 0000000..3ffbf30
--- /dev/null
@@ -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();
+
+  }
+
+}
index a361235f99ca1204f15bbdf8ac57384cf726f99b..96aae56460a3839b223b0bef6139f267ff03887e 100644 (file)
@@ -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);
   }
 
 }
index 432461ad528cbfb07687121b0a46cf31d6f854c7..c0eadf86144850eb873599d3185119de14da33cf 100644 (file)
@@ -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 (
index 357bc4030280e757d04387e1f68bdb32998dfc3f..b8dc2a859b917ed80d880d40d5ac5b130f77347d 100644 (file)
@@ -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 {