]> source.dussan.org Git - sonarqube.git/commitdiff
WS api/projects/delete delete project and associated data - SONAR-6528 312/head
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Tue, 19 May 2015 08:24:14 +0000 (10:24 +0200)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Tue, 19 May 2015 13:25:06 +0000 (15:25 +0200)
server/sonar-server/src/main/java/org/sonar/server/component/ComponentCleanerService.java
server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectsDeleteAction.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/db/DbClient.java
server/sonar-server/src/test/java/org/sonar/server/component/ComponentCleanerServiceMediumTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectsDeleteActionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/test/ws/TestsListActionTest.java
sonar-core/src/main/java/org/sonar/core/purge/PurgeDao.java

index 89154e1231e1d40bf621496c28c6f79214a4f74c..62245dca64fbdd5d1d622c22c1d6c9aca21e0f5d 100644 (file)
 package org.sonar.server.component;
 
 import org.sonar.api.ServerSide;
+import org.sonar.api.resources.ResourceType;
+import org.sonar.api.resources.ResourceTypes;
 import org.sonar.api.resources.Scopes;
 import org.sonar.core.component.ComponentDto;
 import org.sonar.core.persistence.DbSession;
 import org.sonar.core.persistence.MyBatis;
 import org.sonar.core.purge.IdUuidPair;
-import org.sonar.core.purge.PurgeDao;
 import org.sonar.server.db.DbClient;
 import org.sonar.server.issue.index.IssueAuthorizationIndexer;
 import org.sonar.server.issue.index.IssueIndexer;
 import org.sonar.server.source.index.SourceLineIndexer;
 import org.sonar.server.test.index.TestIndexer;
 
