]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6599 WS api/projects/delete delete one project
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Mon, 1 Jun 2015 09:29:51 +0000 (11:29 +0200)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Mon, 1 Jun 2015 09:33:36 +0000 (11:33 +0200)
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
server/sonar-server/src/main/java/org/sonar/server/project/ws/BulkDeleteAction.java
server/sonar-server/src/main/java/org/sonar/server/project/ws/DeleteAction.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWs.java
server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/project/ws/BulkDeleteActionTest.java
server/sonar-server/src/test/java/org/sonar/server/project/ws/DeleteActionTest.java [new file with mode: 0644]

index ab1d0513eccf1b89124424263b86a2a190fbe36d..f05626b68c4fa701c60f18831be300cf7f88b0d4 100644 (file)
@@ -207,10 +207,7 @@ import org.sonar.server.plugins.ws.PluginWSCommons;
 import org.sonar.server.plugins.ws.PluginsWs;
 import org.sonar.server.plugins.ws.UninstallAction;
 import org.sonar.server.plugins.ws.UpdatesAction;
-import org.sonar.server.project.ws.BulkDeleteAction;
-import org.sonar.server.project.ws.GhostsAction;
-import org.sonar.server.project.ws.ProjectsWs;
-import org.sonar.server.project.ws.ProvisionedAction;
+import org.sonar.server.project.ws.ProjectsWsModule;
 import org.sonar.server.properties.ProjectSettingsFactory;
 import org.sonar.server.qualitygate.QgateProjectFinder;
 import org.sonar.server.qualitygate.QualityGates;
