3 * Copyright (C) 2009-2020 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.server.organization.ws;
22 import com.google.common.collect.ImmutableSet;
23 import com.tngtech.java.junit.dataprovider.DataProvider;
24 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
25 import com.tngtech.java.junit.dataprovider.UseDataProvider;
26 import java.util.Arrays;
27 import java.util.Collection;
28 import java.util.Collections;
29 import java.util.Random;
31 import java.util.stream.IntStream;
32 import org.junit.Rule;
33 import org.junit.Test;
34 import org.junit.rules.ExpectedException;
35 import org.junit.runner.RunWith;
36 import org.sonar.api.resources.ResourceTypes;
37 import org.sonar.api.utils.System2;
38 import org.sonar.core.util.UuidFactoryFast;
39 import org.sonar.db.DbClient;
40 import org.sonar.db.DbSession;
41 import org.sonar.db.DbTester;
42 import org.sonar.db.Pagination;
43 import org.sonar.db.component.ComponentDto;
44 import org.sonar.db.component.ComponentTesting;
45 import org.sonar.db.organization.OrganizationDto;
46 import org.sonar.db.organization.OrganizationQuery;
47 import org.sonar.db.permission.template.PermissionTemplateDto;
48 import org.sonar.db.project.ProjectDto;
49 import org.sonar.db.qualitygate.QGateWithOrgDto;
50 import org.sonar.db.qualitygate.QualityGateDto;
51 import org.sonar.db.user.GroupDto;
52 import org.sonar.db.user.UserDto;
53 import org.sonar.db.webhook.WebhookDto;
54 import org.sonar.server.component.ComponentCleanerService;
55 import org.sonar.server.es.EsClient;
56 import org.sonar.server.es.EsTester;
57 import org.sonar.server.es.ProjectIndexers;
58 import org.sonar.server.es.SearchOptions;
59 import org.sonar.server.organization.BillingValidations;
60 import org.sonar.server.organization.BillingValidationsProxy;
61 import org.sonar.server.project.Project;
62 import org.sonar.server.project.ProjectLifeCycleListeners;
63 import org.sonar.server.qualityprofile.QProfileFactoryImpl;
64 import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
65 import org.sonar.server.user.index.UserIndex;
66 import org.sonar.server.user.index.UserIndexer;
67 import org.sonar.server.user.index.UserQuery;
69 import static com.google.common.collect.ImmutableList.of;
70 import static java.util.Arrays.asList;
71 import static java.util.Collections.emptySet;
72 import static java.util.Collections.singleton;
73 import static org.assertj.core.api.Assertions.assertThat;
74 import static org.mockito.ArgumentMatchers.any;
75 import static org.mockito.ArgumentMatchers.anyList;
76 import static org.mockito.Mockito.doThrow;
77 import static org.mockito.Mockito.mock;
78 import static org.mockito.Mockito.spy;
79 import static org.mockito.Mockito.verify;
80 import static org.sonar.core.util.stream.MoreCollectors.toSet;
81 import static org.sonar.db.user.UserTesting.newUserDto;
82 import static org.sonar.server.organization.ws.OrganizationDeleter.PAGE_SIZE;
84 @RunWith(DataProviderRunner.class)
85 public class OrganizationDeleterTest {
88 public final DbTester db = DbTester.create(new System2()).setDisableDefaultOrganization(true);
89 private final DbClient dbClient = db.getDbClient();
90 private final DbSession dbSession = db.getSession();
93 public ExpectedException exception = ExpectedException.none();
96 public final EsTester es = EsTester.create();
97 private final EsClient esClient = es.client();
98 private ResourceTypes mockResourceTypes = mock(ResourceTypes.class);
99 private final ComponentCleanerService componentCleanerService = spy(new ComponentCleanerService(db.getDbClient(), mockResourceTypes, mock(ProjectIndexers.class)));
100 private final UserIndex userIndex = new UserIndex(esClient, System2.INSTANCE);
101 private final UserIndexer userIndexer = new UserIndexer(dbClient, esClient);
102 private final ProjectLifeCycleListeners projectLifeCycleListeners = mock(ProjectLifeCycleListeners.class);
103 private final BillingValidationsProxy billingValidations = mock(BillingValidationsProxy.class);
105 private final OrganizationDeleter underTest = new OrganizationDeleter(dbClient, componentCleanerService, userIndexer,
106 new QProfileFactoryImpl(dbClient, UuidFactoryFast.getInstance(), new System2(), new ActiveRuleIndexer(dbClient, esClient)), projectLifeCycleListeners, billingValidations);
109 public void delete_specified_organization() {
110 OrganizationDto organization = db.organizations().insert();
112 underTest.delete(dbSession, organization);
114 verifyOrganizationDoesNotExist(organization);
115 verify(projectLifeCycleListeners).onProjectsDeleted(emptySet());
119 public void delete_webhooks_of_organization_if_exist() {
120 OrganizationDto organization = db.organizations().insert();
121 db.webhooks().insertWebhook(organization);
122 ProjectDto project = db.components().insertPrivateProjectDto(organization);
123 WebhookDto projectWebhook = db.webhooks().insertWebhook(project);
124 db.webhookDelivery().insert(projectWebhook);
126 underTest.delete(dbSession, organization);
128 assertThat(db.countRowsOfTable(db.getSession(), "webhooks")).isZero();
129 assertThat(db.countRowsOfTable(db.getSession(), "webhook_deliveries")).isZero();
133 public void clear_user_homepage_on_organization_if_exists() {
134 OrganizationDto organization = db.organizations().insert();
135 UserDto user = dbClient.userDao().insert(dbSession, newUserDto().setHomepageType("ORGANIZATION").setHomepageParameter(organization.getUuid()));
138 underTest.delete(dbSession, organization);
140 UserDto userReloaded = dbClient.userDao().selectByUuid(dbSession, user.getUuid());
141 assertThat(userReloaded.getHomepageType()).isNull();
142 assertThat(userReloaded.getHomepageParameter()).isNull();
146 public void clear_project_homepage_on_organization_if_exists() {
147 OrganizationDto organization = db.organizations().insert();
148 ComponentDto project = db.components().insertPrivateProject(organization);
149 UserDto user = dbClient.userDao().insert(dbSession,
150 newUserDto().setHomepageType("PROJECT").setHomepageParameter(project.uuid()));
153 underTest.delete(dbSession, organization);
155 UserDto userReloaded = dbClient.userDao().selectByUuid(dbSession, user.getUuid());
156 assertThat(userReloaded.getHomepageType()).isNull();
157 assertThat(userReloaded.getHomepageParameter()).isNull();
158 verify(projectLifeCycleListeners).onProjectsDeleted(ImmutableSet.of(Project.from(project)));
162 @UseDataProvider("OneOrMoreIterations")
163 public void delete_components(int numberOfIterations) {
164 OrganizationDto organization = db.organizations().insert();
165 Set<ComponentDto> projects = IntStream.range(0, numberOfIterations).mapToObj(i -> {
166 ComponentDto project = db.components().insertPrivateProject(organization);
167 ComponentDto module = db.components().insertComponent(ComponentTesting.newModuleDto(project));
168 ComponentDto directory = db.components().insertComponent(ComponentTesting.newDirectory(module, "a/b" + i));
169 ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(module, directory));
170 ComponentDto branch = db.components().insertProjectBranch(project);
174 underTest.delete(dbSession, organization);
176 verifyOrganizationDoesNotExist(organization);
177 assertThat(db.countRowsOfTable(db.getSession(), "projects")).isZero();
178 verify(projectLifeCycleListeners).onProjectsDeleted(projects.stream().map(Project::from).collect(toSet()));
182 public static Object[][] OneOrMoreIterations() {
183 return new Object[][] {
185 {1 + new Random().nextInt(10)},
190 public void delete_branches() {
191 OrganizationDto organization = db.organizations().insert();
192 ComponentDto project = db.components().insertPublicProject(organization);
193 ComponentDto branch = db.components().insertProjectBranch(project);
195 underTest.delete(dbSession, organization);
197 verifyOrganizationDoesNotExist(organization);
198 assertThat(db.countRowsOfTable(db.getSession(), "projects")).isZero();
199 assertThat(db.countRowsOfTable(db.getSession(), "project_branches")).isZero();
200 verify(projectLifeCycleListeners).onProjectsDeleted(ImmutableSet.of(Project.from(project)));
204 public void delete_members() {
205 OrganizationDto org = db.organizations().insert();
206 OrganizationDto otherOrg = db.organizations().insert();
207 UserDto user1 = db.users().insertUser();
208 UserDto user2 = db.users().insertUser();
209 db.organizations().addMember(org, user1);
210 db.organizations().addMember(otherOrg, user1);
211 db.organizations().addMember(org, user2);
212 userIndexer.commitAndIndex(db.getSession(), asList(user1, user2));
214 underTest.delete(dbSession, org);
216 verifyOrganizationDoesNotExist(org);
217 assertThat(db.getDbClient().organizationMemberDao().select(db.getSession(), org.getUuid(), user1.getUuid())).isNotPresent();
218 assertThat(db.getDbClient().organizationMemberDao().select(db.getSession(), org.getUuid(), user2.getUuid())).isNotPresent();
219 assertThat(db.getDbClient().organizationMemberDao().select(db.getSession(), otherOrg.getUuid(), user1.getUuid())).isPresent();
220 assertThat(userIndex.search(UserQuery.builder().setOrganizationUuid(org.getUuid()).build(), new SearchOptions()).getTotal()).isEqualTo(0);
221 assertThat(userIndex.search(UserQuery.builder().setOrganizationUuid(otherOrg.getUuid()).build(), new SearchOptions()).getTotal()).isEqualTo(1);
222 verify(projectLifeCycleListeners).onProjectsDeleted(emptySet());
226 public void delete_quality_gates() {
227 QualityGateDto builtInQualityGate = db.qualityGates().insertBuiltInQualityGate();
228 OrganizationDto organization = db.organizations().insert();
229 db.qualityGates().associateQualityGateToOrganization(builtInQualityGate, organization);
230 OrganizationDto otherOrganization = db.organizations().insert();
231 db.qualityGates().associateQualityGateToOrganization(builtInQualityGate, otherOrganization);
232 QGateWithOrgDto qualityGate = db.qualityGates().insertQualityGate(organization);
233 QGateWithOrgDto qualityGateInOtherOrg = db.qualityGates().insertQualityGate(otherOrganization);
235 underTest.delete(dbSession, organization);
237 verifyOrganizationDoesNotExist(organization);
238 assertThat(db.select("select uuid as \"uuid\" from quality_gates"))
239 .extracting(row -> (String) row.get("uuid"))
240 .containsExactlyInAnyOrder(qualityGateInOtherOrg.getUuid(), builtInQualityGate.getUuid());
241 assertThat(db.select("select organization_uuid as \"organizationUuid\" from org_quality_gates"))
242 .extracting(row -> (String) row.get("organizationUuid"))
243 .containsOnly(otherOrganization.getUuid());
245 // Check built-in quality gate is still available in other organization
246 assertThat(db.getDbClient().qualityGateDao().selectByOrganizationAndName(db.getSession(), otherOrganization, "Sonar way")).isNotNull();
247 verify(projectLifeCycleListeners).onProjectsDeleted(emptySet());
251 public void projectLifeCycleListener_are_notified_even_if_deletion_of_a_project_throws_an_Exception() {
252 OrganizationDto organization = db.organizations().insert();
253 ComponentDto[] components = new ComponentDto[] {
254 db.components().insertPublicProject(organization),
255 db.components().insertPublicProject(organization),
256 db.components().insertPublicProject(organization)
258 ProjectDto[] projects = Arrays.stream(components).map(c -> dbClient.projectDao().selectByUuid(dbSession, c.uuid()).get()).toArray(ProjectDto[]::new);
260 RuntimeException expectedException = new RuntimeException("Faking deletion of 2nd project throwing an exception");
261 doThrow(expectedException).when(componentCleanerService).delete(any(), anyList());
262 exception.expect(RuntimeException.class);
263 exception.expectMessage(expectedException.getMessage());
265 underTest.delete(dbSession, organization);
267 verify(projectLifeCycleListeners).onProjectsDeleted(Arrays.stream(projects).map(Project::from).collect(toSet()));
271 public void call_billing_validation_on_delete() {
272 OrganizationDto organization = db.organizations().insert();
274 underTest.delete(dbSession, organization);
276 verify(billingValidations).onDelete(any(BillingValidations.Organization.class));
280 public void delete_organization_alm_binding() {
281 OrganizationDto organization = db.organizations().insert();
282 db.alm().insertOrganizationAlmBinding(organization, db.alm().insertAlmAppInstall(), true);
284 underTest.delete(dbSession, organization);
286 assertThat(db.getDbClient().organizationAlmBindingDao().selectByOrganization(db.getSession(), organization)).isNotPresent();
290 @UseDataProvider("queriesAndUnmatchedOrganizationKeys")
291 public void delete_organizations_matched_by_query(OrganizationQuery query, Collection<String> unmatchedOrgKeys) {
292 db.organizations().insert(o -> o.setKey("org1"));
293 db.organizations().insert(o -> o.setKey("org2"));
294 db.organizations().insert(o -> o.setKey("org3"));
296 underTest.deleteByQuery(query);
298 assertThat(dbClient.organizationDao().selectByQuery(db.getSession(), OrganizationQuery.returnAll(), Pagination.all()))
299 .extracting(OrganizationDto::getKey)
300 .containsExactlyInAnyOrderElementsOf(unmatchedOrgKeys);
304 public static Object[][] queriesAndUnmatchedOrganizationKeys() {
305 return new Object[][] {
306 {OrganizationQuery.returnAll(), Collections.emptyList()},
307 {OrganizationQuery.newOrganizationQueryBuilder().setKeys(singleton("nonexistent")).build(), Arrays.asList("org1", "org2", "org3")},
308 {OrganizationQuery.newOrganizationQueryBuilder().setKeys(singleton("org1")).build(), Arrays.asList("org2", "org3")},
313 public void delete_organizations_for_all_query_pages() {
314 int orgsCountGreaterThanPageSize = PAGE_SIZE + 1;
316 IntStream.range(0, orgsCountGreaterThanPageSize).forEach(ignored -> db.organizations().insert());
318 OrganizationQuery query = OrganizationQuery.returnAll();
319 assertThat(dbClient.organizationDao().countByQuery(db.getSession(), query)).isEqualTo(orgsCountGreaterThanPageSize);
321 underTest.deleteByQuery(query);
323 assertThat(dbClient.organizationDao().countByQuery(db.getSession(), query)).isZero();
326 private void verifyOrganizationDoesNotExist(OrganizationDto organization) {
327 assertThat(db.getDbClient().organizationDao().selectByKey(dbSession, organization.getKey())).isEmpty();