]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10345 Update Webhooks search ws to use new WEBHOOKS table.
authorGuillaume Jambet <guillaume.jambet@sonarsource.com>
Thu, 8 Feb 2018 15:18:05 +0000 (16:18 +0100)
committerGuillaume Jambet <guillaume.jambet@gmail.com>
Thu, 1 Mar 2018 14:21:05 +0000 (15:21 +0100)
27 files changed:
server/sonar-db-core/src/main/resources/org/sonar/db/version/schema-h2.ddl
server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDto.java
server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookTesting.java [deleted file]
server/sonar-db-dao/src/main/resources/org/sonar/db/webhook/WebhookMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookDbTester.java
server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookTesting.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v71/CreateWebhooksTable.java
server/sonar-server/src/main/java/org/sonar/server/webhook/WebHooksImpl.java
server/sonar-server/src/main/java/org/sonar/server/webhook/ws/CreateAction.java
server/sonar-server/src/main/java/org/sonar/server/webhook/ws/DeleteAction.java
server/sonar-server/src/main/java/org/sonar/server/webhook/ws/ListAction.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/webhook/ws/SearchAction.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/webhook/ws/UpdateAction.java
server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhookSupport.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/resources/org/sonar/server/webhook/ws/example-webhook-create.json
server/sonar-server/src/test/java/org/sonar/server/webhook/AsynchronousWebHooksImplTest.java
server/sonar-server/src/test/java/org/sonar/server/webhook/SynchronousWebHooksImplTest.java
server/sonar-server/src/test/java/org/sonar/server/webhook/ws/CreateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/webhook/ws/DeleteActionTest.java
server/sonar-server/src/test/java/org/sonar/server/webhook/ws/ListActionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/webhook/ws/SearchActionTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/webhook/ws/UpdateActionTest.java

index e965838d04821f76cf1e597b8e44c6928c7abc3e..3ab27f0b1eaa08c9f99cf55c80ad5c66a825da9e 100644 (file)
@@ -760,7 +760,7 @@ CREATE TABLE "WEBHOOKS" (
   "ORGANIZATION_UUID" VARCHAR(40),
   "PROJECT_UUID" VARCHAR(40),
   "CREATED_AT" BIGINT NOT NULL,
-  "UPDATED_AT" BIGINT
+  "UPDATED_AT" BIGINT NOT NULL
 );
 CREATE UNIQUE INDEX "PK_WEBHOOKS" ON "WEBHOOKS" ("UUID");
 CREATE INDEX "ORGANIZATION_WEBHOOK" ON "WEBHOOKS" ("ORGANIZATION_UUID");
index 0711adecf5c135a870d9c211c3948c55b57fd14c..8dafc97939033a2cf060082758da9885c8bc0983 100644 (file)
@@ -24,6 +24,8 @@ import java.util.Optional;
 import org.sonar.api.utils.System2;
 import org.sonar.db.Dao;
 import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.organization.OrganizationDto;
 
 import static com.google.common.base.Preconditions.checkState;
 
@@ -39,24 +41,20 @@ public class WebhookDao implements Dao {
     return Optional.ofNullable(mapper(dbSession).selectByUuid(uuid));
   }
 
