diff options
author | Janos Gyerik <janos.gyerik@sonarsource.com> | 2019-07-03 11:11:08 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2019-07-05 20:21:13 +0200 |
commit | cdafda526fa26697b976a14187b7f210a257a75e (patch) | |
tree | a2fab37e9b1ffb1e4f10d21a4fde353dae25b703 | |
parent | c2ee935557c8cc2dd34c48761cc8eafbe75b50dc (diff) | |
download | sonarqube-cdafda526fa26697b976a14187b7f210a257a75e.tar.gz sonarqube-cdafda526fa26697b976a14187b7f210a257a75e.zip |
SC-799 Extract organization deletion to OrganizationDeleter
6 files changed, 585 insertions, 107 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/DeleteAction.java b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/DeleteAction.java index 152540a8027..9d76d8adf9c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/DeleteAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/DeleteAction.java @@ -19,33 +19,16 @@ */ package org.sonar.server.organization.ws; -import java.util.Collection; -import java.util.List; -import java.util.Set; -import org.sonar.api.resources.Qualifiers; -import org.sonar.api.resources.Scopes; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; -import org.sonar.core.util.stream.MoreCollectors; import org.sonar.db.DbClient; import org.sonar.db.DbSession; -import org.sonar.db.component.ComponentDto; import org.sonar.db.organization.OrganizationDto; -import org.sonar.db.qualitygate.QualityGateDto; -import org.sonar.db.qualityprofile.QProfileDto; -import org.sonar.db.user.UserDto; -import org.sonar.server.component.ComponentCleanerService; -import org.sonar.server.organization.BillingValidations; -import org.sonar.server.organization.BillingValidationsProxy; import org.sonar.server.organization.DefaultOrganization; import org.sonar.server.organization.DefaultOrganizationProvider; import org.sonar.server.organization.OrganizationFlags; -import org.sonar.server.project.Project; -import org.sonar.server.project.ProjectLifeCycleListeners; -import org.sonar.server.qualityprofile.QProfileFactory; import org.sonar.server.user.UserSession; -import org.sonar.server.user.index.UserIndexer; import static com.google.common.base.Preconditions.checkArgument; import static org.sonar.db.permission.OrganizationPermission.ADMINISTER; @@ -60,25 +43,16 @@ public class DeleteAction implements OrganizationsWsAction { private final UserSession userSession; private final DbClient dbClient; private final DefaultOrganizationProvider defaultOrganizationProvider; - private final ComponentCleanerService componentCleanerService; private final OrganizationFlags organizationFlags; - private final UserIndexer userIndexer; - private final QProfileFactory qProfileFactory; - private final ProjectLifeCycleListeners projectLifeCycleListeners; - private final BillingValidationsProxy billingValidations; + private final OrganizationDeleter organizationDeleter; - public DeleteAction(UserSession userSession, DbClient dbClient, DefaultOrganizationProvider defaultOrganizationProvider, ComponentCleanerService componentCleanerService, - OrganizationFlags organizationFlags, UserIndexer userIndexer, QProfileFactory qProfileFactory, ProjectLifeCycleListeners projectLifeCycleListeners, - BillingValidationsProxy billingValidations) { + public DeleteAction(UserSession userSession, DbClient dbClient, DefaultOrganizationProvider defaultOrganizationProvider, + OrganizationFlags organizationFlags, OrganizationDeleter organizationDeleter) { this.userSession = userSession; this.dbClient = dbClient; this.defaultOrganizationProvider = defaultOrganizationProvider; - this.componentCleanerService = componentCleanerService; this.organizationFlags = organizationFlags; - this.userIndexer = userIndexer; - this.qProfileFactory = qProfileFactory; - this.projectLifeCycleListeners = projectLifeCycleListeners; - this.billingValidations = billingValidations; + this.organizationDeleter = organizationDeleter; } @Override @@ -111,78 +85,12 @@ public class DeleteAction implements OrganizationsWsAction { OrganizationDto organization = checkFoundWithOptional(dbClient.organizationDao().selectByKey(dbSession, key), "Organization with key '%s' not found", key); userSession.checkPermission(ADMINISTER, organization); - deleteProjects(dbSession, organization); - deletePermissions(dbSession, organization); - deleteGroups(dbSession, organization); - deleteQualityProfiles(dbSession, organization); - deleteQualityGates(dbSession, organization); - deleteOrganizationAlmBinding(dbSession, organization); - deleteOrganization(dbSession, organization); - billingValidations.onDelete(new BillingValidations.Organization(organization.getKey(), organization.getUuid(), organization.getName())); + organizationDeleter.delete(dbSession, organization); response.noContent(); } } - private void deleteProjects(DbSession dbSession, OrganizationDto organization) { - List<ComponentDto> roots = dbClient.componentDao().selectProjectsByOrganization(dbSession, organization.getUuid()); - try { - componentCleanerService.delete(dbSession, roots); - } finally { - Set<Project> projects = roots.stream() - .filter(DeleteAction::isMainProject) - .map(Project::from) - .collect(MoreCollectors.toSet()); - projectLifeCycleListeners.onProjectsDeleted(projects); - } - } - - private static boolean isMainProject(ComponentDto p) { - return Scopes.PROJECT.equals(p.scope()) - && Qualifiers.PROJECT.equals(p.qualifier()) - && p.getMainBranchProjectUuid() == null; - } - - private void deletePermissions(DbSession dbSession, OrganizationDto organization) { - dbClient.permissionTemplateDao().deleteByOrganization(dbSession, organization.getUuid()); - dbClient.userPermissionDao().deleteByOrganization(dbSession, organization.getUuid()); - dbClient.groupPermissionDao().deleteByOrganization(dbSession, organization.getUuid()); - } - - private void deleteGroups(DbSession dbSession, OrganizationDto organization) { - dbClient.groupDao().deleteByOrganization(dbSession, organization.getUuid()); - } - - private void deleteQualityProfiles(DbSession dbSession, OrganizationDto organization) { - List<QProfileDto> profiles = dbClient.qualityProfileDao().selectOrderedByOrganizationUuid(dbSession, organization); - qProfileFactory.delete(dbSession, profiles); - } - - private void deleteQualityGates(DbSession dbSession, OrganizationDto organization) { - Collection<QualityGateDto> qualityGates = dbClient.qualityGateDao().selectAll(dbSession, organization); - dbClient.qualityGateDao().deleteByUuids(dbSession, qualityGates.stream() - .filter(q -> !q.isBuiltIn()) - .map(QualityGateDto::getUuid) - .collect(MoreCollectors.toList())); - dbClient.qualityGateDao().deleteOrgQualityGatesByOrganization(dbSession, organization); - } - - private void deleteOrganizationAlmBinding(DbSession dbSession, OrganizationDto organization){ - dbClient.organizationAlmBindingDao().deleteByOrganization(dbSession, organization); - } - - private void deleteOrganization(DbSession dbSession, OrganizationDto organization) { - Collection<String> uuids = dbClient.organizationMemberDao().selectUserUuidsByOrganizationUuid(dbSession, organization.getUuid()); - dbClient.organizationMemberDao().deleteByOrganizationUuid(dbSession, organization.getUuid()); - dbClient.organizationDao().deleteByUuid(dbSession, organization.getUuid()); - dbClient.userDao().cleanHomepage(dbSession, organization); - dbClient.webhookDao().selectByOrganizationUuid(dbSession, organization.getUuid()) - .forEach(wh -> dbClient.webhookDeliveryDao().deleteByWebhook(dbSession, wh)); - dbClient.webhookDao().deleteByOrganization(dbSession, organization); - List<UserDto> users = dbClient.userDao().selectByUuids(dbSession, uuids); - userIndexer.commitAndIndex(dbSession, users); - } - private static void preventDeletionOfDefaultOrganization(String key, DefaultOrganization defaultOrganization) { checkArgument(!defaultOrganization.getKey().equals(key), "Default Organization can't be deleted"); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationDeleter.java b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationDeleter.java new file mode 100644 index 00000000000..fb638feeda9 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationDeleter.java @@ -0,0 +1,164 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.organization.ws; + +import com.google.common.annotations.VisibleForTesting; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.resources.Scopes; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.core.util.stream.MoreCollectors; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.db.organization.OrganizationQuery; +import org.sonar.db.qualitygate.QualityGateDto; +import org.sonar.db.qualityprofile.QProfileDto; +import org.sonar.db.user.UserDto; +import org.sonar.server.component.ComponentCleanerService; +import org.sonar.server.organization.BillingValidations; +import org.sonar.server.organization.BillingValidationsProxy; +import org.sonar.server.project.Project; +import org.sonar.server.project.ProjectLifeCycleListeners; +import org.sonar.server.qualityprofile.QProfileFactory; +import org.sonar.server.user.index.UserIndexer; + +import static org.sonar.db.Pagination.forPage; + +public class OrganizationDeleter { + + private static final Logger LOGGER = Loggers.get(OrganizationDeleter.class); + + @VisibleForTesting + static final int PAGE_SIZE = 100; + + private final DbClient dbClient; + private final ComponentCleanerService componentCleanerService; + private final UserIndexer userIndexer; + private final QProfileFactory qProfileFactory; + private final ProjectLifeCycleListeners projectLifeCycleListeners; + private final BillingValidationsProxy billingValidations; + + public OrganizationDeleter(DbClient dbClient, ComponentCleanerService componentCleanerService, UserIndexer userIndexer, + QProfileFactory qProfileFactory, ProjectLifeCycleListeners projectLifeCycleListeners, + BillingValidationsProxy billingValidations) { + this.dbClient = dbClient; + this.componentCleanerService = componentCleanerService; + this.userIndexer = userIndexer; + this.qProfileFactory = qProfileFactory; + this.projectLifeCycleListeners = projectLifeCycleListeners; + this.billingValidations = billingValidations; + } + + void delete(DbSession dbSession, OrganizationDto organization) { + deleteProjects(dbSession, organization); + deletePermissions(dbSession, organization); + deleteGroups(dbSession, organization); + deleteQualityProfiles(dbSession, organization); + deleteQualityGates(dbSession, organization); + deleteOrganizationAlmBinding(dbSession, organization); + deleteOrganization(dbSession, organization); + billingValidations.onDelete(new BillingValidations.Organization(organization.getKey(), organization.getUuid(), organization.getName())); + } + + private void deleteProjects(DbSession dbSession, OrganizationDto organization) { + List<ComponentDto> roots = dbClient.componentDao().selectProjectsByOrganization(dbSession, organization.getUuid()); + try { + componentCleanerService.delete(dbSession, roots); + } finally { + Set<Project> projects = roots.stream() + .filter(OrganizationDeleter::isMainProject) + .map(Project::from) + .collect(MoreCollectors.toSet()); + projectLifeCycleListeners.onProjectsDeleted(projects); + } + } + + private static boolean isMainProject(ComponentDto p) { + return Scopes.PROJECT.equals(p.scope()) + && Qualifiers.PROJECT.equals(p.qualifier()) + && p.getMainBranchProjectUuid() == null; + } + + private void deletePermissions(DbSession dbSession, OrganizationDto organization) { + dbClient.permissionTemplateDao().deleteByOrganization(dbSession, organization.getUuid()); + dbClient.userPermissionDao().deleteByOrganization(dbSession, organization.getUuid()); + dbClient.groupPermissionDao().deleteByOrganization(dbSession, organization.getUuid()); + } + + private void deleteGroups(DbSession dbSession, OrganizationDto organization) { + dbClient.groupDao().deleteByOrganization(dbSession, organization.getUuid()); + } + + private void deleteQualityProfiles(DbSession dbSession, OrganizationDto organization) { + List<QProfileDto> profiles = dbClient.qualityProfileDao().selectOrderedByOrganizationUuid(dbSession, organization); + qProfileFactory.delete(dbSession, profiles); + } + + private void deleteQualityGates(DbSession dbSession, OrganizationDto organization) { + Collection<QualityGateDto> qualityGates = dbClient.qualityGateDao().selectAll(dbSession, organization); + dbClient.qualityGateDao().deleteByUuids(dbSession, qualityGates.stream() + .filter(q -> !q.isBuiltIn()) + .map(QualityGateDto::getUuid) + .collect(MoreCollectors.toList())); + dbClient.qualityGateDao().deleteOrgQualityGatesByOrganization(dbSession, organization); + } + + private void deleteOrganizationAlmBinding(DbSession dbSession, OrganizationDto organization) { + dbClient.organizationAlmBindingDao().deleteByOrganization(dbSession, organization); + } + + private void deleteOrganization(DbSession dbSession, OrganizationDto organization) { + Collection<String> uuids = dbClient.organizationMemberDao().selectUserUuidsByOrganizationUuid(dbSession, organization.getUuid()); + dbClient.organizationMemberDao().deleteByOrganizationUuid(dbSession, organization.getUuid()); + dbClient.organizationDao().deleteByUuid(dbSession, organization.getUuid()); + dbClient.userDao().cleanHomepage(dbSession, organization); + dbClient.webhookDao().selectByOrganizationUuid(dbSession, organization.getUuid()) + .forEach(wh -> dbClient.webhookDeliveryDao().deleteByWebhook(dbSession, wh)); + dbClient.webhookDao().deleteByOrganization(dbSession, organization); + List<UserDto> users = dbClient.userDao().selectByUuids(dbSession, uuids); + userIndexer.commitAndIndex(dbSession, users); + } + + void deleteByQuery(OrganizationQuery query) { + try (DbSession dbSession = dbClient.openSession(false)) { + deleteByQuery(dbSession, query); + } + } + + private void deleteByQuery(DbSession dbSession, OrganizationQuery query) { + while (true) { + int total = dbClient.organizationDao().countByQuery(dbSession, query); + if (total == 0) { + return; + } + + dbClient.organizationDao().selectByQuery(dbSession, query, forPage(1).andSize(PAGE_SIZE)) + .forEach(org -> { + LOGGER.info("deleting organization {} ({})", org.getName(), org.getKey()); + delete(dbSession, org); + }); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationsWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationsWsModule.java index 6c1d1eaa0b0..4d9f5083e65 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationsWsModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/organization/ws/OrganizationsWsModule.java @@ -50,6 +50,7 @@ public class OrganizationsWsModule extends Module { EnableSupportAction.class, AddMemberAction.class, CreateAction.class, + OrganizationDeleter.class, DeleteAction.class, RemoveMemberAction.class, UpdateAction.class); diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/ws/DeleteActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/DeleteActionTest.java index 8b19fa4b1ec..5f801accaad 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/organization/ws/DeleteActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/DeleteActionTest.java @@ -47,9 +47,6 @@ import org.sonar.db.qualitygate.QualityGateDto; import org.sonar.db.qualityprofile.QProfileDto; import org.sonar.db.user.GroupDto; import org.sonar.db.user.UserDto; -import org.sonar.db.webhook.WebhookDbTester; -import org.sonar.db.webhook.WebhookDeliveryDao; -import org.sonar.db.webhook.WebhookDeliveryDbTester; import org.sonar.db.webhook.WebhookDto; import org.sonar.server.component.ComponentCleanerService; import org.sonar.server.es.EsTester; @@ -115,14 +112,10 @@ public class DeleteActionTest { private QProfileFactory qProfileFactory = new QProfileFactoryImpl(dbClient, mock(UuidFactory.class), System2.INSTANCE, mock(ActiveRuleIndexer.class)); private UserIndex userIndex = new UserIndex(es.client(), System2.INSTANCE); private UserIndexer userIndexer = new UserIndexer(dbClient, es.client()); - private final WebhookDbTester webhookDbTester = db.webhooks(); - private final WebhookDeliveryDao deliveryDao = dbClient.webhookDeliveryDao(); - private final WebhookDeliveryDbTester webhookDeliveryDbTester = db.webhookDelivery(); private ProjectLifeCycleListeners projectLifeCycleListeners = mock(ProjectLifeCycleListeners.class); private BillingValidationsProxy billingValidationsProxy = mock(BillingValidationsProxy.class); - private WsActionTester wsTester = new WsActionTester( - new DeleteAction(userSession, dbClient, defaultOrganizationProvider, spiedComponentCleanerService, organizationFlags, userIndexer, qProfileFactory, projectLifeCycleListeners, - billingValidationsProxy)); + private OrganizationDeleter organizationDeleter = new OrganizationDeleter(dbClient, spiedComponentCleanerService, userIndexer, qProfileFactory, projectLifeCycleListeners, billingValidationsProxy); + private WsActionTester wsTester = new WsActionTester(new DeleteAction(userSession, dbClient, defaultOrganizationProvider, organizationFlags, organizationDeleter)); @Test public void definition() { diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/ws/OrganizationDeleterTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/OrganizationDeleterTest.java new file mode 100644 index 00000000000..407e68eb0ae --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/OrganizationDeleterTest.java @@ -0,0 +1,412 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.organization.ws; + +import com.google.common.collect.ImmutableSet; +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Random; +import java.util.Set; +import java.util.stream.IntStream; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.sonar.api.utils.System2; +import org.sonar.core.util.UuidFactoryFast; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.Pagination; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.ComponentTesting; +import org.sonar.db.component.ResourceTypesRule; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.db.organization.OrganizationQuery; +import org.sonar.db.permission.template.PermissionTemplateDto; +import org.sonar.db.qualitygate.QGateWithOrgDto; +import org.sonar.db.qualitygate.QualityGateDto; +import org.sonar.db.qualityprofile.QProfileDto; +import org.sonar.db.user.GroupDto; +import org.sonar.db.user.UserDto; +import org.sonar.db.webhook.WebhookDto; +import org.sonar.server.component.ComponentCleanerService; +import org.sonar.server.es.EsClient; +import org.sonar.server.es.EsTester; +import org.sonar.server.es.ProjectIndexers; +import org.sonar.server.es.SearchOptions; +import org.sonar.server.organization.BillingValidations; +import org.sonar.server.organization.BillingValidationsProxy; +import org.sonar.server.project.Project; +import org.sonar.server.project.ProjectLifeCycleListeners; +import org.sonar.server.qualityprofile.QProfileFactoryImpl; +import org.sonar.server.qualityprofile.index.ActiveRuleIndexer; +import org.sonar.server.user.index.UserIndex; +import org.sonar.server.user.index.UserIndexer; +import org.sonar.server.user.index.UserQuery; + +import static com.google.common.collect.ImmutableList.of; +import static java.util.Arrays.asList; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.sonar.api.resources.Qualifiers.APP; +import static org.sonar.api.resources.Qualifiers.PROJECT; +import static org.sonar.api.resources.Qualifiers.VIEW; +import static org.sonar.core.util.stream.MoreCollectors.toSet; +import static org.sonar.db.user.UserTesting.newUserDto; +import static org.sonar.server.organization.ws.OrganizationDeleter.PAGE_SIZE; + +@RunWith(DataProviderRunner.class) +public class OrganizationDeleterTest { + + @Rule + public final DbTester db = DbTester.create(new System2()).setDisableDefaultOrganization(true); + private final DbClient dbClient = db.getDbClient(); + private final DbSession dbSession = db.getSession(); + + @Rule + public final EsTester es = EsTester.create(); + private final EsClient esClient = es.client(); + + private final ResourceTypesRule resourceTypes = new ResourceTypesRule().setRootQualifiers(PROJECT, VIEW, APP).setAllQualifiers(PROJECT, VIEW, APP); + private final ComponentCleanerService componentCleanerService = spy(new ComponentCleanerService(db.getDbClient(), resourceTypes, mock(ProjectIndexers.class))); + private final UserIndex userIndex = new UserIndex(esClient, System2.INSTANCE); + private final UserIndexer userIndexer = new UserIndexer(dbClient, esClient); + private final ProjectLifeCycleListeners projectLifeCycleListeners = mock(ProjectLifeCycleListeners.class); + private final BillingValidationsProxy billingValidations = mock(BillingValidationsProxy.class); + + private final OrganizationDeleter underTest = new OrganizationDeleter(dbClient, componentCleanerService, userIndexer, + new QProfileFactoryImpl(dbClient, UuidFactoryFast.getInstance(), new System2(), new ActiveRuleIndexer(dbClient, esClient)), + projectLifeCycleListeners, + billingValidations); + + @Test + public void delete_specified_organization() { + OrganizationDto organization = db.organizations().insert(); + + underTest.delete(dbSession, organization); + + verifyOrganizationDoesNotExist(organization); + verify(projectLifeCycleListeners).onProjectsDeleted(emptySet()); + } + + @Test + public void delete_webhooks_of_organization_if_exist() { + OrganizationDto organization = db.organizations().insert(); + db.webhooks().insertWebhook(organization); + ComponentDto project = db.components().insertPrivateProject(organization); + WebhookDto projectWebhook = db.webhooks().insertWebhook(project); + db.webhookDelivery().insert(projectWebhook); + + underTest.delete(dbSession, organization); + + assertThat(db.countRowsOfTable(db.getSession(), "webhooks")).isZero(); + assertThat(db.countRowsOfTable(db.getSession(), "webhook_deliveries")).isZero(); + } + + @Test + public void clear_user_homepage_on_organization_if_exists() { + OrganizationDto organization = db.organizations().insert(); + UserDto user = dbClient.userDao().insert(dbSession, newUserDto().setHomepageType("ORGANIZATION").setHomepageParameter(organization.getUuid())); + dbSession.commit(); + + underTest.delete(dbSession, organization); + + UserDto userReloaded = dbClient.userDao().selectUserById(dbSession, user.getId()); + assertThat(userReloaded.getHomepageType()).isNull(); + assertThat(userReloaded.getHomepageParameter()).isNull(); + } + + @Test + public void clear_project_homepage_on_organization_if_exists() { + OrganizationDto organization = db.organizations().insert(); + ComponentDto project = db.components().insertPrivateProject(organization); + UserDto user = dbClient.userDao().insert(dbSession, + newUserDto().setHomepageType("PROJECT").setHomepageParameter(project.uuid())); + dbSession.commit(); + + underTest.delete(dbSession, organization); + + UserDto userReloaded = dbClient.userDao().selectUserById(dbSession, user.getId()); + assertThat(userReloaded.getHomepageType()).isNull(); + assertThat(userReloaded.getHomepageParameter()).isNull(); + verify(projectLifeCycleListeners).onProjectsDeleted(ImmutableSet.of(Project.from(project))); + } + + @Test + @UseDataProvider("OneOrMoreIterations") + public void delete_components(int numberOfIterations) { + OrganizationDto organization = db.organizations().insert(); + Set<ComponentDto> projects = IntStream.range(0, numberOfIterations).mapToObj(i -> { + ComponentDto project = db.components().insertPrivateProject(organization); + ComponentDto module = db.components().insertComponent(ComponentTesting.newModuleDto(project)); + ComponentDto directory = db.components().insertComponent(ComponentTesting.newDirectory(module, "a/b" + i)); + ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(module, directory)); + ComponentDto branch = db.components().insertProjectBranch(project); + return project; + }).collect(toSet()); + + + underTest.delete(dbSession, organization); + + verifyOrganizationDoesNotExist(organization); + assertThat(db.countRowsOfTable(db.getSession(), "projects")).isZero(); + verify(projectLifeCycleListeners).onProjectsDeleted(projects.stream().map(Project::from).collect(toSet())); + } + + @DataProvider + public static Object[][] OneOrMoreIterations() { + return new Object[][] { + {1}, + {1 + new Random().nextInt(10)}, + }; + } + + @Test + public void delete_branches() { + OrganizationDto organization = db.organizations().insert(); + ComponentDto project = db.components().insertMainBranch(organization); + ComponentDto branch = db.components().insertProjectBranch(project); + + underTest.delete(dbSession, organization); + + verifyOrganizationDoesNotExist(organization); + assertThat(db.countRowsOfTable(db.getSession(), "projects")).isZero(); + assertThat(db.countRowsOfTable(db.getSession(), "project_branches")).isZero(); + verify(projectLifeCycleListeners).onProjectsDeleted(ImmutableSet.of(Project.from(project))); + } + + @Test + public void delete_permissions_templates_and_permissions_and_groups() { + OrganizationDto org = db.organizations().insert(); + OrganizationDto otherOrg = db.organizations().insert(); + + UserDto user1 = db.users().insertUser(); + UserDto user2 = db.users().insertUser(); + GroupDto group1 = db.users().insertGroup(org); + GroupDto group2 = db.users().insertGroup(org); + GroupDto otherGroup1 = db.users().insertGroup(otherOrg); + GroupDto otherGroup2 = db.users().insertGroup(otherOrg); + + ComponentDto projectDto = db.components().insertPublicProject(org); + ComponentDto otherProjectDto = db.components().insertPublicProject(otherOrg); + + db.users().insertPermissionOnAnyone(org, "u1"); + db.users().insertPermissionOnAnyone(otherOrg, "not deleted u1"); + db.users().insertPermissionOnUser(org, user1, "u2"); + db.users().insertPermissionOnUser(otherOrg, user1, "not deleted u2"); + db.users().insertPermissionOnGroup(group1, "u3"); + db.users().insertPermissionOnGroup(otherGroup1, "not deleted u3"); + db.users().insertProjectPermissionOnAnyone("u4", projectDto); + db.users().insertProjectPermissionOnAnyone("not deleted u4", otherProjectDto); + db.users().insertProjectPermissionOnGroup(group1, "u5", projectDto); + db.users().insertProjectPermissionOnGroup(otherGroup1, "not deleted u5", otherProjectDto); + db.users().insertProjectPermissionOnUser(user1, "u6", projectDto); + db.users().insertProjectPermissionOnUser(user1, "not deleted u6", otherProjectDto); + + PermissionTemplateDto templateDto = db.permissionTemplates().insertTemplate(org); + PermissionTemplateDto otherTemplateDto = db.permissionTemplates().insertTemplate(otherOrg); + + underTest.delete(dbSession, org); + + verifyOrganizationDoesNotExist(org); + assertThat(dbClient.groupDao().selectByIds(dbSession, of(group1.getId(), otherGroup1.getId(), group2.getId(), otherGroup2.getId()))) + .extracting(GroupDto::getId) + .containsOnly(otherGroup1.getId(), otherGroup2.getId()); + assertThat(dbClient.permissionTemplateDao().selectByUuid(dbSession, templateDto.getUuid())) + .isNull(); + assertThat(dbClient.permissionTemplateDao().selectByUuid(dbSession, otherTemplateDto.getUuid())) + .isNotNull(); + assertThat(db.select("select role as \"role\" from USER_ROLES")) + .extracting(row -> (String) row.get("role")) + .doesNotContain("u2", "u6") + .contains("not deleted u2", "not deleted u6"); + assertThat(db.select("select role as \"role\" from GROUP_ROLES")) + .extracting(row -> (String) row.get("role")) + .doesNotContain("u1", "u3", "u4", "u5") + .contains("not deleted u1", "not deleted u3", "not deleted u4", "not deleted u5"); + verify(projectLifeCycleListeners).onProjectsDeleted(ImmutableSet.of(Project.from(projectDto))); + } + + @Test + public void delete_members() { + OrganizationDto org = db.organizations().insert(); + OrganizationDto otherOrg = db.organizations().insert(); + UserDto user1 = db.users().insertUser(); + UserDto user2 = db.users().insertUser(); + db.organizations().addMember(org, user1); + db.organizations().addMember(otherOrg, user1); + db.organizations().addMember(org, user2); + userIndexer.commitAndIndex(db.getSession(), asList(user1, user2)); + + underTest.delete(dbSession, org); + + verifyOrganizationDoesNotExist(org); + assertThat(db.getDbClient().organizationMemberDao().select(db.getSession(), org.getUuid(), user1.getId())).isNotPresent(); + assertThat(db.getDbClient().organizationMemberDao().select(db.getSession(), org.getUuid(), user2.getId())).isNotPresent(); + assertThat(db.getDbClient().organizationMemberDao().select(db.getSession(), otherOrg.getUuid(), user1.getId())).isPresent(); + assertThat(userIndex.search(UserQuery.builder().setOrganizationUuid(org.getUuid()).build(), new SearchOptions()).getTotal()).isEqualTo(0); + assertThat(userIndex.search(UserQuery.builder().setOrganizationUuid(otherOrg.getUuid()).build(), new SearchOptions()).getTotal()).isEqualTo(1); + verify(projectLifeCycleListeners).onProjectsDeleted(emptySet()); + } + + @Test + public void delete_quality_profiles() { + OrganizationDto org = db.organizations().insert(); + OrganizationDto otherOrg = db.organizations().insert(); + QProfileDto profileInOrg = db.qualityProfiles().insert(org); + QProfileDto profileInOtherOrg = db.qualityProfiles().insert(otherOrg); + + underTest.delete(dbSession, org); + + verifyOrganizationDoesNotExist(org); + assertThat(db.select("select uuid as \"profileKey\" from org_qprofiles")) + .extracting(row -> (String) row.get("profileKey")) + .containsOnly(profileInOtherOrg.getKee()); + } + + @Test + public void delete_quality_gates() { + QualityGateDto builtInQualityGate = db.qualityGates().insertBuiltInQualityGate(); + OrganizationDto organization = db.organizations().insert(); + db.qualityGates().associateQualityGateToOrganization(builtInQualityGate, organization); + OrganizationDto otherOrganization = db.organizations().insert(); + db.qualityGates().associateQualityGateToOrganization(builtInQualityGate, otherOrganization); + QGateWithOrgDto qualityGate = db.qualityGates().insertQualityGate(organization); + QGateWithOrgDto qualityGateInOtherOrg = db.qualityGates().insertQualityGate(otherOrganization); + + underTest.delete(dbSession, organization); + + verifyOrganizationDoesNotExist(organization); + assertThat(db.select("select uuid as \"uuid\" from quality_gates")) + .extracting(row -> (String) row.get("uuid")) + .containsExactlyInAnyOrder(qualityGateInOtherOrg.getUuid(), builtInQualityGate.getUuid()); + assertThat(db.select("select organization_uuid as \"organizationUuid\" from org_quality_gates")) + .extracting(row -> (String) row.get("organizationUuid")) + .containsOnly(otherOrganization.getUuid()); + + // Check built-in quality gate is still available in other organization + assertThat(db.getDbClient().qualityGateDao().selectByOrganizationAndName(db.getSession(), otherOrganization, "Sonar way")).isNotNull(); + verify(projectLifeCycleListeners).onProjectsDeleted(emptySet()); + } + + @Test + @UseDataProvider("indexOfFailingProjectDeletion") + public void projectLifeCycleListener_are_notified_even_if_deletion_of_a_project_throws_an_Exception(int failingProjectIndex) { + OrganizationDto organization = db.organizations().insert(); + ComponentDto[] projects = new ComponentDto[] { + db.components().insertPrivateProject(organization), + db.components().insertPrivateProject(organization), + db.components().insertPrivateProject(organization) + }; + RuntimeException expectedException = new RuntimeException("Faking deletion of 2nd project throwing an exception"); + doThrow(expectedException) + .when(componentCleanerService).delete(any(), eq(projects[failingProjectIndex])); + + try { + underTest.delete(dbSession, organization); + fail("A RuntimeException should have been thrown"); + } catch (RuntimeException e) { + assertThat(e).isSameAs(expectedException); + verify(projectLifeCycleListeners).onProjectsDeleted(Arrays.stream(projects).map(Project::from).collect(toSet())); + } + } + + @Test + public void call_billing_validation_on_delete() { + OrganizationDto organization = db.organizations().insert(); + + underTest.delete(dbSession, organization); + + verify(billingValidations).onDelete(any(BillingValidations.Organization.class)); + } + + @Test + public void delete_organization_alm_binding() { + OrganizationDto organization = db.organizations().insert(); + db.alm().insertOrganizationAlmBinding(organization, db.alm().insertAlmAppInstall(), true); + + underTest.delete(dbSession, organization); + + assertThat(db.getDbClient().organizationAlmBindingDao().selectByOrganization(db.getSession(), organization)).isNotPresent(); + } + + @DataProvider + public static Object[][] indexOfFailingProjectDeletion() { + return new Object[][] { + {0}, + {1}, + {2} + }; + } + + + @Test + @UseDataProvider("queriesAndUnmatchedOrganizationKeys") + public void delete_organizations_matched_by_query(OrganizationQuery query, Collection<String> unmatchedOrgKeys) { + db.organizations().insert(o -> o.setKey("org1")); + db.organizations().insert(o -> o.setKey("org2")); + db.organizations().insert(o -> o.setKey("org3")); + + underTest.deleteByQuery(query); + + assertThat(dbClient.organizationDao().selectByQuery(db.getSession(), OrganizationQuery.returnAll(), Pagination.all())) + .extracting(OrganizationDto::getKey) + .containsExactlyInAnyOrderElementsOf(unmatchedOrgKeys); + } + + @DataProvider + public static Object[][] queriesAndUnmatchedOrganizationKeys() { + return new Object[][] { + {OrganizationQuery.returnAll(), Collections.emptyList()}, + {OrganizationQuery.newOrganizationQueryBuilder().setKeys(singleton("nonexistent")).build(), Arrays.asList("org1", "org2", "org3")}, + {OrganizationQuery.newOrganizationQueryBuilder().setKeys(singleton("org1")).build(), Arrays.asList("org2", "org3")}, + }; + } + + @Test + public void delete_organizations_for_all_query_pages() { + int orgsCountGreaterThanPageSize = PAGE_SIZE + 1; + + IntStream.range(0, orgsCountGreaterThanPageSize).forEach(ignored -> db.organizations().insert()); + + OrganizationQuery query = OrganizationQuery.returnAll(); + assertThat(dbClient.organizationDao().countByQuery(db.getSession(), query)).isEqualTo(orgsCountGreaterThanPageSize); + + underTest.deleteByQuery(query); + + assertThat(dbClient.organizationDao().countByQuery(db.getSession(), query)).isZero(); + } + + private void verifyOrganizationDoesNotExist(OrganizationDto organization) { + assertThat(db.getDbClient().organizationDao().selectByKey(dbSession, organization.getKey())).isEmpty(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/ws/OrganizationsWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/OrganizationsWsModuleTest.java index bc041d334eb..d6023ac7374 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/organization/ws/OrganizationsWsModuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/OrganizationsWsModuleTest.java @@ -49,7 +49,7 @@ public class OrganizationsWsModuleTest { underTest.configure(container); assertThat(container.getPicoContainer().getComponentAdapters()) - .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 12); + .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 13); } } |