From 07a20e83c4037ab7853c57aa92e614d47be74cf4 Mon Sep 17 00:00:00 2001 From: Guillaume Jambet Date: Thu, 8 Feb 2018 12:07:36 +0100 Subject: [PATCH] SONAR-10345 Add Webhooks update ws --- .../java/org/sonar/db/webhook/WebhookDao.java | 15 +- .../org/sonar/db/webhook/WebhookMapper.java | 2 + .../org/sonar/db/webhook/WebhookMapper.xml | 8 + .../org/sonar/db/webhook/WebhookDaoTest.java | 39 ++- .../org/sonar/db/webhook/WebhookDbTester.java | 12 +- .../sonar/server/webhook/ws/CreateAction.java | 34 +-- .../sonar/server/webhook/ws/UpdateAction.java | 131 ++++++++++ .../server/webhook/ws/WebhookSupport.java | 24 +- .../server/webhook/ws/WebhooksWsModule.java | 2 + .../webhook/ws/WebhooksWsParameters.java | 3 + .../server/webhook/ws/CreateActionTest.java | 3 +- .../server/webhook/ws/UpdateActionTest.java | 246 ++++++++++++++++++ .../webhook/ws/WebhooksWsModuleTest.java | 2 +- 13 files changed, 474 insertions(+), 47 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/webhook/ws/UpdateAction.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/webhook/ws/UpdateActionTest.java diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDao.java index 9bec0fe477f..b972edbe665 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDao.java @@ -39,6 +39,14 @@ public class WebhookDao implements Dao { return Optional.ofNullable(mapper(dbSession).selectByUuid(uuid)); } + public List selectByOrganizationUuid(DbSession dbSession, String organizationUuid) { + return mapper(dbSession).selectForOrganizationUuidOrderedByName(organizationUuid); + } + + public List selectByProjectUuid(DbSession dbSession, String projectUuid) { + return mapper(dbSession).selectForProjectUuidOrderedByName(projectUuid); + } + public void insert(DbSession dbSession, WebhookDto dto) { checkState(dto.getOrganizationUuid() != null || dto.getProjectUuid() != null, @@ -48,14 +56,11 @@ public class WebhookDao implements Dao { "A webhook can not be linked to both an organization and a project."); mapper(dbSession).insert(dto.setCreatedAt(system2.now())); - } - public List selectByOrganizationUuid(DbSession dbSession, String organizationUuid) { - return mapper(dbSession).selectForOrganizationUuidOrderedByName(organizationUuid); } - public List selectByProjectUuid(DbSession dbSession, String projectUuid) { - return mapper(dbSession).selectForProjectUuidOrderedByName(projectUuid); + public void update(DbSession dbSession, WebhookDto dto) { + mapper(dbSession).update(dto.setUpdatedAt(system2.now())); } private static WebhookMapper mapper(DbSession dbSession) { diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookMapper.java index 12499745c35..3b5706c62bc 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookMapper.java @@ -34,4 +34,6 @@ public interface WebhookMapper { void insert(WebhookDto dto); + void update(WebhookDto dto); + } diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/webhook/WebhookMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/webhook/WebhookMapper.xml index c85df463612..0eb69994650 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/webhook/WebhookMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/webhook/WebhookMapper.xml @@ -55,4 +55,12 @@ ) + + update webhooks set + name=#{name}, + url=#{url}, + updated_at=#{updatedAt, jdbcType=BIGINT} + where uuid=#{uuid, jdbcType=VARCHAR} + + diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDaoTest.java index 4b52eaae3c7..01e8ef62225 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDaoTest.java @@ -28,6 +28,8 @@ import org.sonar.api.utils.System2; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.DbTester; +import org.sonar.db.organization.OrganizationDbTester; +import org.sonar.db.organization.OrganizationDto; import static org.assertj.core.api.Assertions.assertThat; @@ -44,6 +46,8 @@ public class WebhookDaoTest { private final DbClient dbClient = dbTester.getDbClient(); private final DbSession dbSession = dbTester.getSession(); private final WebhookDao underTest = dbClient.webhookDao(); + private final WebhookDbTester webhookDbTester = dbTester.webhooks(); + private final OrganizationDbTester organizationDbTester = dbTester.organizations(); @Test public void selectByUuid_returns_empty_if_uuid_does_not_exist() { @@ -83,17 +87,36 @@ public class WebhookDaoTest { underTest.insert(dbSession, dto); - WebhookDto stored = selectByUuid(dto.getUuid()); + WebhookDto reloaded = selectByUuid(dto.getUuid()); - assertThat(stored.getUuid()).isEqualTo(dto.getUuid()); - assertThat(stored.getName()).isEqualTo(dto.getName()); - assertThat(stored.getUrl()).isEqualTo(dto.getUrl()); - assertThat(stored.getOrganizationUuid()).isNull(); - assertThat(stored.getProjectUuid()).isEqualTo(dto.getProjectUuid()); - assertThat(new Date(stored.getCreatedAt())).isInSameMinuteWindowAs(new Date(system2.now())); - assertThat(stored.getUpdatedAt()).isNull(); + assertThat(reloaded.getUuid()).isEqualTo(dto.getUuid()); + assertThat(reloaded.getName()).isEqualTo(dto.getName()); + assertThat(reloaded.getUrl()).isEqualTo(dto.getUrl()); + assertThat(reloaded.getOrganizationUuid()).isNull(); + assertThat(reloaded.getProjectUuid()).isEqualTo(dto.getProjectUuid()); + assertThat(new Date(reloaded.getCreatedAt())).isInSameMinuteWindowAs(new Date(system2.now())); + assertThat(reloaded.getUpdatedAt()).isNull(); } + @Test + public void update() { + + OrganizationDto organization = organizationDbTester.insert(); + WebhookDto dto = webhookDbTester.insertForOrganizationUuid(organization.getUuid()); + + underTest.update(dbSession, dto.setName("a-fancy-webhook").setUrl("http://www.fancy-webhook.io")); + + WebhookDto reloaded = underTest.selectByUuid(dbSession, dto.uuid).get(); + assertThat(reloaded.getUuid()).isEqualTo(dto.getUuid()); + assertThat(reloaded.getName()).isEqualTo("a-fancy-webhook"); + assertThat(reloaded.getUrl()).isEqualTo("http://www.fancy-webhook.io"); + assertThat(reloaded.getProjectUuid()).isNull(); + assertThat(reloaded.getOrganizationUuid()).isEqualTo(dto.getOrganizationUuid()); + assertThat(reloaded.getCreatedAt()).isEqualTo(dto.getCreatedAt()); + assertThat(new Date(reloaded.getUpdatedAt())).isInSameMinuteWindowAs(new Date(system2.now())); + } + + @Test public void fail_if_webhook_does_not_have_an_organization_nor_a_project() { 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 index a0fcc49a0f3..7b744a52db6 100644 --- 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 @@ -19,9 +19,13 @@ */ package org.sonar.db.webhook; +import java.util.Optional; import org.sonar.db.DbSession; import org.sonar.db.DbTester; +import static org.sonar.db.webhook.WebhookTesting.newWebhookDtoForOrganization; +import static org.sonar.db.webhook.WebhookTesting.newWebhookDtoForProject; + public class WebhookDbTester { private final DbTester dbTester; @@ -31,11 +35,11 @@ public class WebhookDbTester { } public WebhookDto insertForOrganizationUuid(String organizationUuid) { - return insert(WebhookTesting.newWebhookDtoForOrganization(organizationUuid)); + return insert(newWebhookDtoForOrganization(organizationUuid)); } public WebhookDto insertWebhookForProjectUuid(String projectUuid) { - return insert(WebhookTesting.newWebhookDtoForProject(projectUuid)); + return insert(newWebhookDtoForProject(projectUuid)); } public WebhookDto insert(WebhookDto dto) { @@ -45,4 +49,8 @@ public class WebhookDbTester { return dto; } + public Optional selectWebhook(String uuid) { + DbSession dbSession = dbTester.getSession(); + return dbTester.getDbClient().webhookDao().selectByUuid(dbSession, uuid); + } } 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 b5962eb3814..f3ecf08f1ff 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 @@ -37,10 +37,7 @@ import org.sonarqube.ws.Webhooks; import static com.google.common.base.Preconditions.checkState; import static java.lang.String.format; -import static java.util.Locale.ENGLISH; import static org.apache.commons.lang.StringUtils.isNotBlank; -import static org.sonar.api.web.UserRole.ADMIN; -import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; import static org.sonar.server.webhook.ws.WebhooksWsParameters.ACTION_CREATE; import static org.sonar.server.webhook.ws.WebhooksWsParameters.NAME_PARAM; import static org.sonar.server.webhook.ws.WebhooksWsParameters.NAME_PARAM_MAXIMUM_LENGTH; @@ -68,12 +65,15 @@ public class CreateAction implements WebhooksWsAction { private final UserSession userSession; private final DefaultOrganizationProvider defaultOrganizationProvider; private final UuidFactory uuidFactory; + private final WebhookSupport webhookSupport; - public CreateAction(DbClient dbClient, UserSession userSession, DefaultOrganizationProvider defaultOrganizationProvider, UuidFactory uuidFactory) { + public CreateAction(DbClient dbClient, UserSession userSession, DefaultOrganizationProvider defaultOrganizationProvider, + UuidFactory uuidFactory, WebhookSupport webhookSupport) { this.dbClient = dbClient; this.userSession = userSession; this.defaultOrganizationProvider = defaultOrganizationProvider; this.uuidFactory = uuidFactory; + this.webhookSupport = webhookSupport; } @Override @@ -139,13 +139,13 @@ public class CreateAction implements WebhooksWsAction { 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); + webhookSupport.checkUserPermissionOn(componentDto); projectDto = componentDto; } else { - checkUserPermissionOn(organizationDto); + webhookSupport.checkUserPermissionOn(organizationDto); } - checkUrlPattern(url, "Url parameter with value '%s' is not a valid url", url); + webhookSupport.checkUrlPattern(url, "Url parameter with value '%s' is not a valid url", url); WebhookDto webhookDto = doHandle(dbSession, organizationDto, projectDto, name, url); @@ -166,7 +166,7 @@ public class CreateAction implements WebhooksWsAction { WebhookDto dto = new WebhookDto() .setUuid(uuidFactory.create()) .setName(name) - .setUrl(url) + .setUrl(url); if (project != null) { checkNumberOfWebhook(numberOfWebhookOf(dbSession, project), "Maximum number of webhook reached for project '%s'", project.getKey()); @@ -203,30 +203,12 @@ public class CreateAction implements WebhooksWsAction { return dbClient.webhookDao().selectByProjectUuid(dbSession, project.uuid()).size(); } - private void checkUserPermissionOn(ComponentDto componentDto) { - userSession.checkComponentPermission(ADMIN, componentDto); - } - - private void checkUserPermissionOn(OrganizationDto organizationDto) { - userSession.checkPermission(ADMINISTER, organizationDto); - } - private static void checkThatProjectBelongsToOrganization(ComponentDto componentDto, OrganizationDto organizationDto, String message, Object... messageArguments) { if (!organizationDto.getUuid().equals(componentDto.getOrganizationUuid())) { throw new NotFoundException(format(message, messageArguments)); } } - private static void checkUrlPattern(String url, String message, Object... messageArguments) { - if (!url.toLowerCase(ENGLISH).startsWith("http://") && !url.toLowerCase(ENGLISH).startsWith("https://")) { - throw new IllegalArgumentException(format(message, messageArguments)); - } - String sub = url.substring("http://".length()); - if (sub.contains(":") && !sub.substring(sub.indexOf(':')).contains("@")) { - throw new IllegalArgumentException(format(message, messageArguments)); - } - } - private OrganizationDto defaultOrganizationDto(DbSession dbSession) { String uuid = defaultOrganizationProvider.get().getUuid(); Optional organizationDto = dbClient.organizationDao().selectByUuid(dbSession, uuid); diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/UpdateAction.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/UpdateAction.java new file mode 100644 index 00000000000..e9b3a819771 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/UpdateAction.java @@ -0,0 +1,131 @@ +/* + * 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 java.util.Optional; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.db.webhook.WebhookDto; +import org.sonar.server.user.UserSession; + +import static java.util.Optional.ofNullable; +import static org.sonar.server.webhook.ws.WebhooksWsParameters.KEY_PARAM; +import static org.sonar.server.webhook.ws.WebhooksWsParameters.KEY_PARAM_MAXIMUN_LENGTH; +import static org.sonar.server.webhook.ws.WebhooksWsParameters.NAME_PARAM; +import static org.sonar.server.webhook.ws.WebhooksWsParameters.NAME_PARAM_MAXIMUM_LENGTH; +import static org.sonar.server.webhook.ws.WebhooksWsParameters.UPDATE_ACTION; +import static org.sonar.server.webhook.ws.WebhooksWsParameters.URL_PARAM; +import static org.sonar.server.webhook.ws.WebhooksWsParameters.URL_PARAM_MAXIMUM_LENGTH; +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; + +public class UpdateAction implements WebhooksWsAction { + + private final DbClient dbClient; + private final UserSession userSession; + private final WebhookSupport webhookSupport; + + public UpdateAction(DbClient dbClient, UserSession userSession, WebhookSupport webhookSupport) { + this.dbClient = dbClient; + this.userSession = userSession; + this.webhookSupport = webhookSupport; + } + + @Override + public void define(WebService.NewController controller) { + + WebService.NewAction action = controller.createAction(UPDATE_ACTION) + .setPost(true) + .setDescription("Update a Webhook.
" + + "Requires the global, organization or project permission.") + .setSince("7.1") + .setHandler(this); + + action.createParam(KEY_PARAM) + .setRequired(true) + .setMaximumLength(KEY_PARAM_MAXIMUN_LENGTH) + .setDescription("The key of the webhook to be updated") + .setExampleValue(KEY_PROJECT_EXAMPLE_001); + + action.createParam(NAME_PARAM) + .setRequired(true) + .setMaximumLength(NAME_PARAM_MAXIMUM_LENGTH) + .setDescription("new name of the webhook") + .setExampleValue(NAME_WEBHOOK_EXAMPLE_001); + + action.createParam(URL_PARAM) + .setRequired(true) + .setMaximumLength(URL_PARAM_MAXIMUM_LENGTH) + .setDescription("new url to be called by the webhook") + .setExampleValue(URL_WEBHOOK_EXAMPLE_001); + + } + + @Override + public void handle(Request request, Response response) throws Exception { + + userSession.checkLoggedIn(); + + String webhookKey = request.param(KEY_PARAM); + String name = request.mandatoryParam(NAME_PARAM); + String url = request.mandatoryParam(URL_PARAM); + + webhookSupport.checkUrlPattern(url, "Url parameter with value '%s' is not a valid url", url); + + try (DbSession dbSession = dbClient.openSession(false)) { + + Optional dtoOptional = dbClient.webhookDao().selectByUuid(dbSession, webhookKey); + WebhookDto webhookDto = checkFoundWithOptional(dtoOptional, "No webhook with key '%s'", webhookKey); + + String organizationUuid = webhookDto.getOrganizationUuid(); + if (organizationUuid != null) { + Optional optionalDto = dbClient.organizationDao().selectByUuid(dbSession, organizationUuid); + OrganizationDto organizationDto = checkStateWithOptional(optionalDto, "the requested organization '%s' was not found", organizationUuid); + webhookSupport.checkUserPermissionOn(organizationDto); + updateWebhook(dbSession, webhookDto, name, url); + } + + String projectUuid = webhookDto.getProjectUuid(); + if (projectUuid != null) { + Optional optionalDto = ofNullable(dbClient.componentDao().selectByUuid(dbSession, projectUuid).orNull()); + ComponentDto componentDto = checkStateWithOptional(optionalDto, "the requested project '%s' was not found", projectUuid); + webhookSupport.checkUserPermissionOn(componentDto); + updateWebhook(dbSession, webhookDto, name, url); + } + + dbSession.commit(); + } + + response.noContent(); + } + + private void updateWebhook(DbSession dbSession, WebhookDto webhookDto, String name, String url) { + dbClient.webhookDao().update(dbSession, webhookDto.setName(name).setUrl(url)); + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhookSupport.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhookSupport.java index 3201daf29ba..2d6257069d4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhookSupport.java +++ b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhookSupport.java @@ -19,16 +19,32 @@ */ package org.sonar.server.webhook.ws; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.server.user.UserSession; + import static java.lang.String.format; import static java.util.Locale.ENGLISH; +import static org.sonar.api.web.UserRole.ADMIN; +import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; + +public class WebhookSupport { + + private final UserSession userSession; -class WebhookSupport { + public WebhookSupport(UserSession userSession) { + this.userSession = userSession; + } + + void checkUserPermissionOn(ComponentDto componentDto) { + userSession.checkComponentPermission(ADMIN, componentDto); + } - private WebhookSupport() { - // only statics + void checkUserPermissionOn(OrganizationDto organizationDto) { + userSession.checkPermission(ADMINISTER, organizationDto); } - static void checkUrlPattern(String url, String message, Object... messageArguments) { + void checkUrlPattern(String url, String message, Object... messageArguments) { if (!url.toLowerCase(ENGLISH).startsWith("http://") && !url.toLowerCase(ENGLISH).startsWith("https://")) { throw new IllegalArgumentException(format(message, messageArguments)); } 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 92334805107..3927c5da41b 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 @@ -25,9 +25,11 @@ public class WebhooksWsModule extends Module { @Override protected void configureModule() { add( + WebhookSupport.class, WebhooksWs.class, SearchAction.class, CreateAction.class, + UpdateAction.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 0d800f476c6..10b2c7b0498 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 @@ -25,6 +25,7 @@ class WebhooksWsParameters { static final String ACTION_CREATE = "create"; + static final String UPDATE_ACTION = "update"; static final String SEARCH_ACTION = "search"; @@ -38,6 +39,8 @@ class WebhooksWsParameters { static final int URL_PARAM_MAXIMUM_LENGTH = 512; static final String COMPONENT_KEY_PARAM = "component"; static final int COMPONENT_KEY_PARAM_MAXIMUM_LENGTH = 255; + static final String KEY_PARAM = "key"; + static final int KEY_PARAM_MAXIMUN_LENGTH = 40; private WebhooksWsParameters() { // prevent instantiation 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 index 3ffbf303afa..fc3f7bcdfb4 100644 --- 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 @@ -74,7 +74,8 @@ public class CreateActionTest { 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 WebhookSupport webhookSupport = new WebhookSupport(userSession); + private org.sonar.server.webhook.ws.CreateAction underTest = new CreateAction(dbClient, userSession, defaultOrganizationProvider, uuidFactory, webhookSupport); private WsActionTester wsActionTester = new WsActionTester(underTest); @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/UpdateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/UpdateActionTest.java new file mode 100644 index 00000000000..0bf1e5ebc29 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/UpdateActionTest.java @@ -0,0 +1,246 @@ +/* + * 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 java.util.Optional; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.server.ws.WebService; +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.db.webhook.WebhookDto; +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.TestResponse; +import org.sonar.server.ws.WsActionTester; + +import static java.net.HttpURLConnection.HTTP_NO_CONTENT; +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.KEY_PARAM; +import static org.sonar.server.webhook.ws.WebhooksWsParameters.NAME_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 UpdateActionTest { + + @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 WebhookSupport webhookSupport = new WebhookSupport(userSession); + private UpdateAction underTest = new UpdateAction(dbClient, userSession, webhookSupport); + 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.params()) + .extracting(WebService.Param::key, WebService.Param::isRequired) + .containsExactlyInAnyOrder( + tuple("key", true), + tuple("name", true), + tuple("url", true)); + + } + + @Test + public void update_a_project_webhook() { + + ComponentDto project = componentDbTester.insertPrivateProject(); + WebhookDto dto = webhookDbTester.insertWebhookForProjectUuid(project.uuid()); + userSession.logIn().addProjectPermission(ADMIN, project); + + TestResponse response = wsActionTester.newRequest() + .setParam(KEY_PARAM, dto.getUuid()) + .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) + .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001) + .execute(); + + assertThat(response.getStatus()).isEqualTo(HTTP_NO_CONTENT); + Optional reloaded = webhookDbTester.selectWebhook(dto.getUuid()); + assertThat(reloaded.get()).isNotNull(); + assertThat(reloaded.get().getName()).isEqualTo(NAME_WEBHOOK_EXAMPLE_001); + assertThat(reloaded.get().getUrl()).isEqualTo(URL_WEBHOOK_EXAMPLE_001); + assertThat(reloaded.get().getOrganizationUuid()).isNull(); + assertThat(reloaded.get().getProjectUuid()).isEqualTo(dto.getProjectUuid()); + + } + + @Test + public void update_an_organization_webhook() { + + OrganizationDto organization = organizationDbTester.insert(); + WebhookDto dto = webhookDbTester.insertForOrganizationUuid(organization.getUuid()); + userSession.logIn().addPermission(ADMINISTER, organization.getUuid()); + + TestResponse response = wsActionTester.newRequest() + .setParam(KEY_PARAM, dto.getUuid()) + .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) + .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001) + .execute(); + + assertThat(response.getStatus()).isEqualTo(HTTP_NO_CONTENT); + Optional reloaded = webhookDbTester.selectWebhook(dto.getUuid()); + assertThat(reloaded.get()).isNotNull(); + assertThat(reloaded.get().getName()).isEqualTo(NAME_WEBHOOK_EXAMPLE_001); + assertThat(reloaded.get().getUrl()).isEqualTo(URL_WEBHOOK_EXAMPLE_001); + assertThat(reloaded.get().getOrganizationUuid()).isEqualTo(dto.getOrganizationUuid()); + assertThat(reloaded.get().getProjectUuid()).isNull(); + + } + + @Test + public void fail_if_webhook_does_not_exist() { + + userSession.logIn().addPermission(ADMINISTER, defaultOrganizationProvider.get().getUuid()); + + expectedException.expect(NotFoundException.class); + expectedException.expectMessage("No webhook with key 'inexistent-webhook-uuid'"); + + wsActionTester.newRequest() + .setParam(KEY_PARAM, "inexistent-webhook-uuid") + .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) + .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001) + .execute(); + } + + @Test + public void fail_if_not_logged_in() throws Exception { + + OrganizationDto organization = organizationDbTester.insert(); + WebhookDto dto = webhookDbTester.insertForOrganizationUuid(organization.getUuid()); + userSession.anonymous(); + + expectedException.expect(UnauthorizedException.class); + + wsActionTester.newRequest() + .setParam(KEY_PARAM, dto.getUuid()) + .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) + .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001) + .execute(); + + } + + @Test + public void fail_if_no_permission_on_webhook_scope_project() { + + ComponentDto project = componentDbTester.insertPrivateProject(); + WebhookDto dto = webhookDbTester.insertWebhookForProjectUuid(project.uuid()); + + userSession.logIn(); + + expectedException.expect(ForbiddenException.class); + expectedException.expectMessage("Insufficient privileges"); + + wsActionTester.newRequest() + .setParam(KEY_PARAM, dto.getUuid()) + .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) + .setParam(URL_PARAM, URL_WEBHOOK_EXAMPLE_001) + .execute(); + + } + + @Test + public void fail_if_no_permission_on_webhook_scope_organization() { + + OrganizationDto organization = organizationDbTester.insert(); + WebhookDto dto = webhookDbTester.insertForOrganizationUuid(organization.getUuid()); + + userSession.logIn(); + + expectedException.expect(ForbiddenException.class); + expectedException.expectMessage("Insufficient privileges"); + + wsActionTester.newRequest() + .setParam(KEY_PARAM, dto.getUuid()) + .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 { + + ComponentDto project = componentDbTester.insertPrivateProject(); + WebhookDto dto = webhookDbTester.insertWebhookForProjectUuid(project.uuid()); + userSession.logIn().addProjectPermission(ADMIN, project); + + expectedException.expect(IllegalArgumentException.class); + + wsActionTester.newRequest() + .setParam(KEY_PARAM, dto.getUuid()) + .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 { + + ComponentDto project = componentDbTester.insertPrivateProject(); + WebhookDto dto = webhookDbTester.insertWebhookForProjectUuid(project.uuid()); + userSession.logIn().addProjectPermission(ADMIN, project); + + expectedException.expect(IllegalArgumentException.class); + + wsActionTester.newRequest() + .setParam(KEY_PARAM, dto.getUuid()) + .setParam(NAME_PARAM, NAME_WEBHOOK_EXAMPLE_001) + .setParam(URL_PARAM, "http://:www.wrong-protocol.com/") + .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 96aae56460a..7afb02511bd 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 + 4); + assertThat(container.size()).isEqualTo(3 + 6); } } -- 2.39.5