From 0efa53f5b816bcbea76dc30d398869f288025888 Mon Sep 17 00:00:00 2001 From: Teryk Bellahsene Date: Mon, 1 Jun 2015 11:29:51 +0200 Subject: [PATCH] SONAR-6599 WS api/projects/delete delete one project --- .../platformlevel/PlatformLevel4.java | 10 +- .../server/project/ws/BulkDeleteAction.java | 4 +- .../sonar/server/project/ws/DeleteAction.java | 114 ++++++++ .../sonar/server/project/ws/ProjectsWs.java | 2 +- .../server/project/ws/ProjectsWsModule.java | 35 +++ .../project/ws/BulkDeleteActionTest.java | 24 +- .../server/project/ws/DeleteActionTest.java | 267 ++++++++++++++++++ 7 files changed, 432 insertions(+), 24 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/project/ws/DeleteAction.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/project/ws/DeleteActionTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index ab1d0513ecc..f05626b68c4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -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, diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/ws/BulkDeleteAction.java b/server/sonar-server/src/main/java/org/sonar/server/project/ws/BulkDeleteAction.java index 6ebf54a3574..52293fdc6c8 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/project/ws/BulkDeleteAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/project/ws/BulkDeleteAction.java @@ -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 index 00000000000..4665a53ff2d --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/project/ws/DeleteAction.java @@ -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.
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"); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWs.java b/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWs.java index 6f96f2b5bbb..320a08f7083 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWs.java +++ b/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWs.java @@ -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 index 00000000000..983137cd9db --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java @@ -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); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/project/ws/BulkDeleteActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/project/ws/BulkDeleteActionTest.java index f73b4baa96a..c447745f4e0 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/project/ws/BulkDeleteActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/project/ws/BulkDeleteActionTest.java @@ -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 index 00000000000..37d48248a46 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/project/ws/DeleteActionTest.java @@ -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.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); + } +} -- 2.39.5