+import java.util.List;
+
 @ServerSide
 public class ComponentCleanerService {
 
   private final DbClient dbClient;
-  private final PurgeDao purgeDao;
   private final IssueAuthorizationIndexer issueAuthorizationIndexer;
   private final IssueIndexer issueIndexer;
   private final SourceLineIndexer sourceLineIndexer;
   private final TestIndexer testIndexer;
+  private final ResourceTypes resourceTypes;
 
-  public ComponentCleanerService(DbClient dbClient, PurgeDao purgeDao, IssueAuthorizationIndexer issueAuthorizationIndexer, IssueIndexer issueIndexer,
-                                 SourceLineIndexer sourceLineIndexer, TestIndexer testIndexer) {
+  public ComponentCleanerService(DbClient dbClient, IssueAuthorizationIndexer issueAuthorizationIndexer, IssueIndexer issueIndexer,
+    SourceLineIndexer sourceLineIndexer, TestIndexer testIndexer, ResourceTypes resourceTypes) {
     this.dbClient = dbClient;
-    this.purgeDao = purgeDao;
     this.issueAuthorizationIndexer = issueAuthorizationIndexer;
     this.issueIndexer = issueIndexer;
     this.sourceLineIndexer = sourceLineIndexer;
     this.testIndexer = testIndexer;
+    this.resourceTypes = resourceTypes;
+  }
+
+  public void delete(DbSession dbSession, List<ComponentDto> projects) {
+    for (ComponentDto project : projects) {
+      delete(dbSession, project);
+    }
   }
 
   public void delete(String projectKey) {
     DbSession dbSession = dbClient.openSession(false);
     try {
       ComponentDto project = dbClient.componentDao().selectByKey(dbSession, projectKey);
-      if (!Scopes.PROJECT.equals(project.scope())) {
-        throw new IllegalArgumentException("Only projects can be deleted");
-      }
-      purgeDao.deleteResourceTree(new IdUuidPair(project.getId(), project.uuid()));
-      dbSession.commit();
-
-      deleteFromIndices(project.uuid());
+      delete(dbSession, project);
     } finally {
       MyBatis.closeQuietly(dbSession);
     }
   }
 
+  private void delete(DbSession dbSession, ComponentDto project) {
+    if (hasNotProjectScope(project) || isNotDeletable(project)) {
+      throw new IllegalArgumentException("Only projects can be deleted");
+    }
+    dbClient.purgeDao().deleteResourceTree(dbSession, new IdUuidPair(project.getId(), project.uuid()));
+    dbSession.commit();
+
+    deleteFromIndices(project.uuid());
+  }
+
   private void deleteFromIndices(String projectUuid) {
     // optimization : index "issues" is refreshed once at the end
     issueAuthorizationIndexer.deleteProject(projectUuid, false);
@@ -77,4 +90,12 @@ public class ComponentCleanerService {
     testIndexer.deleteByProject(projectUuid);
   }
 
+  private boolean hasNotProjectScope(ComponentDto project) {
+    return !Scopes.PROJECT.equals(project.scope());
+  }
+
+  private boolean isNotDeletable(ComponentDto project) {
+    ResourceType resourceType = resourceTypes.get(project.qualifier());
+    return resourceType == null || !resourceType.getBooleanProperty("deletable");
+  }
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectsDeleteAction.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectsDeleteAction.java
new file mode 100644 (file)
index 0000000..179a24d
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * 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.component.ws;
+
+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.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;
+
+import javax.annotation.Nullable;
+
+import java.util.List;
+
+public class ProjectsDeleteAction implements ProjectsWsAction {
+  private static final String ACTION = "delete";
+  private static final String PARAM_UUIDS = "uuids";
+  private static final String PARAM_KEYS = "keys";
+
+  private final ComponentCleanerService componentCleanerService;
+  private final DbClient dbClient;
+  private final UserSession userSession;
+
+  public ProjectsDeleteAction(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)
+      .setDescription("Delete one or several projects.<br /> Requires Admin permission.")
+      .setSince("5.2")
+      .setHandler(this);
+
+    action
+      .createParam(PARAM_UUIDS)
+      .setDescription("List of project UUIDs to delete")
+      .setExampleValue("ce4c03d6-430f-40a9-b777-ad877c00aa4d,c526ef20-131b-4486-9357-063fa64b5079");
+
+    action
+      .createParam(PARAM_KEYS)
+      .setDescription("List of project keys to delete")
+      .setExampleValue("org.apache.hbas:hbase,com.microsoft.roslyn:roslyn");
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+    userSession.checkGlobalPermission(UserRole.ADMIN);
+    List<String> uuids = request.paramAsStrings(PARAM_UUIDS);
+    List<String> keys = request.paramAsStrings(PARAM_KEYS);
+
+    DbSession dbSession = dbClient.openSession(false);
+    try {
+      List<ComponentDto> projects = searchProjects(dbSession, uuids, keys);
+      componentCleanerService.delete(dbSession, projects);
+    } finally {
+      MyBatis.closeQuietly(dbSession);
+    }
+
+    response.noContent();
+  }
+
+  private List<ComponentDto> searchProjects(DbSession dbSession, @Nullable List<String> uuids, @Nullable List<String> keys) {
+    if (uuids != null) {
+      return dbClient.componentDao().selectByUuids(dbSession, uuids);
+    }
+    if (keys != null) {
+      return dbClient.componentDao().selectByKeys(dbSession, keys);
+    }
+
+    throw new IllegalArgumentException("UUIDs or keys must be provided");
+  }
+}
index 9c2f690cc7c33d59b1c8f8ac1172670cfa583821..c1c69559104f9f80c64dc6da83e59ae43b06b7e5 100644 (file)
@@ -28,6 +28,7 @@ import org.sonar.core.persistence.Database;
 import org.sonar.core.persistence.DbSession;
 import org.sonar.core.persistence.MyBatis;
 import org.sonar.core.properties.PropertiesDao;
+import org.sonar.core.purge.PurgeDao;
 import org.sonar.core.qualityprofile.db.QualityProfileDao;
 import org.sonar.core.resource.ResourceDao;
 import org.sonar.core.technicaldebt.db.CharacteristicDao;
@@ -102,6 +103,7 @@ public class DbClient {
   private final ComponentLinkDao componentLinkDao;
   private final EventDao eventDao;
   private final FileDependencyDao fileDependencyDao;
+  private final PurgeDao purgeDao;
 
   public DbClient(Database db, MyBatis myBatis, DaoComponent... daoComponents) {
     this.db = db;
@@ -142,6 +144,7 @@ public class DbClient {
     componentLinkDao = getDao(map, ComponentLinkDao.class);
     eventDao = getDao(map, EventDao.class);
     fileDependencyDao = getDao(map, FileDependencyDao.class);
+    purgeDao = getDao(map, PurgeDao.class);
   }
 
   public Database database() {
@@ -276,6 +279,10 @@ public class DbClient {
     return fileDependencyDao;
   }
 
+  public PurgeDao purgeDao() {
+    return purgeDao;
+  }
+
   private <K> K getDao(Map<Class, DaoComponent> map, Class<K> clazz) {
     return (K) map.get(clazz);
   }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ComponentCleanerServiceMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ComponentCleanerServiceMediumTest.java
deleted file mode 100644 (file)
index 57b48ce..0000000
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * 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.component;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.security.DefaultGroups;
-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.rule.RuleDto;
-import org.sonar.server.component.db.ComponentDao;
-import org.sonar.server.db.DbClient;
-import org.sonar.server.es.EsClient;
-import org.sonar.server.issue.IssueTesting;
-import org.sonar.server.issue.db.IssueDao;
-import org.sonar.server.issue.index.IssueIndex;
-import org.sonar.server.issue.index.IssueIndexDefinition;
-import org.sonar.server.issue.index.IssueIndexer;
-import org.sonar.server.permission.InternalPermissionService;
-import org.sonar.server.permission.PermissionChange;
-import org.sonar.server.rule.RuleTesting;
-import org.sonar.server.rule.db.RuleDao;
-import org.sonar.server.search.IndexClient;
-import org.sonar.server.tester.ServerTester;
-import org.sonar.server.tester.UserSessionRule;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class ComponentCleanerServiceMediumTest {
-
-  @ClassRule
-  public static ServerTester tester = new ServerTester();
-  @Rule
-  public UserSessionRule userSessionRule = UserSessionRule.forServerTester(tester);
-
-  DbClient db;
-  IndexClient index;
-  DbSession session;
-
-  ComponentCleanerService service;
-
-  @Before
-  public void setUp() {
-    tester.clearDbAndIndexes();
-
-    db = tester.get(DbClient.class);
-    index = tester.get(IndexClient.class);
-    session = db.openSession(false);
-    service = tester.get(ComponentCleanerService.class);
-  }
-
-  @After
-  public void after() {
-    session.close();
-  }
-
-  @Test
-  public void delete_project() {
-    ComponentDto project = ComponentTesting.newProjectDto();
-    db.componentDao().insert(session, project);
-    session.commit();
-
-    service.delete(project.getKey());
-
-    assertThat(db.componentDao().selectNullableByKey(session, project.key())).isNull();
-  }
-
-  @Test
-  public void remove_issue_permission_index_when_deleting_a_project() {
-    ComponentDto project = ComponentTesting.newProjectDto();
-    db.componentDao().insert(session, project);
-
-    // project can be seen by anyone
-    session.commit();
-    userSessionRule.login("admin").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
-    tester.get(InternalPermissionService.class).addPermission(new PermissionChange().setComponentKey(project.getKey()).setGroup(DefaultGroups.ANYONE).setPermission(UserRole.USER));
-
-    assertThat(countIssueAuthorizationDocs()).isEqualTo(1);
-
-    service.delete(project.getKey());
-
-    assertThat(countIssueAuthorizationDocs()).isEqualTo(0);
-  }
-
-  @Test
-  public void remove_issue_when_deleting_a_project() {
-    // ARRANGE
-    ComponentDto project = ComponentTesting.newProjectDto();
-    db.componentDao().insert(session, project);
-
-    RuleDto rule = RuleTesting.newXooX1();
-    tester.get(RuleDao.class).insert(session, rule);
-
-    ComponentDto file = ComponentTesting.newFileDto(project);
-    tester.get(ComponentDao.class).insert(session, file);
-
-    tester.get(IssueDao.class).insert(session, IssueTesting.newDto(rule, file, project));
-    session.commit();
-    tester.get(IssueIndexer.class).indexAll();
-
-    assertThat(tester.get(IssueIndex.class).countAll()).isEqualTo(1);
-
-    // ACT
-    service.delete(project.getKey());
-
-    assertThat(tester.get(IssueIndex.class).countAll()).isEqualTo(0);
-  }
-
-  @Test(expected = IllegalArgumentException.class)
-  public void fail_to_delete_not_project() {
-    ComponentDto project = ComponentTesting.newProjectDto();
-    ComponentDto file = ComponentTesting.newFileDto(project);
-    db.componentDao().insert(session, project, file);
-    session.commit();
-
-    service.delete(file.getKey());
-  }
-
-  private long countIssueAuthorizationDocs() {
-    return tester.get(EsClient.class).prepareCount(IssueIndexDefinition.INDEX).setTypes(IssueIndexDefinition.TYPE_AUTHORIZATION).get().getCount();
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectsDeleteActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectsDeleteActionTest.java
new file mode 100644 (file)
index 0000000..b21454b
--- /dev/null
@@ -0,0 +1,252 @@
+/*
+ * 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.component.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 java.util.Arrays;
+
+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;
+
+@Category(DbTests.class)
+public class ProjectsDeleteActionTest {
+
+  @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 ProjectsDeleteAction(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)));
+    db.truncateTables();
+    es.truncateIndices();
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    dbSession.close();
+  }
+
+  @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.newGetRequest("api/projects", "delete")
+      .setParam("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();
+    assertThat(dbClient.componentDao().selectByUuid(dbSession, "project-uuid-2")).isNotNull();
+    assertThat(dbClient.snapshotDao().getNullableByKey(dbSession, snapshotId1)).isNull();
+    assertThat(dbClient.snapshotDao().getNullableByKey(dbSession, snapshotId3)).isNull();
+    assertThat(dbClient.snapshotDao().getNullableByKey(dbSession, snapshotId4)).isNull();
+    assertThat(dbClient.snapshotDao().getNullableByKey(dbSession, snapshotId2)).isNotNull();
+    assertThat(dbClient.issueDao().selectByKeys(dbSession, Arrays.asList("issue-key-1", "issue-key-3", "issue-key-4"))).isEmpty();
+    assertThat(dbClient.issueDao().selectByKey(dbSession, "issue-key-2")).isNotNull();
+  }
+
+  @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.newGetRequest("api/projects", "delete")
+      .setParam("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();
+    assertThat(dbClient.componentDao().selectByUuid(dbSession, "project-uuid-2")).isNotNull();
+  }
+
+  @Test
+  public void delete_documents_indexes() throws Exception {
+    userSessionRule.setGlobalPermissions(UserRole.ADMIN);
+    insertNewProjectInIndexes(1);
+    insertNewProjectInIndexes(2);
+    insertNewProjectInIndexes(3);
+    insertNewProjectInIndexes(4);
+
+    ws.newGetRequest("api/projects", "delete")
+      .setParam("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))
+      .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 {
+    userSessionRule.setGlobalPermissions(UserRole.ADMIN);
+    insertNewProjectInDbAndReturnSnapshotId(1);
+
+    WsTester.Result result = ws.newGetRequest("api/projects", "delete").setParam("uuids", "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);
+
+    ws.newGetRequest("api/projects", "delete").setParam("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.newGetRequest("api/projects", "delete").setParam("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.newGetRequest("api/projects", "delete").setParam("uuids", "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);
+  }
+}
index a73dad722af16df1b28753f740dcf48cc7133b8b..3de460c906dac9d01407c3f3c813089430c160a3 100644 (file)
@@ -25,6 +25,7 @@ import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.experimental.categories.Category;
 import org.sonar.api.config.Settings;
 import org.sonar.api.web.UserRole;
 import org.sonar.core.component.ComponentDto;
@@ -40,10 +41,12 @@ import org.sonar.server.test.index.TestIndex;
 import org.sonar.server.test.index.TestIndexDefinition;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.WsTester;
+import org.sonar.test.DbTests;
 
 import java.util.Arrays;
 import java.util.List;
 
+@Category(DbTests.class)
 public class TestsListActionTest {
   DbClient dbClient;
   DbSession dbSession;
index 9f211cbd35ab64e217ce1c1e84e79b35369cbad4..e35163b64d8bcf90c91c2612126f7a49b1f022a7 100644 (file)
@@ -27,6 +27,7 @@ import org.apache.ibatis.session.SqlSession;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.sonar.api.utils.System2;
+import org.sonar.core.persistence.DaoComponent;
 import org.sonar.core.persistence.DbSession;
 import org.sonar.core.persistence.MyBatis;
 import org.sonar.core.resource.ResourceDao;
@@ -43,7 +44,7 @@ import static org.sonar.api.utils.DateUtils.dateToLong;
 /**
  * @since 2.14
  */
-public class PurgeDao {
+public class PurgeDao implements DaoComponent {
   private static final Logger LOG = LoggerFactory.getLogger(PurgeDao.class);
   private final MyBatis mybatis;
   private final ResourceDao resourceDao;
@@ -163,27 +164,29 @@ public class PurgeDao {
   }
 
   public List<PurgeableSnapshotDto> selectPurgeableSnapshots(long resourceId, DbSession session) {
-    PurgeMapper mapper = session.getMapper(PurgeMapper.class);
     List<PurgeableSnapshotDto> result = Lists.newArrayList();
-    result.addAll(mapper.selectPurgeableSnapshotsWithEvents(resourceId));
-    result.addAll(mapper.selectPurgeableSnapshotsWithoutEvents(resourceId));
+    result.addAll(mapper(session).selectPurgeableSnapshotsWithEvents(resourceId));
+    result.addAll(mapper(session).selectPurgeableSnapshotsWithoutEvents(resourceId));
     // sort by date
     Collections.sort(result);
     return result;
   }
 
   public PurgeDao deleteResourceTree(IdUuidPair rootIdUuid) {
-    final DbSession session = mybatis.openSession(true);
-    final PurgeMapper mapper = session.getMapper(PurgeMapper.class);
+    DbSession session = mybatis.openSession(true);
     try {
-      deleteProject(rootIdUuid, mapper, new PurgeCommands(session, profiler));
-      deleteFileSources(rootIdUuid.getUuid(), new PurgeCommands(session, profiler));
-      return this;
+      return deleteResourceTree(session, rootIdUuid);
     } finally {
       MyBatis.closeQuietly(session);
     }
   }
 
+  public PurgeDao deleteResourceTree(DbSession session, IdUuidPair rootIdUuid) {
+    deleteProject(rootIdUuid, mapper(session), new PurgeCommands(session, profiler));
+    deleteFileSources(rootIdUuid.getUuid(), new PurgeCommands(session, profiler));
+    return this;
+  }
+
   private void deleteFileSources(String rootUuid, PurgeCommands commands) {
     commands.deleteFileSources(rootUuid);
   }
@@ -233,7 +236,10 @@ public class PurgeDao {
   }
 
   public List<String> selectPurgeableFiles(DbSession dbSession, Long projectId) {
-    PurgeMapper mapper = dbSession.getMapper(PurgeMapper.class);
-    return mapper.selectPurgeableFileUuids(projectId);
+    return mapper(dbSession).selectPurgeableFileUuids(projectId);
+  }
+
+  private PurgeMapper mapper(DbSession session) {
+    return session.getMapper(PurgeMapper.class);
   }
 }