@@ -573,21 +570,18 @@ public class PlatformLevel4 extends PlatformLevel {
       PermissionsWs.class,
 
       // components
+      ProjectsWsModule.class,
       DefaultComponentFinder.class,
       DefaultRubyComponentService.class,
       ComponentService.class,
       ResourcesWs.class,
       ComponentsWs.class,
-      ProjectsWs.class,
-      BulkDeleteAction.class,
       org.sonar.server.component.ws.AppAction.class,
       org.sonar.server.component.ws.SearchAction.class,
       EventsWs.class,
       NewAlerts.class,
       NewAlerts.newMetadata(),
       ComponentCleanerService.class,
-      ProvisionedAction.class,
-      GhostsAction.class,
 
       // views
       ViewIndexDefinition.class,
index 6ebf54a357412879a746879d233f5f192f8c42ef..52293fdc6c856d251df0231e76282d2e86ffca34 100644 (file)
@@ -38,8 +38,8 @@ import java.util.List;
 public class BulkDeleteAction implements ProjectsWsAction {
   private static final String ACTION = "bulk_delete";
 
-  private static final String PARAM_UUIDS = "ids";
-  private static final String PARAM_KEYS = "keys";
+  public static final String PARAM_UUIDS = "ids";
+  public static final String PARAM_KEYS = "keys";
 
   private final ComponentCleanerService componentCleanerService;
   private final DbClient dbClient;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/ws/DeleteAction.java b/server/sonar-server/src/main/java/org/sonar/server/project/ws/DeleteAction.java
new file mode 100644 (file)
index 0000000..4665a53
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.project.ws;
+
+import java.util.Arrays;
+import javax.annotation.Nullable;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.component.ComponentDto;
+import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.MyBatis;
+import org.sonar.server.component.ComponentCleanerService;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.user.UserSession;
+
+public class DeleteAction implements ProjectsWsAction {
+  private static final String ACTION = "delete";
+
+  public static final String PARAM_UUID = "id";
+  public static final String PARAM_KEY = "key";
+
+  private final ComponentCleanerService componentCleanerService;
+  private final DbClient dbClient;
+  private final UserSession userSession;
+
+  public DeleteAction(ComponentCleanerService componentCleanerService, DbClient dbClient, UserSession userSession) {
+    this.componentCleanerService = componentCleanerService;
+    this.dbClient = dbClient;
+    this.userSession = userSession;
+  }
+
+  @Override
+  public void define(WebService.NewController context) {
+    WebService.NewAction action = context
+      .createAction(ACTION)
+      .setPost(true)
+      .setDescription("Delete a project.<br /> Requires 'Administer System' permission or 'Administer' permission on the project.")
+      .setSince("5.2")
+      .setHandler(this);
+
+    action
+      .createParam(PARAM_UUID)
+      .setDescription("Project UUID")
+      .setExampleValue("ce4c03d6-430f-40a9-b777-ad877c00aa4d");
+
+    action
+      .createParam(PARAM_KEY)
+      .setDescription("Project key")
+      .setExampleValue("org.apache.hbas:hbase");
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+    String uuid = request.param(PARAM_UUID);
+    String key = request.param(PARAM_KEY);
+    checkPermissions(uuid, key);
+
+    DbSession dbSession = dbClient.openSession(false);
+    try {
+      ComponentDto project = searchProject(dbSession, uuid, key);
+      componentCleanerService.delete(dbSession, Arrays.asList(project));
+    } finally {
+      MyBatis.closeQuietly(dbSession);
+    }
+
+    response.noContent();
+  }
+
+  private void checkPermissions(@Nullable String uuid, @Nullable String key) {
+    if (missPermissionsBasedOnUuid(uuid) || missPermissionsBasedOnKey(key)) {
+      userSession.checkLoggedIn().checkGlobalPermission(GlobalPermissions.SYSTEM_ADMIN);
+    }
+  }
+
+  private boolean missPermissionsBasedOnKey(@Nullable String key) {
+    return key != null && !userSession.hasProjectPermission(UserRole.ADMIN, key) && !userSession.hasGlobalPermission(GlobalPermissions.SYSTEM_ADMIN);
+  }
+
+  private boolean missPermissionsBasedOnUuid(@Nullable String uuid) {
+    return uuid != null && !userSession.hasProjectPermissionByUuid(UserRole.ADMIN, uuid) && !userSession.hasGlobalPermission(GlobalPermissions.SYSTEM_ADMIN);
+  }
+
+  private ComponentDto searchProject(DbSession dbSession, @Nullable String uuid, @Nullable String key) {
+    if (uuid != null) {
+      return dbClient.componentDao().selectByUuid(dbSession, uuid);
+    }
+    if (key != null) {
+      return dbClient.componentDao().selectByKey(dbSession, key);
+    }
+
+    throw new IllegalArgumentException("UUID or key must be provided");
+  }
+}
index 6f96f2b5bbb1517651b15bd15db8044bb274bc56..320a08f70837197863a55476ebf2639c2e09546c 100644 (file)
@@ -25,7 +25,7 @@ import org.sonar.api.server.ws.RailsHandler;
 import org.sonar.api.server.ws.WebService;
 
 public class ProjectsWs implements WebService {
-  private static final String ENDPOINT = "api/projects";
+  public static final String ENDPOINT = "api/projects";
 
   private final ProjectsWsAction[] actions;
 
diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java
new file mode 100644 (file)
index 0000000..983137c
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.project.ws;
+
+import org.sonar.core.component.Module;
+
+public class ProjectsWsModule extends Module {
+  @Override
+  protected void configureModule() {
+    add(
+      ProjectsWs.class,
+      BulkDeleteAction.class,
+      DeleteAction.class,
+      GhostsAction.class,
+      ProvisionedAction.class);
+  }
+}
index f73b4baa96adbf64d50b9bc710c0ca2912f2cf31..c447745f4e0a76d59d0316fde202597b72d1e037 100644 (file)
@@ -39,6 +39,7 @@ import org.sonar.api.web.UserRole;
 import org.sonar.core.component.ComponentDto;
 import org.sonar.core.component.SnapshotDto;
 import org.sonar.core.issue.db.IssueDto;
+import org.sonar.core.permission.GlobalPermissions;
 import org.sonar.core.persistence.DbSession;
 import org.sonar.core.persistence.DbTester;
 import org.sonar.core.purge.PurgeDao;
@@ -74,6 +75,8 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
+import static org.sonar.server.project.ws.BulkDeleteAction.PARAM_KEYS;
+import static org.sonar.server.project.ws.BulkDeleteAction.PARAM_UUIDS;
 
 @Category(DbTests.class)
 public class BulkDeleteActionTest {
@@ -106,6 +109,7 @@ public class BulkDeleteActionTest {
     when(mockResourceTypes.get(anyString())).thenReturn(resourceType);
     ws = new WsTester(new ProjectsWs(new BulkDeleteAction(new ComponentCleanerService(dbClient, new IssueAuthorizationIndexer(dbClient, es.client()), new IssueIndexer(
       dbClient, es.client()), new SourceLineIndexer(dbClient, es.client()), new TestIndexer(dbClient, es.client()), mockResourceTypes), dbClient, userSessionRule)));
+    userSessionRule.setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
     db.truncateTables();
     es.truncateIndices();
   }
@@ -117,14 +121,13 @@ public class BulkDeleteActionTest {
 
   @Test
   public void delete_projects_and_data_in_db_by_uuids() throws Exception {
-    userSessionRule.setGlobalPermissions(UserRole.ADMIN);
     long snapshotId1 = insertNewProjectInDbAndReturnSnapshotId(1);
     long snapshotId2 = insertNewProjectInDbAndReturnSnapshotId(2);
     long snapshotId3 = insertNewProjectInDbAndReturnSnapshotId(3);
     long snapshotId4 = insertNewProjectInDbAndReturnSnapshotId(4);
 
     ws.newPostRequest("api/projects", ACTION)
-      .setParam("ids", "project-uuid-1, project-uuid-3, project-uuid-4").execute();
+      .setParam(PARAM_UUIDS, "project-uuid-1, project-uuid-3, project-uuid-4").execute();
     dbSession.commit();
 
     assertThat(dbClient.componentDao().selectByUuids(dbSession, Arrays.asList("project-uuid-1", "project-uuid-3", "project-uuid-4"))).isEmpty();
@@ -139,14 +142,13 @@ public class BulkDeleteActionTest {
 
   @Test
   public void delete_projects_and_data_in_db_by_keys() throws Exception {
-    userSessionRule.setGlobalPermissions(UserRole.ADMIN);
     insertNewProjectInDbAndReturnSnapshotId(1);
     insertNewProjectInDbAndReturnSnapshotId(2);
     insertNewProjectInDbAndReturnSnapshotId(3);
     insertNewProjectInDbAndReturnSnapshotId(4);
 
     ws.newPostRequest("api/projects", ACTION)
-      .setParam("keys", "project-key-1, project-key-3, project-key-4").execute();
+      .setParam(PARAM_KEYS, "project-key-1, project-key-3, project-key-4").execute();
     dbSession.commit();
 
     assertThat(dbClient.componentDao().selectByUuids(dbSession, Arrays.asList("project-uuid-1", "project-uuid-3", "project-uuid-4"))).isEmpty();
@@ -155,14 +157,13 @@ public class BulkDeleteActionTest {
 
   @Test
   public void delete_documents_indexes() throws Exception {
-    userSessionRule.setGlobalPermissions(UserRole.ADMIN);
     insertNewProjectInIndexes(1);
     insertNewProjectInIndexes(2);
     insertNewProjectInIndexes(3);
     insertNewProjectInIndexes(4);
 
     ws.newPostRequest("api/projects", ACTION)
-      .setParam("keys", "project-key-1, project-key-3, project-key-4").execute();
+      .setParam(PARAM_KEYS, "project-key-1, project-key-3, project-key-4").execute();
 
     String remainingProjectUuid = "project-uuid-2";
     assertThat(es.getDocumentFieldValues(IssueIndexDefinition.INDEX, IssueIndexDefinition.TYPE_ISSUE, IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID))
@@ -177,10 +178,9 @@ public class BulkDeleteActionTest {
 
   @Test
   public void web_service_returns_204() throws Exception {
-    userSessionRule.setGlobalPermissions(UserRole.ADMIN);
     insertNewProjectInDbAndReturnSnapshotId(1);
 
-    WsTester.Result result = ws.newPostRequest("api/projects", ACTION).setParam("ids", "project-uuid-1").execute();
+    WsTester.Result result = ws.newPostRequest("api/projects", ACTION).setParam(PARAM_UUIDS, "project-uuid-1").execute();
 
     result.assertNoContent();
   }
@@ -190,28 +190,26 @@ public class BulkDeleteActionTest {
     userSessionRule.setGlobalPermissions(UserRole.CODEVIEWER, UserRole.ISSUE_ADMIN, UserRole.USER);
     expectedException.expect(ForbiddenException.class);
 
-    ws.newPostRequest("api/projects", ACTION).setParam("uuids", "whatever-the-uuid").execute();
+    ws.newPostRequest("api/projects", ACTION).setParam(PARAM_UUIDS, "whatever-the-uuid").execute();
   }
 
   @Test
   public void fail_if_scope_is_not_project() throws Exception {
-    userSessionRule.setGlobalPermissions(UserRole.ADMIN);
     expectedException.expect(IllegalArgumentException.class);
     dbClient.componentDao().insert(dbSession, ComponentTesting.newFileDto(ComponentTesting.newProjectDto(), "file-uuid"));
     dbSession.commit();
 
-    ws.newPostRequest("api/projects", ACTION).setParam("uuids", "file-uuid").execute();
+    ws.newPostRequest("api/projects", ACTION).setParam(PARAM_UUIDS, "file-uuid").execute();
   }
 
   @Test
   public void fail_if_qualifier_is_not_deletable() throws Exception {
-    userSessionRule.setGlobalPermissions(UserRole.ADMIN);
     expectedException.expect(IllegalArgumentException.class);
     dbClient.componentDao().insert(dbSession, ComponentTesting.newProjectDto("project-uuid").setQualifier(Qualifiers.FILE));
     dbSession.commit();
     when(resourceType.getBooleanProperty(anyString())).thenReturn(false);
 
-    ws.newPostRequest("api/projects", ACTION).setParam("uuids", "project-uuid").execute();
+    ws.newPostRequest("api/projects", ACTION).setParam(PARAM_UUIDS, "project-uuid").execute();
   }
 
   private long insertNewProjectInDbAndReturnSnapshotId(int id) {
diff --git a/server/sonar-server/src/test/java/org/sonar/server/project/ws/DeleteActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/project/ws/DeleteActionTest.java
new file mode 100644 (file)
index 0000000..37d4824
--- /dev/null
@@ -0,0 +1,267 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.project.ws;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.config.Settings;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.resources.ResourceType;
+import org.sonar.api.resources.ResourceTypes;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.utils.System2;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.component.ComponentDto;
+import org.sonar.core.component.SnapshotDto;
+import org.sonar.core.issue.db.IssueDto;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.DbTester;
+import org.sonar.core.purge.PurgeDao;
+import org.sonar.core.purge.PurgeProfiler;
+import org.sonar.core.resource.ResourceDao;
+import org.sonar.core.rule.RuleDto;
+import org.sonar.server.component.ComponentCleanerService;
+import org.sonar.server.component.ComponentTesting;
+import org.sonar.server.component.SnapshotTesting;
+import org.sonar.server.component.db.ComponentDao;
+import org.sonar.server.component.db.SnapshotDao;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.issue.IssueTesting;
+import org.sonar.server.issue.db.IssueDao;
+import org.sonar.server.issue.index.IssueAuthorizationIndexer;
+import org.sonar.server.issue.index.IssueIndexDefinition;
+import org.sonar.server.issue.index.IssueIndexer;
+import org.sonar.server.rule.RuleTesting;
+import org.sonar.server.rule.db.RuleDao;
+import org.sonar.server.source.index.SourceLineDoc;
+import org.sonar.server.source.index.SourceLineIndexDefinition;
+import org.sonar.server.source.index.SourceLineIndexer;
+import org.sonar.server.test.index.TestDoc;
+import org.sonar.server.test.index.TestIndexDefinition;
+import org.sonar.server.test.index.TestIndexer;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.WsTester;
+import org.sonar.test.DbTests;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.server.project.ws.DeleteAction.PARAM_KEY;
+import static org.sonar.server.project.ws.DeleteAction.PARAM_UUID;
+
+@Category(DbTests.class)
+public class DeleteActionTest {
+
+  private static final String ACTION = "delete";
+  @ClassRule
+  public static DbTester db = new DbTester();
+  @ClassRule
+  public static EsTester es = new EsTester().addDefinitions(new IssueIndexDefinition(new Settings()), new SourceLineIndexDefinition(new Settings()),
+    new TestIndexDefinition(new Settings()));
+  @Rule
+  public UserSessionRule userSessionRule = UserSessionRule.standalone();
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  WsTester ws;
+  DbClient dbClient;
+  DbSession dbSession;
+  ResourceType resourceType;
+
+  @Before
+  public void setUp() throws Exception {
+    ComponentDao componentDao = new ComponentDao();
+    ResourceDao resourceDao = new ResourceDao(db.myBatis(), System2.INSTANCE);
+    PurgeDao purgeDao = new PurgeDao(db.myBatis(), resourceDao, new PurgeProfiler(), System2.INSTANCE);
+    dbClient = new DbClient(db.database(), db.myBatis(), componentDao, purgeDao, new RuleDao(System2.INSTANCE), new IssueDao(db.myBatis()), new SnapshotDao(System2.INSTANCE));
+    dbSession = dbClient.openSession(false);
+    resourceType = mock(ResourceType.class);
+    when(resourceType.getBooleanProperty(anyString())).thenReturn(true);
+    ResourceTypes mockResourceTypes = mock(ResourceTypes.class);
+    when(mockResourceTypes.get(anyString())).thenReturn(resourceType);
+    ws = new WsTester(new ProjectsWs(new DeleteAction(new ComponentCleanerService(dbClient, new IssueAuthorizationIndexer(dbClient, es.client()), new IssueIndexer(
+      dbClient, es.client()), new SourceLineIndexer(dbClient, es.client()), new TestIndexer(dbClient, es.client()), mockResourceTypes), dbClient, userSessionRule)));
+    userSessionRule.login("login").setGlobalPermissions(UserRole.ADMIN);
+    db.truncateTables();
+    es.truncateIndices();
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    dbSession.close();
+  }
+
+  @Test
+  public void delete_project_and_data_in_db_by_uuid() throws Exception {
+    long snapshotId1 = insertNewProjectInDbAndReturnSnapshotId(1);
+    long snapshotId2 = insertNewProjectInDbAndReturnSnapshotId(2);
+
+    newRequest()
+      .setParam(PARAM_UUID, "project-uuid-1").execute();
+    dbSession.commit();
+
+    assertThat(dbClient.componentDao().selectNullableByUuid(dbSession, "project-uuid-1")).isNull();
+    assertThat(dbClient.componentDao().selectByUuid(dbSession, "project-uuid-2")).isNotNull();
+    assertThat(dbClient.snapshotDao().getNullableByKey(dbSession, snapshotId1)).isNull();
+    assertThat(dbClient.snapshotDao().getNullableByKey(dbSession, snapshotId2)).isNotNull();
+    assertThat(dbClient.issueDao().selectNullableByKey(dbSession, "issue-key-1")).isNull();
+    assertThat(dbClient.issueDao().selectByKey(dbSession, "issue-key-2")).isNotNull();
+  }
+
+  @Test
+  public void delete_projects_and_data_in_db_by_key() throws Exception {
+    insertNewProjectInDbAndReturnSnapshotId(1);
+    insertNewProjectInDbAndReturnSnapshotId(2);
+
+    newRequest()
+      .setParam(PARAM_KEY, "project-key-1").execute();
+    dbSession.commit();
+
+    assertThat(dbClient.componentDao().selectNullableByUuid(dbSession, "project-uuid-1")).isNull();
+    assertThat(dbClient.componentDao().selectByUuid(dbSession, "project-uuid-2")).isNotNull();
+  }
+
+  @Test
+  public void delete_projects_by_uuid_when_admin_on_the_project() throws Exception {
+    insertNewProjectInDbAndReturnSnapshotId(1);
+    userSessionRule.login("login").addProjectUuidPermissions(UserRole.ADMIN, "project-uuid-1");
+
+    newRequest().setParam(PARAM_UUID, "project-uuid-1").execute();
+    dbSession.commit();
+
+    assertThat(dbClient.componentDao().selectNullableByUuid(dbSession, "project-uuid-1")).isNull();
+  }
+
+  @Test
+  public void delete_projects_by_key_when_admin_on_the_project() throws Exception {
+    insertNewProjectInDbAndReturnSnapshotId(1);
+    // can't use addProjectUuidPermissions as mock keep separated lists
+    userSessionRule.login("login").addProjectPermissions(UserRole.ADMIN, "project-key-1");
+
+    newRequest().setParam(PARAM_KEY, "project-key-1").execute();
+    dbSession.commit();
+
+    assertThat(dbClient.componentDao().selectNullableByUuid(dbSession, "project-uuid-1")).isNull();
+  }
+
+  @Test
+  public void delete_documents_indexes() throws Exception {
+    insertNewProjectInIndexes(1);
+    insertNewProjectInIndexes(2);
+
+    newRequest()
+      .setParam(PARAM_KEY, "project-key-1").execute();
+
+    String remainingProjectUuid = "project-uuid-2";
+    assertThat(es.getDocumentFieldValues(IssueIndexDefinition.INDEX, IssueIndexDefinition.TYPE_ISSUE, IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID))
+      .containsOnly(remainingProjectUuid);
+    assertThat(es.getDocumentFieldValues(IssueIndexDefinition.INDEX, IssueIndexDefinition.TYPE_AUTHORIZATION, IssueIndexDefinition.FIELD_AUTHORIZATION_PROJECT_UUID))
+      .containsOnly(remainingProjectUuid);
+    assertThat(es.getDocumentFieldValues(SourceLineIndexDefinition.INDEX, SourceLineIndexDefinition.TYPE, SourceLineIndexDefinition.FIELD_PROJECT_UUID))
+      .containsOnly(remainingProjectUuid);
+    assertThat(es.getDocumentFieldValues(TestIndexDefinition.INDEX, TestIndexDefinition.TYPE, TestIndexDefinition.FIELD_PROJECT_UUID))
+      .containsOnly(remainingProjectUuid);
+  }
+
+  @Test
+  public void web_service_returns_204() throws Exception {
+    insertNewProjectInDbAndReturnSnapshotId(1);
+
+    WsTester.Result result = newRequest().setParam(PARAM_UUID, "project-uuid-1").execute();
+
+    result.assertNoContent();
+  }
+
+  @Test
+  public void fail_if_insufficient_privileges() throws Exception {
+    userSessionRule.setGlobalPermissions(UserRole.CODEVIEWER, UserRole.ISSUE_ADMIN, UserRole.USER);
+    expectedException.expect(ForbiddenException.class);
+
+    newRequest().setParam(PARAM_UUID, "whatever-the-uuid").execute();
+  }
+
+  @Test
+  public void fail_if_scope_is_not_project() throws Exception {
+    expectedException.expect(IllegalArgumentException.class);
+    dbClient.componentDao().insert(dbSession, ComponentTesting.newFileDto(ComponentTesting.newProjectDto(), "file-uuid"));
+    dbSession.commit();
+
+    newRequest().setParam(PARAM_UUID, "file-uuid").execute();
+  }
+
+  @Test
+  public void fail_if_qualifier_is_not_deletable() throws Exception {
+    expectedException.expect(IllegalArgumentException.class);
+    dbClient.componentDao().insert(dbSession, ComponentTesting.newProjectDto("project-uuid").setQualifier(Qualifiers.FILE));
+    dbSession.commit();
+    when(resourceType.getBooleanProperty(anyString())).thenReturn(false);
+
+    newRequest().setParam(PARAM_UUID, "project-uuid").execute();
+  }
+
+  private long insertNewProjectInDbAndReturnSnapshotId(int id) {
+    String suffix = String.valueOf(id);
+    ComponentDto project = ComponentTesting
+      .newProjectDto("project-uuid-" + suffix)
+      .setKey("project-key-" + suffix);
+    RuleDto rule = RuleTesting.newDto(RuleKey.of("sonarqube", "rule-" + suffix));
+    dbClient.ruleDao().insert(dbSession, rule);
+    IssueDto issue = IssueTesting.newDto(rule, project, project).setKee("issue-key-" + suffix);
+    dbClient.componentDao().insert(dbSession, project);
+    SnapshotDto snapshot = dbClient.snapshotDao().insert(dbSession, SnapshotTesting.createForProject(project));
+    dbClient.issueDao().insert(dbSession, issue);
+    dbSession.commit();
+
+    return snapshot.getId();
+  }
+
+  private void insertNewProjectInIndexes(int id) throws Exception {
+    String suffix = String.valueOf(id);
+    ComponentDto project = ComponentTesting
+      .newProjectDto("project-uuid-" + suffix)
+      .setKey("project-key-" + suffix);
+    dbClient.componentDao().insert(dbSession, project);
+    dbSession.commit();
+
+    es.putDocuments(IssueIndexDefinition.INDEX, IssueIndexDefinition.TYPE_ISSUE, IssueTesting.newDoc("issue-key-" + suffix, project));
+    SourceLineDoc sourceLineDoc = new SourceLineDoc()
+      .setProjectUuid(project.uuid())
+      .setFileUuid(project.uuid());
+    es.putDocuments(IssueIndexDefinition.INDEX, IssueIndexDefinition.TYPE_AUTHORIZATION,
+      ImmutableMap.<String, Object>of(IssueIndexDefinition.FIELD_AUTHORIZATION_PROJECT_UUID, project.uuid()));
+
+    es.putDocuments(SourceLineIndexDefinition.INDEX, SourceLineIndexDefinition.TYPE, sourceLineDoc);
+    TestDoc testDoc = new TestDoc().setUuid("test-uuid-" + suffix).setProjectUuid(project.uuid()).setFileUuid(project.uuid());
+    es.putDocuments(TestIndexDefinition.INDEX, TestIndexDefinition.TYPE, testDoc);
+  }
+
+  private WsTester.TestRequest newRequest() {
+    return ws.newPostRequest(ProjectsWs.ENDPOINT, ACTION);
+  }
+}