From: Teryk Bellahsene Date: Tue, 19 May 2015 08:24:14 +0000 (+0200) Subject: WS api/projects/delete delete project and associated data - SONAR-6528 X-Git-Tag: 5.2-RC1~1914 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=refs%2Fpull%2F312%2Fhead;p=sonarqube.git WS api/projects/delete delete project and associated data - SONAR-6528 --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ComponentCleanerService.java b/server/sonar-server/src/main/java/org/sonar/server/component/ComponentCleanerService.java index 89154e1231e..62245dca64f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/component/ComponentCleanerService.java +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ComponentCleanerService.java @@ -21,54 +21,67 @@ 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 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 index 00000000000..179a24d741b --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/ProjectsDeleteAction.java @@ -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.
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 uuids = request.paramAsStrings(PARAM_UUIDS); + List keys = request.paramAsStrings(PARAM_KEYS); + + DbSession dbSession = dbClient.openSession(false); + try { + List projects = searchProjects(dbSession, uuids, keys); + componentCleanerService.delete(dbSession, projects); + } finally { + MyBatis.closeQuietly(dbSession); + } + + response.noContent(); + } + + private List searchProjects(DbSession dbSession, @Nullable List uuids, @Nullable List 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"); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/DbClient.java b/server/sonar-server/src/main/java/org/sonar/server/db/DbClient.java index 9c2f690cc7c..c1c69559104 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/DbClient.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/DbClient.java @@ -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 getDao(Map map, Class 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 index 57b48cedd78..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/component/ComponentCleanerServiceMediumTest.java +++ /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 index 00000000000..b21454b0980 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/component/ws/ProjectsDeleteActionTest.java @@ -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.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); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/test/ws/TestsListActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/test/ws/TestsListActionTest.java index a73dad722af..3de460c906d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/test/ws/TestsListActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/test/ws/TestsListActionTest.java @@ -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; diff --git a/sonar-core/src/main/java/org/sonar/core/purge/PurgeDao.java b/sonar-core/src/main/java/org/sonar/core/purge/PurgeDao.java index 9f211cbd35a..e35163b64d8 100644 --- a/sonar-core/src/main/java/org/sonar/core/purge/PurgeDao.java +++ b/sonar-core/src/main/java/org/sonar/core/purge/PurgeDao.java @@ -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 selectPurgeableSnapshots(long resourceId, DbSession session) { - PurgeMapper mapper = session.getMapper(PurgeMapper.class); List 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 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); } }