-  public List<WebhookDto> selectByOrganizationUuid(DbSession dbSession, String organizationUuid) {
-    return mapper(dbSession).selectForOrganizationUuidOrderedByName(organizationUuid);
+  public List<WebhookDto> selectByOrganizationUuid(DbSession dbSession, OrganizationDto organizationDto) {
+    return mapper(dbSession).selectForOrganizationUuidOrderedByName(organizationDto.getUuid());
   }
 
-  public List<WebhookDto> selectByProjectUuid(DbSession dbSession, String projectUuid) {
-    return mapper(dbSession).selectForProjectUuidOrderedByName(projectUuid);
+  public List<WebhookDto> selectByProjectUuid(DbSession dbSession, ComponentDto componentDto) {
+    return mapper(dbSession).selectForProjectUuidOrderedByName(componentDto.uuid());
   }
 
   public void insert(DbSession dbSession, WebhookDto dto) {
-
     checkState(dto.getOrganizationUuid() != null || dto.getProjectUuid() != null,
       "A webhook can not be created if not linked to an organization or a project.");
-
     checkState(dto.getOrganizationUuid() == null || dto.getProjectUuid() == null,
       "A webhook can not be linked to both an organization and a project.");
-
-    mapper(dbSession).insert(dto.setCreatedAt(system2.now()));
-
+    mapper(dbSession).insert(dto.setCreatedAt(system2.now()).setUpdatedAt(system2.now()));
   }
 
   public void update(DbSession dbSession, WebhookDto dto) {
index d03e9f0816640eca94978446c6fe26485565fd4b..5c783098bddb0ffb777abf7adc7eee1a4d850202 100644 (file)
@@ -24,23 +24,20 @@ import javax.annotation.Nullable;
 public class WebhookDto {
 
   /** Technical unique identifier, can't be null */
-  protected String uuid;
+  private String uuid;
   /** Name, can't be null */
-  protected String name;
+  private String name;
   /** URL, can't be null */
-  protected String url;
+  private String url;
 
   @Nullable
-  protected String organizationUuid;
+  private String organizationUuid;
 
   @Nullable
-  protected String projectUuid;
+  private String projectUuid;
 
-  /** createdAt, can't be null */
-  protected Long createdAt;
-  /** URL, can be null */
-  @Nullable
-  protected Long updatedAt;
+  private long createdAt;
+  private long updatedAt;
 
   public WebhookDto setUuid(String uuid) {
     this.uuid = uuid;
@@ -67,12 +64,12 @@ public class WebhookDto {
     return this;
   }
 
-  WebhookDto setCreatedAt(Long createdAt) {
+  WebhookDto setCreatedAt(long createdAt) {
     this.createdAt = createdAt;
     return this;
   }
 
-  WebhookDto setUpdatedAt(@Nullable Long updatedAt) {
+  WebhookDto setUpdatedAt(long updatedAt) {
     this.updatedAt = updatedAt;
     return this;
   }
@@ -99,12 +96,12 @@ public class WebhookDto {
     return projectUuid;
   }
 
-  public Long getCreatedAt() {
+  public long getCreatedAt() {
     return createdAt;
   }
 
   @Nullable
-  public Long getUpdatedAt() {
+  public long getUpdatedAt() {
     return updatedAt;
   }
 }
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
deleted file mode 100644 (file)
index 0eb167c..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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 33171e72276ed1ea5eecbe40d55a21693d59f14e..bdba2cdec540cf0f9c2713e421af36280e9c045d 100644 (file)
@@ -4,7 +4,7 @@
 
 <mapper namespace="org.sonar.db.webhook.WebhookMapper">
 
-  <sql id="sqlLiteColumns">
+  <sql id="sqlColumns">
     uuid,
     name,
     url,
 
 
   <select id="selectByUuid" parameterType="String" resultType="org.sonar.db.webhook.WebhookDto">
-    select <include refid="sqlLiteColumns" />
+    select <include refid="sqlColumns" />
     from webhooks
     where uuid = #{webhookUuid,jdbcType=VARCHAR}
   </select>
 
   <select id="selectForOrganizationUuidOrderedByName" parameterType="String" resultType="org.sonar.db.webhook.WebhookDto">
-    select <include refid="sqlLiteColumns" />
+    select <include refid="sqlColumns" />
     from webhooks
     where organization_uuid = #{organizationUuid,jdbcType=VARCHAR}
     order by name asc
   </select>
 
   <select id="selectForProjectUuidOrderedByName" parameterType="String" resultType="org.sonar.db.webhook.WebhookDto">
-    select <include refid="sqlLiteColumns" />
+    select <include refid="sqlColumns" />
     from webhooks
     where project_uuid = #{projectUuid,jdbcType=VARCHAR}
     order by name asc
index 2dafd3b90a910ea2b7ff684c7afb416d411097df..2b16e6535eb472f914494acdc97277d8a238654d 100644 (file)
@@ -73,7 +73,7 @@ public class WebhookDaoTest {
     assertThat(stored.getOrganizationUuid()).isEqualTo(dto.getOrganizationUuid());
     assertThat(stored.getProjectUuid()).isNull();
     assertThat(new Date(stored.getCreatedAt())).isInSameMinuteWindowAs(new Date(system2.now()));
-    assertThat(stored.getUpdatedAt()).isNull();
+    assertThat(new Date(stored.getUpdatedAt())).isInSameMinuteWindowAs(new Date(system2.now()));
   }
 
   @Test
@@ -95,18 +95,18 @@ public class WebhookDaoTest {
     assertThat(reloaded.getOrganizationUuid()).isNull();
     assertThat(reloaded.getProjectUuid()).isEqualTo(dto.getProjectUuid());
     assertThat(new Date(reloaded.getCreatedAt())).isInSameMinuteWindowAs(new Date(system2.now()));
-    assertThat(reloaded.getUpdatedAt()).isNull();
+    assertThat(new Date(reloaded.getUpdatedAt())).isInSameMinuteWindowAs(new Date(system2.now()));
   }
 
   @Test
   public void update() {
 
     OrganizationDto organization = organizationDbTester.insert();
-    WebhookDto dto = webhookDbTester.insertForOrganizationUuid(organization.getUuid());
+    WebhookDto dto = webhookDbTester.insertWebhook(organization);
 
     underTest.update(dbSession, dto.setName("a-fancy-webhook").setUrl("http://www.fancy-webhook.io"));
 
-    WebhookDto reloaded = underTest.selectByUuid(dbSession, dto.uuid).get();
+    WebhookDto reloaded = underTest.selectByUuid(dbSession, dto.getUuid()).get();
     assertThat(reloaded.getUuid()).isEqualTo(dto.getUuid());
     assertThat(reloaded.getName()).isEqualTo("a-fancy-webhook");
     assertThat(reloaded.getUrl()).isEqualTo("http://www.fancy-webhook.io");
@@ -120,11 +120,11 @@ public class WebhookDaoTest {
   public void delete() {
 
     OrganizationDto organization = organizationDbTester.insert();
-    WebhookDto dto = webhookDbTester.insertForOrganizationUuid(organization.getUuid());
+    WebhookDto dto = webhookDbTester.insertWebhook(organization);
 
     underTest.delete(dbSession, dto.getUuid());
 
-    Optional<WebhookDto> reloaded = underTest.selectByUuid(dbSession, dto.uuid);
+    Optional<WebhookDto> reloaded = underTest.selectByUuid(dbSession, dto.getUuid());
     assertThat(reloaded).isEmpty();
   }
 
index 7b744a52db6b45cde8f4c4bc1e06cd4e3fa5b6c2..b5fa03eae33c87def34a81dd68ce07ecbabf5139 100644 (file)
@@ -22,9 +22,10 @@ package org.sonar.db.webhook;
 import java.util.Optional;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.organization.OrganizationDto;
 
-import static org.sonar.db.webhook.WebhookTesting.newWebhookDtoForOrganization;
-import static org.sonar.db.webhook.WebhookTesting.newWebhookDtoForProject;
+import static org.sonar.db.webhook.WebhookTesting.newWebhook;
 
 public class WebhookDbTester {
 
@@ -34,12 +35,12 @@ public class WebhookDbTester {
     this.dbTester = dbTester;
   }
 
-  public WebhookDto insertForOrganizationUuid(String organizationUuid) {
-    return insert(newWebhookDtoForOrganization(organizationUuid));
+  public WebhookDto insertWebhook(OrganizationDto organizationDto) {
+    return insert(newWebhook(organizationDto));
   }
 
-  public WebhookDto insertWebhookForProjectUuid(String projectUuid) {
-    return insert(newWebhookDtoForProject(projectUuid));
+  public WebhookDto insertWebhook(ComponentDto project) {
+    return insert(newWebhook(project));
   }
 
   public WebhookDto insert(WebhookDto dto) {
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookTesting.java b/server/sonar-db-dao/src/test/java/org/sonar/db/webhook/WebhookTesting.java
new file mode 100644 (file)
index 0000000..0c56b9a
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * 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 org.sonar.db.component.ComponentDto;
+import org.sonar.db.organization.OrganizationDto;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+
+public class WebhookTesting {
+
+  private WebhookTesting() {
+    // only statics
+  }
+
+  public static WebhookDto newWebhook(ComponentDto project) {
+    return getWebhookDto()
+      .setProjectUuid(project.uuid());
+  }
+
+  public static WebhookDto newWebhook(OrganizationDto organizationDto) {
+    return getWebhookDto()
+      .setOrganizationUuid(organizationDto.getUuid());
+  }
+
+  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 a82bf22ede62a8b5f58b8d94cafe5aab5ad055c7..5ea0e721cdcf36312c85c2d7b2c0ea48aec799fb 100644 (file)
@@ -45,17 +45,15 @@ public class CreateWebhooksTable extends DdlChange {
     .setIsNullable(false)
     .setLimit(UUID_SIZE)
     .build();
-  private static final int NAME_COLUMN_SIZE = 100;
   private static final VarcharColumnDef NAME_COLUMN = newVarcharColumnDefBuilder()
     .setColumnName("name")
     .setIsNullable(false)
-    .setLimit(NAME_COLUMN_SIZE)
+    .setLimit(100)
     .build();
-  private static final int URL_COLUMN_SIZE = 2000;
   private static final VarcharColumnDef URL_COLUMN = newVarcharColumnDefBuilder()
     .setColumnName("url")
     .setIsNullable(false)
-    .setLimit(URL_COLUMN_SIZE)
+    .setLimit(2000)
     .build();
   private static final VarcharColumnDef ORGANIZATION_UUID_COLUMN = newVarcharColumnDefBuilder()
     .setColumnName("organization_uuid")
@@ -76,11 +74,8 @@ public class CreateWebhooksTable extends DdlChange {
     .setIsNullable(true)
     .build();
 
-  private Database db;
-
   public CreateWebhooksTable(Database db) {
     super(db);
-    this.db = db;
   }
 
   @Override
@@ -119,7 +114,7 @@ public class CreateWebhooksTable extends DdlChange {
   }
 
   private boolean tableExists() throws SQLException {
-    try (Connection connection = db.getDataSource().getConnection()) {
+    try (Connection connection = getDatabase().getDataSource().getConnection()) {
       return DatabaseUtils.tableExists(TABLE_NAME, connection);
     }
   }
index cae1aa5eaf0e865a41d545c2f351e02faa7825e7..db41cfeedaa37b13c886aa49b3c5b790909f94a0 100644 (file)
  */
 package org.sonar.server.webhook;
 
-import java.util.Arrays;
 import java.util.List;
-import java.util.Objects;
 import java.util.Optional;
 import java.util.function.Supplier;
 import java.util.stream.Stream;
-import org.sonar.api.config.Configuration;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
-import org.sonar.core.config.WebhookProperties;
 import org.sonar.core.util.stream.MoreCollectors;
+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.WebhookDao;
+import org.sonar.db.webhook.WebhookDto;
 import org.sonar.server.async.AsyncExecution;
 
-import static java.lang.String.format;
-import static org.sonar.core.config.WebhookProperties.MAX_WEBHOOKS_PER_TYPE;
+import static java.util.Optional.ofNullable;
+import static org.sonar.server.ws.WsUtils.checkStateWithOptional;
 
 public class WebHooksImpl implements WebHooks {
 
   private static final Logger LOGGER = Loggers.get(WebHooksImpl.class);
-  private static final String WEBHOOK_PROPERTY_FORMAT = "%s.%s";
 
   private final WebhookCaller caller;
   private final WebhookDeliveryStorage deliveryStorage;
   private final AsyncExecution asyncExecution;
+  private final DbClient dbClient;
 
-  public WebHooksImpl(WebhookCaller caller, WebhookDeliveryStorage deliveryStorage, AsyncExecution asyncExecution) {
+  public WebHooksImpl(WebhookCaller caller, WebhookDeliveryStorage deliveryStorage, AsyncExecution asyncExecution, DbClient dbClient) {
     this.caller = caller;
     this.deliveryStorage = deliveryStorage;
     this.asyncExecution = asyncExecution;
+    this.dbClient = dbClient;
   }
 
   @Override
-  public boolean isEnabled(Configuration config) {
-    return readWebHooksFrom(config)
+  public boolean isEnabled(ComponentDto projectDto) {
+    return readWebHooksFrom(projectDto)
       .findAny()
       .isPresent();
   }
 
-  public static Stream<NameUrl> readWebHooksFrom(Configuration config) {
-    return Stream.concat(
-      getWebhookProperties(config, WebhookProperties.GLOBAL_KEY).stream(),
-      getWebhookProperties(config, WebhookProperties.PROJECT_KEY).stream())
-      .map(
-        webHookProperty -> {
-          String name = config.get(format(WEBHOOK_PROPERTY_FORMAT, webHookProperty, WebhookProperties.NAME_FIELD)).orElse(null);
-          String url = config.get(format(WEBHOOK_PROPERTY_FORMAT, webHookProperty, WebhookProperties.URL_FIELD)).orElse(null);
-          if (name == null || url == null) {
-            return null;
-          }
-          return new NameUrl(name, url);
-        })
-      .filter(Objects::nonNull);
-  }
-
-  private static List<String> getWebhookProperties(Configuration config, String propertyKey) {
-    String[] webhookIds = config.getStringArray(propertyKey);
-    return Arrays.stream(webhookIds)
-      .map(webhookId -> format(WEBHOOK_PROPERTY_FORMAT, propertyKey, webhookId))
-      .limit(MAX_WEBHOOKS_PER_TYPE)
-      .collect(MoreCollectors.toList(webhookIds.length));
+  private Stream<WebhookDto> readWebHooksFrom(ComponentDto projectDto) {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      Optional<OrganizationDto> optionalDto = dbClient.organizationDao().selectByUuid(dbSession, projectDto.getOrganizationUuid());
+      OrganizationDto organizationDto = checkStateWithOptional(optionalDto, "the requested organization '%s' was not found", projectDto.getOrganizationUuid());
+      WebhookDao dao = dbClient.webhookDao();
+      return Stream.concat(
+        dao.selectByProjectUuid(dbSession, projectDto).stream(),
+        dao.selectByOrganizationUuid(dbSession, organizationDto).stream());
+    }
   }
 
   @Override
-  public void sendProjectAnalysisUpdate(Configuration config, Analysis analysis, Supplier<WebhookPayload> payloadSupplier) {
-    List<Webhook> webhooks = readWebHooksFrom(config)
-      .map(nameUrl -> new Webhook(analysis.getProjectUuid(), analysis.getCeTaskUuid(), analysis.getAnalysisUuid(), nameUrl.getName(), nameUrl.getUrl()))
+  public void sendProjectAnalysisUpdate(ComponentDto componentDto, Analysis analysis, Supplier<WebhookPayload> payloadSupplier) {
+    List<Webhook> webhooks = readWebHooksFrom(componentDto)
+      .map(dto -> new Webhook(analysis.getProjectUuid(), analysis.getCeTaskUuid(), analysis.getAnalysisUuid(), dto.getName(), dto.getUrl()))
       .collect(MoreCollectors.toList());
     if (webhooks.isEmpty()) {
       return;
@@ -99,6 +89,15 @@ public class WebHooksImpl implements WebHooks {
     asyncExecution.addToQueue(() -> deliveryStorage.purge(analysis.getProjectUuid()));
   }
 
+  @Override
+  public void sendProjectAnalysisUpdate(Analysis analysis, Supplier<WebhookPayload> payloadSupplier) {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      Optional<ComponentDto> optionalDto = ofNullable(dbClient.componentDao().selectByUuid(dbSession, analysis.getProjectUuid()).orNull());
+      ComponentDto projectDto = checkStateWithOptional(optionalDto, "the requested project '%s' was not found", analysis.getProjectUuid());
+      sendProjectAnalysisUpdate(projectDto, analysis, payloadSupplier);
+    }
+  }
+
   private static void log(WebhookDelivery delivery) {
     Optional<String> error = delivery.getErrorMessage();
     if (error.isPresent()) {
@@ -110,21 +109,4 @@ public class WebHooksImpl implements WebHooks {
     }
   }
 
-  public static final class NameUrl {
-    private final String name;
-    private final String url;
-
-    private NameUrl(String name, String url) {
-      this.name = name;
-      this.url = url;
-    }
-
-    public String getName() {
-      return name;
-    }
-
-    public String getUrl() {
-      return url;
-    }
-  }
 }
index f3ecf08f1ffd15b42d100272a9ad7695040fa3f3..34d1d76d520b1b2d3ea825e722403108b9e5a6ef 100644 (file)
@@ -30,13 +30,13 @@ 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.exceptions.NotFoundException;
 import org.sonar.server.organization.DefaultOrganizationProvider;
 import org.sonar.server.user.UserSession;
 import org.sonarqube.ws.Webhooks;
 
 import static com.google.common.base.Preconditions.checkState;
 import static java.lang.String.format;
+import static java.util.Optional.ofNullable;
 import static org.apache.commons.lang.StringUtils.isNotBlank;
 import static org.sonar.server.webhook.ws.WebhooksWsParameters.ACTION_CREATE;
 import static org.sonar.server.webhook.ws.WebhooksWsParameters.NAME_PARAM;
@@ -82,7 +82,7 @@ public class CreateAction implements WebhooksWsAction {
     WebService.NewAction action = controller.createAction(ACTION_CREATE)
       .setPost(true)
       .setDescription("Create a Webhook.<br>" +
-        "Requires the global, organization or project permission.")
+        "Requires 'Administer' permission on the specified project, or global 'Administer' permission.")
       .setSince("7.1")
       .setResponseExample(getClass().getResource("example-webhook-create.json"))
       .setHandler(this);
@@ -90,13 +90,15 @@ public class CreateAction implements WebhooksWsAction {
     action.createParam(NAME_PARAM)
       .setRequired(true)
       .setMaximumLength(NAME_PARAM_MAXIMUM_LENGTH)
-      .setDescription("The name of the webhook to create")
+      .setDescription("Name displayed in the administration console of webhooks")
       .setExampleValue(NAME_WEBHOOK_EXAMPLE_001);
 
     action.createParam(URL_PARAM)
       .setRequired(true)
       .setMaximumLength(URL_PARAM_MAXIMUM_LENGTH)
-      .setDescription("The url to be called by the webhook")
+      .setDescription("Server endpoint that will receive the webhook payload, for example 'http://my_server/foo'." +
+        " If HTTP Basic authentication is used, HTTPS is recommended to avoid man in the middle attacks." +
+        " Example: 'https://myLogin:myPassword@my_server/foo'")
       .setExampleValue(URL_WEBHOOK_EXAMPLE_001);
 
     action.createParam(PROJECT_KEY_PARAM)
@@ -136,13 +138,13 @@ public class CreateAction implements WebhooksWsAction {
 
       ComponentDto projectDto = null;
       if (isNotBlank(projectKey)) {
-        Optional<ComponentDto> dtoOptional = Optional.ofNullable(dbClient.componentDao().selectByKey(dbSession, projectKey).orNull());
+        Optional<ComponentDto> dtoOptional = 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);
-        webhookSupport.checkUserPermissionOn(componentDto);
+        webhookSupport.checkThatProjectBelongsToOrganization(componentDto, organizationDto, "Project '%s' does not belong to organisation '%s'", projectKey, organizationKey);
+        webhookSupport.checkPermission(componentDto);
         projectDto = componentDto;
       } else {
-        webhookSupport.checkUserPermissionOn(organizationDto);
+        webhookSupport.checkPermission(organizationDto);
       }
 
       webhookSupport.checkUrlPattern(url, "Url parameter with value '%s' is not a valid url", url);
@@ -195,18 +197,12 @@ public class CreateAction implements WebhooksWsAction {
     }
   }
 
-  private int numberOfWebhookOf(DbSession dbSession, OrganizationDto organization) {
-    return dbClient.webhookDao().selectByOrganizationUuid(dbSession, organization.getUuid()).size();
+  private int numberOfWebhookOf(DbSession dbSession, OrganizationDto organizationDto) {
+    return dbClient.webhookDao().selectByOrganizationUuid(dbSession, organizationDto).size();
   }
 
-  private int numberOfWebhookOf(DbSession dbSession, ComponentDto project) {
-    return dbClient.webhookDao().selectByProjectUuid(dbSession, project.uuid()).size();
-  }
-
-  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 int numberOfWebhookOf(DbSession dbSession, ComponentDto componentDto) {
+    return dbClient.webhookDao().selectByProjectUuid(dbSession, componentDto).size();
   }
 
   private OrganizationDto defaultOrganizationDto(DbSession dbSession) {
index 3888bb5747fe9d7b489ef2ae0e07490e1d1e588b..90dcc5e5de4e1b535e679e2dfd6865e9790e2b3f 100644 (file)
@@ -56,14 +56,15 @@ public class DeleteAction implements WebhooksWsAction {
     WebService.NewAction action = controller.createAction(DELETE_ACTION)
       .setPost(true)
       .setDescription("Delete a Webhook.<br>" +
-        "Requires the global, organization or project permission.")
+        "Requires 'Administer' permission on the specified project, or global 'Administer' 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 deleted")
+      .setDescription("The key of the webhook to be deleted,"+
+        "auto-generated value can be obtained through api/webhooks/create or api/webhooks/list")
       .setExampleValue(KEY_PROJECT_EXAMPLE_001);
 
   }
@@ -84,7 +85,7 @@ public class DeleteAction implements WebhooksWsAction {
       if (organizationUuid != null) {
         Optional<OrganizationDto> optionalDto = dbClient.organizationDao().selectByUuid(dbSession, organizationUuid);
         OrganizationDto organizationDto = checkStateWithOptional(optionalDto, "the requested organization '%s' was not found", organizationUuid);
-        webhookSupport.checkUserPermissionOn(organizationDto);
+        webhookSupport.checkPermission(organizationDto);
         deleteWebhook(dbSession, webhookDto);
       }
 
@@ -92,7 +93,7 @@ public class DeleteAction implements WebhooksWsAction {
       if (projectUuid != null) {
         Optional<ComponentDto> optionalDto = ofNullable(dbClient.componentDao().selectByUuid(dbSession, projectUuid).orNull());
         ComponentDto componentDto = checkStateWithOptional(optionalDto, "the requested project '%s' was not found", projectUuid);
-        webhookSupport.checkUserPermissionOn(componentDto);
+        webhookSupport.checkPermission(componentDto);
         deleteWebhook(dbSession, webhookDto);
       }
 
diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/ListAction.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/ListAction.java
new file mode 100644 (file)
index 0000000..70086aa
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * 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 com.google.common.io.Resources;
+import java.util.List;
+import java.util.Optional;
+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.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.organization.DefaultOrganizationProvider;
+import org.sonar.server.user.UserSession;
+import org.sonarqube.ws.Webhooks.SearchWsResponse.Builder;
+
+import static java.util.Optional.ofNullable;
+import static org.apache.commons.lang.StringUtils.isNotBlank;
+import static org.sonar.server.webhook.ws.WebhooksWsParameters.LIST_ACTION;
+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.ws.KeyExamples.KEY_ORG_EXAMPLE_001;
+import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_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.SearchWsResponse.newBuilder;
+
+public class ListAction implements WebhooksWsAction {
+
+  private final DbClient dbClient;
+  private final UserSession userSession;
+  private final DefaultOrganizationProvider defaultOrganizationProvider;
+  private final WebhookSupport webhookSupport;
+
+  public ListAction(DbClient dbClient, UserSession userSession, DefaultOrganizationProvider defaultOrganizationProvider, WebhookSupport webhookSupport) {
+    this.dbClient = dbClient;
+    this.userSession = userSession;
+    this.defaultOrganizationProvider = defaultOrganizationProvider;
+    this.webhookSupport = webhookSupport;
+  }
+
+  @Override
+  public void define(WebService.NewController controller) {
+
+    WebService.NewAction action = controller.createAction(LIST_ACTION)
+      .setDescription("Search for global webhooks or project webhooks. Webhooks are ordered by name.<br>" +
+        "Requires 'Administer' permission on the specified project, or global 'Administer' permission.")
+      .setSince("7.1")
+      .setResponseExample(Resources.getResource(this.getClass(), "example-webhooks-search.json"))
+      .setHandler(this);
+
+    action.createParam(ORGANIZATION_KEY_PARAM)
+      .setDescription("Organization key. If no organization is provided, the default organization is used.")
+      .setInternal(true)
+      .setRequired(false)
+      .setExampleValue(KEY_ORG_EXAMPLE_001);
+
+    action.createParam(PROJECT_KEY_PARAM)
+      .setDescription("Project key")
+      .setRequired(false)
+      .setExampleValue(KEY_PROJECT_EXAMPLE_001);
+
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+
+    String projectKey = request.param(PROJECT_KEY_PARAM);
+    String organizationKey = request.param(ORGANIZATION_KEY_PARAM);
+
+    userSession.checkLoggedIn();
+
+    writeResponse(request, response, doHandle(organizationKey, projectKey));
+
+  }
+
+  private List<WebhookDto> doHandle(@Nullable String organizationKey, @Nullable String projectKey) {
+
+    try (DbSession dbSession = dbClient.openSession(true)) {
+
+      OrganizationDto organizationDto;
+      if (isNotBlank(organizationKey)) {
+        Optional<OrganizationDto> dtoOptional = dbClient.organizationDao().selectByKey(dbSession, organizationKey);
+        organizationDto = checkFoundWithOptional(dtoOptional, "No organization with key '%s'", organizationKey);
+      } else {
+        organizationDto = defaultOrganizationDto(dbSession);
+      }
+
+      if (isNotBlank(projectKey)) {
+
+        Optional<ComponentDto> optional = ofNullable(dbClient.componentDao().selectByKey(dbSession, projectKey).orNull());
+        ComponentDto componentDto = checkFoundWithOptional(optional, "project %s does not exist", projectKey);
+        webhookSupport.checkPermission(componentDto);
+        webhookSupport.checkThatProjectBelongsToOrganization(componentDto, organizationDto, "Project '%s' does not belong to organisation '%s'", projectKey, organizationKey);
+        webhookSupport.checkPermission(componentDto);
+        return dbClient.webhookDao().selectByProjectUuid(dbSession, componentDto);
+
+      } else {
+
+        webhookSupport.checkPermission(organizationDto);
+        return dbClient.webhookDao().selectByOrganizationUuid(dbSession, organizationDto);
+
+      }
+
+    }
+  }
+
+  private static void writeResponse(Request request, Response response, List<WebhookDto> webhookDtos) {
+
+    Builder responseBuilder = newBuilder();
+
+    webhookDtos
+      .stream()
+      .forEach(webhook -> responseBuilder.addWebhooksBuilder()
+        .setKey(webhook.getUuid())
+        .setName(webhook.getName())
+        .setUrl(webhook.getUrl()));
+
+    writeProtobuf(responseBuilder.build(), request, response);
+  }
+
+  private OrganizationDto defaultOrganizationDto(DbSession dbSession) {
+    String uuid = defaultOrganizationProvider.get().getUuid();
+    Optional<OrganizationDto> organizationDto = dbClient.organizationDao().selectByUuid(dbSession, uuid);
+    return checkStateWithOptional(organizationDto, "the default organization '%s' was not found", uuid);
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/SearchAction.java
deleted file mode 100644 (file)
index 93e0c23..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * 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 com.google.common.base.Optional;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.io.Resources;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-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.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.server.setting.ws.Setting;
-import org.sonar.server.setting.ws.SettingsFinder;
-import org.sonar.server.user.UserSession;
-import org.sonarqube.ws.Webhooks.SearchWsResponse.Builder;
-
-import static org.apache.commons.lang.StringUtils.isNotBlank;
-import static org.sonar.api.web.UserRole.ADMIN;
-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.SEARCH_ACTION;
-import static org.sonar.server.ws.KeyExamples.KEY_ORG_EXAMPLE_001;
-import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
-import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
-import static org.sonar.server.ws.WsUtils.writeProtobuf;
-import static org.sonarqube.ws.Webhooks.SearchWsResponse.newBuilder;
-
-public class SearchAction implements WebhooksWsAction {
-
-  private final DbClient dbClient;
-  private final UserSession userSession;
-  private final SettingsFinder settingsFinder;
-
-  public SearchAction(DbClient dbClient, UserSession userSession, SettingsFinder settingsFinder) {
-    this.dbClient = dbClient;
-    this.userSession = userSession;
-    this.settingsFinder = settingsFinder;
-  }
-
-  @Override
-  public void define(WebService.NewController controller) {
-
-    WebService.NewAction action = controller.createAction(SEARCH_ACTION)
-      .setDescription("Search for global or project webhooks")
-      .setSince("7.1")
-      .setResponseExample(Resources.getResource(this.getClass(), "example-webhooks-search.json"))
-      .setHandler(this);
-
-    action.createParam(ORGANIZATION_KEY_PARAM)
-      .setDescription("Organization key. If no organization is provided, the default organization is used.")
-      .setInternal(true)
-      .setRequired(false)
-      .setExampleValue(KEY_ORG_EXAMPLE_001);
-
-    action.createParam(PROJECT_KEY_PARAM)
-      .setDescription("Project key")
-      .setRequired(false)
-      .setExampleValue(KEY_PROJECT_EXAMPLE_001);
-
-  }
-
-  @Override
-  public void handle(Request request, Response response) throws Exception {
-
-    String projectKey = request.param(PROJECT_KEY_PARAM);
-
-    userSession.checkLoggedIn();
-
-    writeResponse(request, response, doHandle(projectKey));
-
-  }
-
-  private List<Setting> doHandle(@Nullable String projectKey) {
-
-    try (DbSession dbSession = dbClient.openSession(true)) {
-
-      if (isNotBlank(projectKey)) {
-        Optional<ComponentDto> component = dbClient.componentDao().selectByKey(dbSession, projectKey);
-        checkFoundWithOptional(component, "project %s does not exist", projectKey);
-        userSession.checkComponentPermission(ADMIN, component.get());
-        return new ArrayList<>(settingsFinder.loadComponentSettings(dbSession,
-          ImmutableSet.of("sonar.webhooks.project"), component.get()).get(component.get().uuid()));
-      } else {
-        userSession.checkIsSystemAdministrator();
-        return settingsFinder.loadGlobalSettings(dbSession, ImmutableSet.of("sonar.webhooks.global"));
-      }
-    }
-  }
-
-  private static void writeResponse(Request request, Response response, List<Setting> settings) {
-
-    Builder responseBuilder = newBuilder();
-
-    settings
-      .stream()
-      .map(Setting::getPropertySets)
-      .flatMap(Collection::stream)
-      .forEach(map -> responseBuilder.addWebhooksBuilder()
-        .setKey("")
-        .setName(map.get("name"))
-        .setUrl(map.get("url")));
-
-    writeProtobuf(responseBuilder.build(), request, response);
-  }
-
-}
index e9b3a819771406937f1590529702b1161053e7ea..79e552d4561bd64b73c24d8717e41138c5044170 100644 (file)
@@ -62,14 +62,15 @@ public class UpdateAction implements WebhooksWsAction {
     WebService.NewAction action = controller.createAction(UPDATE_ACTION)
       .setPost(true)
       .setDescription("Update a Webhook.<br>" +
-        "Requires the global, organization or project permission.")
+        "Requires 'Administer' permission on the specified project, or global 'Administer' 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")
+      .setDescription("The key of the webhook to be updated,"+
+        "auto-generated value can be obtained through api/webhooks/create or api/webhooks/list")
       .setExampleValue(KEY_PROJECT_EXAMPLE_001);
 
     action.createParam(NAME_PARAM)
@@ -106,7 +107,7 @@ public class UpdateAction implements WebhooksWsAction {
       if (organizationUuid != null) {
         Optional<OrganizationDto> optionalDto = dbClient.organizationDao().selectByUuid(dbSession, organizationUuid);
         OrganizationDto organizationDto = checkStateWithOptional(optionalDto, "the requested organization '%s' was not found", organizationUuid);
-        webhookSupport.checkUserPermissionOn(organizationDto);
+        webhookSupport.checkPermission(organizationDto);
         updateWebhook(dbSession, webhookDto, name, url);
       }
 
@@ -114,7 +115,7 @@ public class UpdateAction implements WebhooksWsAction {
       if (projectUuid != null) {
         Optional<ComponentDto> optionalDto = ofNullable(dbClient.componentDao().selectByUuid(dbSession, projectUuid).orNull());
         ComponentDto componentDto = checkStateWithOptional(optionalDto, "the requested project '%s' was not found", projectUuid);
-        webhookSupport.checkUserPermissionOn(componentDto);
+        webhookSupport.checkPermission(componentDto);
         updateWebhook(dbSession, webhookDto, name, url);
       }
 
index 2d6257069d4342f8349198c80b47a7c6cb048850..2595850b84083261e4b157b756f7273bc6012766 100644 (file)
@@ -21,10 +21,10 @@ package org.sonar.server.webhook.ws;
 
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.organization.OrganizationDto;
+import org.sonar.server.exceptions.NotFoundException;
 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;
 
@@ -32,25 +32,27 @@ public class WebhookSupport {
 
   private final UserSession userSession;
 
-  public WebhookSupport(UserSession userSession) {
+  WebhookSupport(UserSession userSession) {
     this.userSession = userSession;
   }
 
-  void checkUserPermissionOn(ComponentDto componentDto) {
+  void checkPermission(ComponentDto componentDto) {
     userSession.checkComponentPermission(ADMIN, componentDto);
   }
 
-  void checkUserPermissionOn(OrganizationDto organizationDto) {
+  void checkPermission(OrganizationDto organizationDto) {
     userSession.checkPermission(ADMINISTER, organizationDto);
   }
 
   void checkUrlPattern(String url, String message, Object... messageArguments) {
-    if (!url.toLowerCase(ENGLISH).startsWith("http://") && !url.toLowerCase(ENGLISH).startsWith("https://")) {
+    if (okhttp3.HttpUrl.parse(url) == null) {
       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));
+  }
+
+  void checkThatProjectBelongsToOrganization(ComponentDto componentDto, OrganizationDto organizationDto, String message, Object... messageArguments) {
+    if (!organizationDto.getUuid().equals(componentDto.getOrganizationUuid())) {
+      throw new NotFoundException(format(message, messageArguments));
     }
   }
 
index 12625ea3634b460503c6f53c84dc06cec707f3d7..1f323bb7a0aea086b1a8ac7887d9c1402d0854ae 100644 (file)
@@ -27,7 +27,7 @@ public class WebhooksWsModule extends Module {
     add(
       WebhookSupport.class,
       WebhooksWs.class,
-      SearchAction.class,
+      ListAction.class,
       CreateAction.class,
       UpdateAction.class,
       DeleteAction.class,
index bf7c97c2ca680c15ec725db2548319d8259bae2f..e35af7703e827602996ed46429edb2cf1b01b187 100644 (file)
@@ -24,7 +24,7 @@ class WebhooksWsParameters {
   static final String WEBHOOKS_CONTROLLER = "api/webhooks";
 
 
-  static final String SEARCH_ACTION = "search";
+  static final String LIST_ACTION = "list";
   static final String ACTION_CREATE = "create";
   static final String UPDATE_ACTION = "update";
   static final String DELETE_ACTION = "delete";
@@ -38,9 +38,7 @@ class WebhooksWsParameters {
   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;
-  static final String KEY_PARAM = "key";
+  static final String KEY_PARAM = "webhook";
   static final int KEY_PARAM_MAXIMUN_LENGTH = 40;
 
   private WebhooksWsParameters() {
index 959ec946aa01e152cfb75c831d5e3fa420bcb9de..2afa9edbc149a94e8b89c0c2faa0d3195a95bbcc 100644 (file)
@@ -31,7 +31,7 @@ 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 NAME_WEBHOOK_EXAMPLE_001 = "My Webhook";
   public static final String URL_WEBHOOK_EXAMPLE_001 = "https://www.my-webhook-listener.com/sonar";
 
   private KeyExamples() {
index 4ef726a32e0490c2448209a229f7aef275b49975..28bb248cf7c42d026e075cc2208cc4046afbedc2 100644 (file)
@@ -1,7 +1,7 @@
 {
   "webhook": {
     "key": "uuid",
-    "name": "my-webhook",
+    "name": "My webhook",
     "url": "https://www.my-webhook-listener.com/sonar"
   }
 }
\ No newline at end of file
index b688ab19462f64ccb75c9b9e09aa4b5e47909499..6aaee28f3a5d83fb3378a4789bea5cd26d5ce040 100644 (file)
@@ -22,9 +22,17 @@ package org.sonar.server.webhook;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import org.junit.Rule;
 import org.junit.Test;
-import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.utils.System2;
+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.async.AsyncExecution;
+import org.sonar.server.organization.DefaultOrganizationProvider;
 
 import static java.util.Objects.requireNonNull;
 import static org.assertj.core.api.Assertions.assertThat;
@@ -33,30 +41,43 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.sonar.db.DbTester.create;
+import static org.sonar.db.webhook.WebhookTesting.newWebhook;
+import static org.sonar.server.organization.TestDefaultOrganizationProvider.from;
 
 public class AsynchronousWebHooksImplTest {
+
+  private System2 system2 = mock(System2.class);
+
+  @Rule
+  public DbTester db = create(system2);
+  private WebhookDbTester webhookDbTester = db.webhooks();
+  private ComponentDbTester componentDbTester = db.components();
+  private OrganizationDbTester organizationDbTester = db.organizations();
+  private DefaultOrganizationProvider defaultOrganizationProvider = from(db);
+
   private static final long NOW = 1_500_000_000_000L;
-  private static final String PROJECT_UUID = "P1_UUID";
 
-  private final MapSettings settings = new MapSettings();
+
   private final TestWebhookCaller caller = new TestWebhookCaller();
   private final WebhookDeliveryStorage deliveryStorage = mock(WebhookDeliveryStorage.class);
   private final WebhookPayload mock = mock(WebhookPayload.class);
   private final RecordingAsyncExecution asyncExecution = new RecordingAsyncExecution();
 
-  private final WebHooksImpl underTest = new WebHooksImpl(caller, deliveryStorage, asyncExecution);
+  private final WebHooksImpl underTest = new WebHooksImpl(caller, deliveryStorage, asyncExecution, db.getDbClient());
 
   @Test
   public void send_global_webhooks() {
-    settings.setProperty("sonar.webhooks.global", "1,2");
-    settings.setProperty("sonar.webhooks.global.1.name", "First");
-    settings.setProperty("sonar.webhooks.global.1.url", "http://url1");
-    settings.setProperty("sonar.webhooks.global.2.name", "Second");
-    settings.setProperty("sonar.webhooks.global.2.url", "http://url2");
+
+    OrganizationDto organizationDto = db.getDefaultOrganization() ;
+    ComponentDto project = componentDbTester.insertPrivateProject(componentDto -> componentDto.setOrganizationUuid(organizationDto.getUuid()));
+    webhookDbTester.insert(newWebhook(organizationDto).setName("First").setUrl("http://url1"));
+    webhookDbTester.insert(newWebhook(organizationDto).setName("Second").setUrl("http://url2"));
+
     caller.enqueueSuccess(NOW, 200, 1_234);
     caller.enqueueFailure(NOW, new IOException("Fail to connect"));
 
-    underTest.sendProjectAnalysisUpdate(settings.asConfig(), new WebHooks.Analysis(PROJECT_UUID, "1", "#1"), () -> mock);
+    underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(project.uuid(), "1", "#1"), () -> mock);
 
     assertThat(caller.countSent()).isZero();
     verifyZeroInteractions(deliveryStorage);
@@ -65,7 +86,7 @@ public class AsynchronousWebHooksImplTest {
 
     assertThat(caller.countSent()).isEqualTo(2);
     verify(deliveryStorage, times(2)).persist(any(WebhookDelivery.class));
-    verify(deliveryStorage).purge(PROJECT_UUID);
+    verify(deliveryStorage).purge(project.uuid());
   }
 
   private static class RecordingAsyncExecution implements AsyncExecution {
index 19c317cc9d862f4284a1fb3e521895dcd46dbcce..38a835f61f9a53e58385c6a7cde8c741ee46b1bd 100644 (file)
 package org.sonar.server.webhook;
 
 import java.io.IOException;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
 import org.junit.Rule;
 import org.junit.Test;
-import org.sonar.api.config.internal.MapSettings;
 import org.sonar.api.utils.log.LogTester;
-import org.sonar.api.utils.log.LoggerLevel;
+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.webhook.WebhookDbTester;
 import org.sonar.server.async.AsyncExecution;
+import org.sonar.server.organization.DefaultOrganizationProvider;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Matchers.any;
@@ -35,143 +37,102 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.sonar.api.utils.log.LoggerLevel.DEBUG;
+import static org.sonar.db.DbTester.create;
+import static org.sonar.db.webhook.WebhookTesting.newWebhook;
+import static org.sonar.server.organization.TestDefaultOrganizationProvider.from;
 
 public class SynchronousWebHooksImplTest {
 
   private static final long NOW = 1_500_000_000_000L;
-  private static final String PROJECT_UUID = "P1_UUID";
 
   @Rule
   public LogTester logTester = new LogTester();
 
-  private final MapSettings settings = new MapSettings();
+  @Rule
+  public DbTester db = create();
+  private DbClient dbClient = db.getDbClient();
+
+  private WebhookDbTester webhookDbTester = db.webhooks();
+  private ComponentDbTester componentDbTester = db.components();
+  private DefaultOrganizationProvider defaultOrganizationProvider = from(db);
+
   private final TestWebhookCaller caller = new TestWebhookCaller();
   private final WebhookDeliveryStorage deliveryStorage = mock(WebhookDeliveryStorage.class);
   private final WebhookPayload mock = mock(WebhookPayload.class);
   private final AsyncExecution synchronousAsyncExecution = Runnable::run;
-  private final WebHooksImpl underTest = new WebHooksImpl(caller, deliveryStorage, synchronousAsyncExecution);
-
-  @Test
-  public void isEnabled_returns_false_if_no_webHoolds() {
-    assertThat(underTest.isEnabled(settings.asConfig())).isFalse();
-  }
-
-  @Test
-  public void isEnabled_returns_true_if_one_valid_global_webhook() {
-    settings.setProperty("sonar.webhooks.global", "1");
-    settings.setProperty("sonar.webhooks.global.1.name", "First");
-    settings.setProperty("sonar.webhooks.global.1.url", "http://url1");
-
-    assertThat(underTest.isEnabled(settings.asConfig())).isTrue();
-  }
+  private final WebHooksImpl underTest = new WebHooksImpl(caller, deliveryStorage, synchronousAsyncExecution, dbClient);
 
   @Test
-  public void isEnabled_returns_false_if_only_one_global_webhook_without_url() {
-    settings.setProperty("sonar.webhooks.global", "1");
-    settings.setProperty("sonar.webhooks.global.1.name", "First");
+  public void isEnabled_returns_false_if_no_webhooks() {
+    ComponentDto componentDto = componentDbTester.insertPrivateProject();
 
-    assertThat(underTest.isEnabled(settings.asConfig())).isFalse();
+    assertThat(underTest.isEnabled(componentDto)).isFalse();
   }
 
   @Test
-  public void isEnabled_returns_false_if_only_one_global_webhook_without_name() {
-    settings.setProperty("sonar.webhooks.global", "1");
-    settings.setProperty("sonar.webhooks.global.1.url", "http://url1");
+  public void isEnabled_returns_true_if_one_valid_global_webhook() {
+    ComponentDto componentDto = componentDbTester.insertPrivateProject();
+    webhookDbTester.insert(newWebhook(componentDto).setName("First").setUrl("http://url1"));
 
-    assertThat(underTest.isEnabled(settings.asConfig())).isFalse();
+    assertThat(underTest.isEnabled(componentDto)).isTrue();
   }
 
   @Test
   public void isEnabled_returns_true_if_one_valid_project_webhook() {
-    settings.setProperty("sonar.webhooks.project", "1");
-    settings.setProperty("sonar.webhooks.project.1.name", "First");
-    settings.setProperty("sonar.webhooks.project.1.url", "http://url1");
-
-    assertThat(underTest.isEnabled(settings.asConfig())).isTrue();
-  }
-
-  @Test
-  public void isEnabled_returns_false_if_only_one_project_webhook_without_url() {
-    settings.setProperty("sonar.webhooks.project", "1");
-    settings.setProperty("sonar.webhooks.project.1.name", "First");
+    String organizationUuid = defaultOrganizationProvider.get().getUuid();
+    ComponentDto componentDto = componentDbTester.insertPrivateProject().setOrganizationUuid(organizationUuid);
+    webhookDbTester.insert(newWebhook(componentDto).setName("First").setUrl("http://url1"));
 
-    assertThat(underTest.isEnabled(settings.asConfig())).isFalse();
+    assertThat(underTest.isEnabled(componentDto)).isTrue();
   }
 
-  @Test
-  public void isEnabled_returns_false_if_only_one_project_webhook_without_name() {
-    settings.setProperty("sonar.webhooks.project", "1");
-    settings.setProperty("sonar.webhooks.project.1.url", "http://url1");
-
-    assertThat(underTest.isEnabled(settings.asConfig())).isFalse();
-  }
 
   @Test
   public void do_nothing_if_no_webhooks() {
-    underTest.sendProjectAnalysisUpdate(settings.asConfig(), new WebHooks.Analysis(PROJECT_UUID, "1", "#1"), () -> mock);
+    ComponentDto componentDto = componentDbTester.insertPrivateProject().setOrganizationUuid(defaultOrganizationProvider.get().getUuid());
+
+    underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(componentDto.uuid(), "1", "#1"), () -> mock);
 
     assertThat(caller.countSent()).isEqualTo(0);
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).isEmpty();
+    assertThat(logTester.logs(DEBUG)).isEmpty();
     verifyZeroInteractions(deliveryStorage);
   }
 
   @Test
   public void send_global_webhooks() {
-    settings.setProperty("sonar.webhooks.global", "1,2");
-    settings.setProperty("sonar.webhooks.global.1.name", "First");
-    settings.setProperty("sonar.webhooks.global.1.url", "http://url1");
-    settings.setProperty("sonar.webhooks.global.2.name", "Second");
-    settings.setProperty("sonar.webhooks.global.2.url", "http://url2");
+
+    ComponentDto componentDto = componentDbTester.insertPrivateProject();
+    webhookDbTester.insert(newWebhook(componentDto).setName("First").setUrl("http://url1"));
+    webhookDbTester.insert(newWebhook(componentDto).setName("Second").setUrl("http://url2"));
     caller.enqueueSuccess(NOW, 200, 1_234);
     caller.enqueueFailure(NOW, new IOException("Fail to connect"));
 
-    underTest.sendProjectAnalysisUpdate(settings.asConfig(), new WebHooks.Analysis(PROJECT_UUID, "1", "#1"), () -> mock);
+    underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(componentDto.uuid(), "1", "#1"), () -> mock);
 
     assertThat(caller.countSent()).isEqualTo(2);
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("Sent webhook 'First' | url=http://url1 | time=1234ms | status=200");
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("Failed to send webhook 'Second' | url=http://url2 | message=Fail to connect");
+    assertThat(logTester.logs(DEBUG)).contains("Sent webhook 'First' | url=http://url1 | time=1234ms | status=200");
+    assertThat(logTester.logs(DEBUG)).contains("Failed to send webhook 'Second' | url=http://url2 | message=Fail to connect");
     verify(deliveryStorage, times(2)).persist(any(WebhookDelivery.class));
-    verify(deliveryStorage).purge(PROJECT_UUID);
+    verify(deliveryStorage).purge(componentDto.uuid());
+
   }
 
   @Test
   public void send_project_webhooks() {
-    settings.setProperty("sonar.webhooks.project", "1");
-    settings.setProperty("sonar.webhooks.project.1.name", "First");
-    settings.setProperty("sonar.webhooks.project.1.url", "http://url1");
+
+    String organizationUuid = defaultOrganizationProvider.get().getUuid();
+    ComponentDto componentDto = componentDbTester.insertPrivateProject().setOrganizationUuid(organizationUuid);
+    webhookDbTester.insert(newWebhook(componentDto).setName("First").setUrl("http://url1"));
     caller.enqueueSuccess(NOW, 200, 1_234);
 
-    underTest.sendProjectAnalysisUpdate(settings.asConfig(), new WebHooks.Analysis(PROJECT_UUID, "1", "#1"), () -> mock);
+    underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(componentDto.uuid(), "1", "#1"), () -> mock);
 
     assertThat(caller.countSent()).isEqualTo(1);
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("Sent webhook 'First' | url=http://url1 | time=1234ms | status=200");
+    assertThat(logTester.logs(DEBUG)).contains("Sent webhook 'First' | url=http://url1 | time=1234ms | status=200");
     verify(deliveryStorage).persist(any(WebhookDelivery.class));
-    verify(deliveryStorage).purge(PROJECT_UUID);
-  }
-
-  @Test
-  public void process_only_the_10_first_global_webhooks() {
-    testMaxWebhooks("sonar.webhooks.global");
-  }
-
-  @Test
-  public void process_only_the_10_first_project_webhooks() {
-    testMaxWebhooks("sonar.webhooks.project");
-  }
-
-  private void testMaxWebhooks(String property) {
-    IntStream.range(1, 15)
-      .forEach(i -> {
-        settings.setProperty(property + "." + i + ".name", "First");
-        settings.setProperty(property + "." + i + ".url", "http://url");
-        caller.enqueueSuccess(NOW, 200, 1_234);
-      });
-    settings.setProperty(property, IntStream.range(1, 15).mapToObj(String::valueOf).collect(Collectors.joining(",")));
-
-    underTest.sendProjectAnalysisUpdate(settings.asConfig(), new WebHooks.Analysis(PROJECT_UUID, "1", "#1"), () -> mock);
+    verify(deliveryStorage).purge(componentDto.uuid());
 
-    assertThat(caller.countSent()).isEqualTo(10);
-    assertThat(logTester.logs(LoggerLevel.DEBUG).stream().filter(log -> log.contains("Sent"))).hasSize(10);
   }
 
 }
index fc3f7bcdfb443cc718b3debefade7dfdc4caad1e..45aee878ad95c2bfdae1f21750e27a85071ea4fd 100644 (file)
@@ -221,7 +221,7 @@ public class CreateActionTest {
     expectedException.expectMessage(format("Maximum number of webhook reached for project '%s'", project.getKey()));
 
     for (int i = 0; i < 10; i++) {
-      webhookDbTester.insertWebhookForProjectUuid(project.uuid());
+      webhookDbTester.insertWebhook(project);
     }
     userSession.logIn().addProjectPermission(ADMIN, project);
 
@@ -242,7 +242,7 @@ public class CreateActionTest {
     expectedException.expectMessage(format("Maximum number of webhook reached for organization '%s'", organization.getKey()));
 
     for (int i = 0; i < 10; i++) {
-      webhookDbTester.insertForOrganizationUuid(organization.getUuid());
+      webhookDbTester.insertWebhook(organization);
     }
     userSession.logIn().addPermission(ADMINISTER, organization.getUuid());
 
index f54d55a6bd1d52ba7011a8a14121b5fd27494da1..f182f86511b7420349b2b096be90cf46239f49f5 100644 (file)
@@ -82,7 +82,7 @@ public class DeleteActionTest {
 
     assertThat(action.params())
       .extracting(WebService.Param::key, WebService.Param::isRequired)
-      .containsExactlyInAnyOrder(tuple("key", true));
+      .containsExactlyInAnyOrder(tuple("webhook", true));
 
   }
 
@@ -90,7 +90,7 @@ public class DeleteActionTest {
   public void delete_a_project_webhook() {
 
     ComponentDto project = componentDbTester.insertPrivateProject();
-    WebhookDto dto = webhookDbTester.insertWebhookForProjectUuid(project.uuid());
+    WebhookDto dto = webhookDbTester.insertWebhook(project);
     userSession.logIn().addProjectPermission(ADMIN, project);
 
     TestResponse response = wsActionTester.newRequest()
@@ -107,7 +107,7 @@ public class DeleteActionTest {
   public void delete_an_organization_webhook() {
 
     OrganizationDto organization = organizationDbTester.insert();
-    WebhookDto dto = webhookDbTester.insertForOrganizationUuid(organization.getUuid());
+    WebhookDto dto = webhookDbTester.insertWebhook(organization);
     userSession.logIn().addPermission(ADMINISTER, organization.getUuid());
 
     TestResponse response = wsActionTester.newRequest()
@@ -137,7 +137,7 @@ public class DeleteActionTest {
   public void fail_if_not_logged_in() throws Exception {
 
     OrganizationDto organization = organizationDbTester.insert();
-    WebhookDto dto = webhookDbTester.insertForOrganizationUuid(organization.getUuid());
+    WebhookDto dto = webhookDbTester.insertWebhook(organization);
     userSession.anonymous();
 
     expectedException.expect(UnauthorizedException.class);
@@ -152,7 +152,7 @@ public class DeleteActionTest {
   public void fail_if_no_permission_on_webhook_scope_project() {
 
     ComponentDto project = componentDbTester.insertPrivateProject();
-    WebhookDto dto = webhookDbTester.insertWebhookForProjectUuid(project.uuid());
+    WebhookDto dto = webhookDbTester.insertWebhook(project);
 
     userSession.logIn();
 
@@ -169,7 +169,7 @@ public class DeleteActionTest {
   public void fail_if_no_permission_on_webhook_scope_organization() {
 
     OrganizationDto organization = organizationDbTester.insert();
-    WebhookDto dto = webhookDbTester.insertForOrganizationUuid(organization.getUuid());
+    WebhookDto dto = webhookDbTester.insertWebhook(organization);
 
     userSession.logIn();
 
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/ListActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/ListActionTest.java
new file mode 100644 (file)
index 0000000..30679d2
--- /dev/null
@@ -0,0 +1,253 @@
+/*
+ * 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.api.server.ws.WebService.Param;
+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.WsActionTester;
+import org.sonarqube.ws.Webhooks.SearchWsResponse;
+import org.sonarqube.ws.Webhooks.SearchWsResponse.Search;
+
+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.ORGANIZATION_KEY_PARAM;
+import static org.sonar.server.webhook.ws.WebhooksWsParameters.PROJECT_KEY_PARAM;
+
+public class ListActionTest {
+
+  @Rule
+  public ExpectedException expectedException = none();
+
+  @Rule
+  public UserSessionRule userSession = standalone();
+
+  @Rule
+  public DbTester db = create();
+
+  private DbClient dbClient = db.getDbClient();
+  private DefaultOrganizationProvider defaultOrganizationProvider = from(db);
+  private WebhookSupport webhookSupport = new WebhookSupport(userSession);
+  private ListAction underTest = new ListAction(dbClient, userSession, defaultOrganizationProvider, webhookSupport);
+
+  private ComponentDbTester componentDbTester = db.components();
+  private WebhookDbTester webhookDbTester = db.webhooks();
+  private OrganizationDbTester organizationDbTester = db.organizations();
+  private WsActionTester wsActionTester = new WsActionTester(underTest);
+
+  @Test
+  public void definition() {
+
+    WebService.Action action = wsActionTester.getDef();
+
+    assertThat(action).isNotNull();
+    assertThat(action.isInternal()).isFalse();
+    assertThat(action.isPost()).isFalse();
+    assertThat(action.responseExampleAsString()).isNotEmpty();
+    assertThat(action.params())
+      .extracting(Param::key, Param::isRequired)
+      .containsExactlyInAnyOrder(
+        tuple("organization", false),
+        tuple("project", false));
+
+  }
+
+  @Test
+  public void search_global_webhooks() {
+
+    WebhookDto dto1 = webhookDbTester.insertWebhook(db.getDefaultOrganization());
+    WebhookDto dto2 = webhookDbTester.insertWebhook(db.getDefaultOrganization());
+    userSession.logIn().addPermission(ADMINISTER, db.getDefaultOrganization().getUuid());
+
+    SearchWsResponse response = wsActionTester.newRequest()
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(response.getWebhooksList())
+      .extracting(Search::getName, Search::getUrl)
+      .contains(tuple(dto1.getName(), dto1.getUrl()),
+        tuple(dto2.getName(), dto2.getUrl()));
+
+  }
+
+  @Test
+  public void search_project_webhooks_when_no_organization_is_provided() {
+
+    ComponentDto project1 = componentDbTester.insertPrivateProject();
+    userSession.logIn().addProjectPermission(ADMIN, project1);
+
+    WebhookDto dto1 = webhookDbTester.insertWebhook(project1);
+    WebhookDto dto2 = webhookDbTester.insertWebhook(project1);
+
+    SearchWsResponse response = wsActionTester.newRequest()
+      .setParam(PROJECT_KEY_PARAM, project1.getKey())
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(response.getWebhooksList())
+      .extracting(Search::getName, Search::getUrl)
+      .contains(tuple(dto1.getName(), dto1.getUrl()),
+        tuple(dto2.getName(), dto2.getUrl()));
+
+  }
+
+  @Test
+  public void search_organization_webhooks() {
+
+    OrganizationDto organizationDto = organizationDbTester.insert();
+    WebhookDto dto1 = webhookDbTester.insertWebhook(organizationDto);
+    WebhookDto dto2 = webhookDbTester.insertWebhook(organizationDto);
+    userSession.logIn().addPermission(ADMINISTER, organizationDto.getUuid());
+
+    SearchWsResponse response = wsActionTester.newRequest()
+      .setParam(ORGANIZATION_KEY_PARAM, organizationDto.getKey())
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(response.getWebhooksList())
+      .extracting(Search::getName, Search::getUrl)
+      .contains(tuple(dto1.getName(), dto1.getUrl()),
+        tuple(dto2.getName(), dto2.getUrl()));
+
+  }
+
+  @Test
+  public void search_project_webhooks_when_organization_is_provided() {
+
+    OrganizationDto organization = organizationDbTester.insert();
+    ComponentDto project = componentDbTester.insertPrivateProject(organization);
+    userSession.logIn().addProjectPermission(ADMIN, project);
+
+    WebhookDto dto1 = webhookDbTester.insertWebhook(project);
+    WebhookDto dto2 = webhookDbTester.insertWebhook(project);
+
+    SearchWsResponse response = wsActionTester.newRequest()
+      .setParam(ORGANIZATION_KEY_PARAM, organization.getKey())
+      .setParam(PROJECT_KEY_PARAM, project.getKey())
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(response.getWebhooksList())
+      .extracting(Search::getName, Search::getUrl)
+      .contains(tuple(dto1.getName(), dto1.getUrl()),
+        tuple(dto2.getName(), dto2.getUrl()));
+
+  }
+
+  @Test
+  public void return_NotFoundException_if_requested_project_is_not_found() throws Exception {
+
+    userSession.logIn().setSystemAdministrator();
+    expectedException.expect(NotFoundException.class);
+
+    wsActionTester.newRequest()
+      .setParam(PROJECT_KEY_PARAM, "pipo")
+      .executeProtobuf(SearchWsResponse.class);
+
+  }
+
+  @Test
+  public void return_NotFoundException_if_requested_organization_is_not_found() throws Exception {
+
+    userSession.logIn().setSystemAdministrator();
+    expectedException.expect(NotFoundException.class);
+
+    wsActionTester.newRequest()
+      .setParam(ORGANIZATION_KEY_PARAM, "pipo")
+      .executeProtobuf(SearchWsResponse.class);
+
+  }
+
+  @Test
+  public void fail_if_project_exists_but_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())
+      .execute();
+
+  }
+
+  @Test
+  public void return_UnauthorizedException_if_not_logged_in() throws Exception {
+
+    userSession.anonymous();
+    expectedException.expect(UnauthorizedException.class);
+
+    wsActionTester.newRequest()
+      .executeProtobuf(SearchWsResponse.class);
+
+  }
+
+  @Test
+  public void throw_ForbiddenException_if_not_organization_administrator() {
+
+    userSession.logIn();
+
+    expectedException.expect(ForbiddenException.class);
+    expectedException.expectMessage("Insufficient privileges");
+
+    wsActionTester.newRequest()
+      .executeProtobuf(SearchWsResponse.class);
+  }
+
+  @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(PROJECT_KEY_PARAM, project.getKey())
+      .executeProtobuf(SearchWsResponse.class);
+
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/SearchActionTest.java
deleted file mode 100644 (file)
index d2c7606..0000000
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * 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.config.PropertyDefinition;
-import org.sonar.api.config.PropertyDefinitions;
-import org.sonar.api.server.ws.WebService;
-import org.sonar.api.server.ws.WebService.Param;
-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.OrganizationDto;
-import org.sonar.db.property.PropertyDbTester;
-import org.sonar.server.exceptions.ForbiddenException;
-import org.sonar.server.exceptions.NotFoundException;
-import org.sonar.server.exceptions.UnauthorizedException;
-import org.sonar.server.setting.ws.SettingsFinder;
-import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.ws.WsActionTester;
-import org.sonarqube.ws.Webhooks.SearchWsResponse;
-import org.sonarqube.ws.Webhooks.SearchWsResponse.Search;
-
-import static com.google.common.collect.ImmutableMap.of;
-import static java.util.Arrays.asList;
-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.PropertyType.PROPERTY_SET;
-import static org.sonar.api.config.PropertyFieldDefinition.build;
-import static org.sonar.api.web.UserRole.ADMIN;
-import static org.sonar.db.DbTester.create;
-import static org.sonar.server.tester.UserSessionRule.standalone;
-import static org.sonar.server.webhook.ws.WebhooksWsParameters.PROJECT_KEY_PARAM;
-
-public class SearchActionTest {
-
-  @Rule
-  public ExpectedException expectedException = none();
-
-  @Rule
-  public UserSessionRule userSession = standalone();
-
-  @Rule
-  public DbTester db = create();
-
-  private DbClient dbClient = db.getDbClient();
-  private PropertyDefinitions definitions = new PropertyDefinitions();
-  private SettingsFinder settingsFinder = new SettingsFinder(dbClient, definitions);
-  private SearchAction underTest = new SearchAction(dbClient, userSession, settingsFinder);
-  private WsActionTester wsActionTester = new WsActionTester(underTest);
-
-  private ComponentDbTester componentDbTester = new ComponentDbTester(db);
-
-  private PropertyDbTester propertyDb = new PropertyDbTester(db);
-
-  @Test
-  public void definition() {
-
-    WebService.Action action = wsActionTester.getDef();
-
-    assertThat(action).isNotNull();
-    assertThat(action.isInternal()).isFalse();
-    assertThat(action.isPost()).isFalse();
-    assertThat(action.responseExampleAsString()).isNotEmpty();
-    assertThat(action.params())
-      .extracting(Param::key, Param::isRequired)
-      .containsExactlyInAnyOrder(
-        tuple("organization", false),
-        tuple("project", false));
-  }
-
-  @Test
-  public void search_global_webhooks() {
-
-    definitions.addComponent(PropertyDefinition
-      .builder("sonar.webhooks.global")
-      .type(PROPERTY_SET)
-      .fields(asList(
-        build("name").name("name").build(),
-        build("url").name("url").build()))
-      .build());
-    propertyDb.insertPropertySet("sonar.webhooks.global", null,
-      of("name", "my first global webhook", "url", "http://127.0.0.1/first-global"),
-      of("name", "my second global webhook", "url", "http://127.0.0.1/second-global"));
-
-    userSession.logIn().setSystemAdministrator();
-
-    SearchWsResponse response = wsActionTester.newRequest()
-      .executeProtobuf(SearchWsResponse.class);
-
-    assertThat(response.getWebhooksList())
-      .extracting(Search::getName, Search::getUrl)
-      .containsExactly(tuple("my first global webhook", "http://127.0.0.1/first-global"),
-        tuple("my second global webhook", "http://127.0.0.1/second-global"));
-  }
-
-  @Test
-  public void search_project_webhooks_when_no_organization_is_provided() {
-    OrganizationDto defaultOrganization = db.getDefaultOrganization();
-    ComponentDto project = db.components().insertPublicProject(defaultOrganization);
-
-    definitions.addComponent(PropertyDefinition
-      .builder("sonar.webhooks.global")
-      .type(PROPERTY_SET)
-      .fields(asList(
-        build("name").name("name").build(),
-        build("url").name("url").build()))
-      .build());
-    propertyDb.insertPropertySet("sonar.webhooks.global", null,
-      of("name", "my first global webhook", "url", "http://127.0.0.1/first-global"),
-      of("name", "my second global webhook", "url", "http://127.0.0.1/second-global"));
-
-    definitions.addComponent(PropertyDefinition
-      .builder("sonar.webhooks.project")
-      .type(PROPERTY_SET)
-      .fields(asList(
-        build("name").name("name").build(),
-        build("url").name("url").build()))
-      .build());
-    propertyDb.insertPropertySet("sonar.webhooks.project", project,
-      of("name", "my first project webhook", "url", "http://127.0.0.1/first-project"),
-      of("name", "my second project webhook", "url", "http://127.0.0.1/second-project"));
-
-    userSession.logIn().addProjectPermission(ADMIN, project);
-
-    SearchWsResponse response = wsActionTester.newRequest()
-      .setParam(PROJECT_KEY_PARAM, project.getKey())
-      .executeProtobuf(SearchWsResponse.class);
-
-    assertThat(response.getWebhooksList())
-      .extracting(Search::getName, Search::getUrl)
-      .containsExactly(tuple("my first project webhook", "http://127.0.0.1/first-project"),
-        tuple("my second project webhook", "http://127.0.0.1/second-project"));
-
-  }
-
-  @Test
-  public void return_UnauthorizedException_if_not_logged_in() throws Exception {
-
-    userSession.anonymous();
-    expectedException.expect(UnauthorizedException.class);
-
-    wsActionTester.newRequest()
-      .executeProtobuf(SearchWsResponse.class);
-  }
-
-  @Test
-  public void return_NotFoundException_if_not_project_is_not_found() throws Exception {
-
-    userSession.logIn().setSystemAdministrator();
-    expectedException.expect(NotFoundException.class);
-
-    wsActionTester.newRequest()
-      .setParam(PROJECT_KEY_PARAM, "pipo")
-      .executeProtobuf(SearchWsResponse.class);
-  }
-
-  @Test
-  public void throw_ForbiddenException_if_not_organization_administrator() {
-
-    userSession.logIn();
-
-    expectedException.expect(ForbiddenException.class);
-    expectedException.expectMessage("Insufficient privileges");
-
-    wsActionTester.newRequest()
-      .executeProtobuf(SearchWsResponse.class);
-  }
-
-  @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(PROJECT_KEY_PARAM, project.getKey())
-      .executeProtobuf(SearchWsResponse.class);
-
-  }
-
-}
index 0bf1e5ebc298498a6e1431bce975d81cd9bb9b2a..2b07545363375c8c1f3ec93ead0c4f646a438a17 100644 (file)
@@ -87,7 +87,7 @@ public class UpdateActionTest {
     assertThat(action.params())
       .extracting(WebService.Param::key, WebService.Param::isRequired)
       .containsExactlyInAnyOrder(
-        tuple("key", true),
+        tuple("webhook", true),
         tuple("name", true),
         tuple("url", true));
 
@@ -97,7 +97,7 @@ public class UpdateActionTest {
   public void update_a_project_webhook() {
 
     ComponentDto project = componentDbTester.insertPrivateProject();
-    WebhookDto dto = webhookDbTester.insertWebhookForProjectUuid(project.uuid());
+    WebhookDto dto = webhookDbTester.insertWebhook(project);
     userSession.logIn().addProjectPermission(ADMIN, project);
 
     TestResponse response = wsActionTester.newRequest()
@@ -120,7 +120,7 @@ public class UpdateActionTest {
   public void update_an_organization_webhook() {
 
     OrganizationDto organization = organizationDbTester.insert();
-    WebhookDto dto = webhookDbTester.insertForOrganizationUuid(organization.getUuid());
+    WebhookDto dto = webhookDbTester.insertWebhook(organization);
     userSession.logIn().addPermission(ADMINISTER, organization.getUuid());
 
     TestResponse response = wsActionTester.newRequest()
@@ -158,7 +158,7 @@ public class UpdateActionTest {
   public void fail_if_not_logged_in() throws Exception {
 
     OrganizationDto organization = organizationDbTester.insert();
-    WebhookDto dto = webhookDbTester.insertForOrganizationUuid(organization.getUuid());
+    WebhookDto dto = webhookDbTester.insertWebhook(organization);
     userSession.anonymous();
 
     expectedException.expect(UnauthorizedException.class);
@@ -175,7 +175,7 @@ public class UpdateActionTest {
   public void fail_if_no_permission_on_webhook_scope_project() {
 
     ComponentDto project = componentDbTester.insertPrivateProject();
-    WebhookDto dto = webhookDbTester.insertWebhookForProjectUuid(project.uuid());
+    WebhookDto dto = webhookDbTester.insertWebhook(project);
 
     userSession.logIn();
 
@@ -194,7 +194,7 @@ public class UpdateActionTest {
   public void fail_if_no_permission_on_webhook_scope_organization() {
 
     OrganizationDto organization = organizationDbTester.insert();
-    WebhookDto dto = webhookDbTester.insertForOrganizationUuid(organization.getUuid());
+    WebhookDto dto = webhookDbTester.insertWebhook(organization);
 
     userSession.logIn();
 
@@ -213,7 +213,7 @@ public class UpdateActionTest {
   public void fail_if_url_is_not_valid() throws Exception {
 
     ComponentDto project = componentDbTester.insertPrivateProject();
-    WebhookDto dto = webhookDbTester.insertWebhookForProjectUuid(project.uuid());
+    WebhookDto dto = webhookDbTester.insertWebhook(project);
     userSession.logIn().addProjectPermission(ADMIN, project);
 
     expectedException.expect(IllegalArgumentException.class);
@@ -230,7 +230,7 @@ public class UpdateActionTest {
   public void fail_if_credential_in_url_is_have_a_wrong_format() throws Exception {
 
     ComponentDto project = componentDbTester.insertPrivateProject();
-    WebhookDto dto = webhookDbTester.insertWebhookForProjectUuid(project.uuid());
+    WebhookDto dto = webhookDbTester.insertWebhook(project);
     userSession.logIn().addProjectPermission(ADMIN, project);
 
     expectedException.expect(IllegalArgumentException.class);