From 67ac2e4220a7ce34533d1c14c8f5f9652c37e08f Mon Sep 17 00:00:00 2001 From: Guillaume Jambet Date: Wed, 7 Feb 2018 11:35:25 +0100 Subject: [PATCH] SONAR-10345 Add Webhooks creation ws --- .../org/sonar/db/webhook/WebhookTesting.java | 49 +++ .../src/test/java/org/sonar/db/DbTester.java | 7 + .../org/sonar/db/webhook/WebhookDbTester.java | 48 +++ .../sonar/server/webhook/ws/CreateAction.java | 31 +- .../server/webhook/ws/WebhooksWsModule.java | 1 + .../webhook/ws/WebhooksWsParameters.java | 13 +- .../java/org/sonar/server/ws/KeyExamples.java | 3 + .../java/org/sonar/server/ws/WsUtils.java | 8 + .../webhook/ws/example-webhook-create.json | 7 + .../server/webhook/ws/CreateActionTest.java | 362 ++++++++++++++++++ .../webhook/ws/WebhooksWsModuleTest.java | 2 +- .../icons-components/AlertSuccessIcon.tsx | 2 +- sonar-ws/src/main/protobuf/ws-webhooks.proto | 10 +- 13 files changed, 525 insertions(+), 18 deletions(-) create mode 100644 server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookTesting.java create mode 100644 server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDbTester.java create mode 100644 server/sonar-server/src/main/resources/org/sonar/server/webhook/ws/example-webhook-create.json create mode 100644 server/sonar-server/src/test/java/org/sonar/server/webhook/ws/CreateActionTest.java 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 { 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 { 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 { 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 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 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 = 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 checkStateWithOptional(java.util.Optional 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 { -- 2.39.5