/* * SonarQube * Copyright (C) 2009-2017 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.db.purge; import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.apache.commons.lang.math.RandomUtils; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.sonar.api.resources.Scopes; import org.sonar.api.utils.System2; import org.sonar.core.util.Uuids; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.DbTester; import org.sonar.db.ce.CeActivityDto; import org.sonar.db.ce.CeQueueDto; import org.sonar.db.ce.CeQueueDto.Status; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentTesting; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.sonar.db.ce.CeTaskTypes.REPORT; import static org.sonar.db.webhook.WebhookDbTesting.newWebhookDeliveryDto; import static org.sonar.db.webhook.WebhookDbTesting.selectAllDeliveryUuids; public class PurgeDaoTest { private static final String THE_PROJECT_UUID = "P1"; private static final long THE_PROJECT_ID = 1L; private System2 system2 = mock(System2.class); @Rule public DbTester dbTester = DbTester.create(system2); private DbClient dbClient = dbTester.getDbClient(); private DbSession dbSession = dbTester.getSession(); private PurgeDao underTest = dbTester.getDbClient().purgeDao(); @Test public void shouldDeleteAbortedBuilds() { dbTester.prepareDbUnit(getClass(), "shouldDeleteAbortedBuilds.xml"); underTest.purge(dbSession, newConfigurationWith30Days(), PurgeListener.EMPTY, new PurgeProfiler()); dbSession.commit(); dbTester.assertDbUnit(getClass(), "shouldDeleteAbortedBuilds-result.xml", "snapshots"); } @Test public void should_purge_project() { dbTester.prepareDbUnit(getClass(), "shouldPurgeProject.xml"); underTest.purge(dbSession, newConfigurationWith30Days(), PurgeListener.EMPTY, new PurgeProfiler()); dbSession.commit(); dbTester.assertDbUnit(getClass(), "shouldPurgeProject-result.xml", "projects", "snapshots"); } @Test public void shouldDeleteHistoricalDataOfDirectoriesAndFiles() { dbTester.prepareDbUnit(getClass(), "shouldDeleteHistoricalDataOfDirectoriesAndFiles.xml"); PurgeConfiguration conf = new PurgeConfiguration( new IdUuidPair(THE_PROJECT_ID, "ABCD"), new String[] {Scopes.DIRECTORY, Scopes.FILE}, 30, System2.INSTANCE, Collections.emptyList()); underTest.purge(dbSession, conf, PurgeListener.EMPTY, new PurgeProfiler()); dbSession.commit(); dbTester.assertDbUnit(getClass(), "shouldDeleteHistoricalDataOfDirectoriesAndFiles-result.xml", "projects", "snapshots"); } @Test public void close_issues_clean_index_and_file_sources_of_disabled_components_specified_by_uuid_in_configuration() { dbTester.prepareDbUnit(getClass(), "close_issues_clean_index_and_files_sources_of_specified_components.xml"); when(system2.now()).thenReturn(1450000000000L); underTest.purge(dbSession, newConfigurationWith30Days(system2, "P1", "EFGH", "GHIJ"), PurgeListener.EMPTY, new PurgeProfiler()); dbSession.commit(); dbTester.assertDbUnit(getClass(), "close_issues_clean_index_and_files_sources_of_specified_components-result.xml", new String[] {"issue_close_date", "issue_update_date"}, "projects", "snapshots", "issues"); } @Test public void shouldDeleteAnalyses() { dbTester.prepareDbUnit(getClass(), "shouldDeleteAnalyses.xml"); underTest.deleteAnalyses(dbSession, new PurgeProfiler(), ImmutableList.of(new IdUuidPair(3, "u3"))); dbTester.assertDbUnit(getClass(), "shouldDeleteAnalyses-result.xml", "snapshots"); } @Test public void shouldSelectPurgeableAnalysis() { dbTester.prepareDbUnit(getClass(), "shouldSelectPurgeableAnalysis.xml"); List<PurgeableAnalysisDto> analyses = underTest.selectPurgeableAnalyses(THE_PROJECT_UUID, dbSession); assertThat(analyses).hasSize(3); assertThat(getById(analyses, "u1").isLast()).isTrue(); assertThat(getById(analyses, "u1").hasEvents()).isFalse(); assertThat(getById(analyses, "u4").isLast()).isFalse(); assertThat(getById(analyses, "u4").hasEvents()).isFalse(); assertThat(getById(analyses, "u5").isLast()).isFalse(); assertThat(getById(analyses, "u5").hasEvents()).isTrue(); } @Test public void delete_project_and_associated_data() { dbTester.prepareDbUnit(getClass(), "shouldDeleteProject.xml"); underTest.deleteProject(dbSession, "A"); dbSession.commit(); assertThat(dbTester.countRowsOfTable("projects")).isZero(); assertThat(dbTester.countRowsOfTable("snapshots")).isZero(); assertThat(dbTester.countRowsOfTable("issues")).isZero(); assertThat(dbTester.countRowsOfTable("issue_changes")).isZero(); assertThat(dbTester.countRowsOfTable("file_sources")).isZero(); } @Test public void delete_project_in_ce_activity_when_deleting_project() { ComponentDto projectToBeDeleted = ComponentTesting.newPrivateProjectDto(dbTester.getDefaultOrganization()); ComponentDto anotherLivingProject = ComponentTesting.newPrivateProjectDto(dbTester.getDefaultOrganization()); dbClient.componentDao().insert(dbSession, projectToBeDeleted, anotherLivingProject); // Insert 2 rows in CE_ACTIVITY : one for the project that will be deleted, and on on another project insertCeActivity(projectToBeDeleted); insertCeActivity(anotherLivingProject); dbSession.commit(); underTest.deleteProject(dbSession, projectToBeDeleted.uuid()); dbSession.commit(); assertThat(dbTester.countRowsOfTable("ce_activity")).isEqualTo(1); } @Test public void delete_tasks_in_ce_queue_when_deleting_project() { ComponentDto projectToBeDeleted = dbTester.components().insertPrivateProject(); ComponentDto anotherLivingProject = dbTester.components().insertPrivateProject(); // Insert 3 rows in CE_QUEUE: two for the project that will be deleted (in order to check that status // is not involved in deletion), and one on another project dbClient.ceQueueDao().insert(dbSession, createCeQueue(projectToBeDeleted, Status.PENDING)); dbClient.ceQueueDao().insert(dbSession, createCeQueue(projectToBeDeleted, Status.IN_PROGRESS)); dbClient.ceQueueDao().insert(dbSession, createCeQueue(anotherLivingProject, Status.PENDING)); dbSession.commit(); underTest.deleteProject(dbSession, projectToBeDeleted.uuid()); dbSession.commit(); assertThat(dbTester.countRowsOfTable("ce_queue")).isEqualTo(1); assertThat(dbTester.countSql("select count(*) from ce_queue where component_uuid='" + projectToBeDeleted.uuid() + "'")).isEqualTo(0); } @Test public void delete_view_and_child() { dbTester.prepareDbUnit(getClass(), "view_sub_view_and_tech_project.xml"); underTest.deleteProject(dbSession, "A"); dbSession.commit(); assertThat(dbTester.countSql("select count(1) from projects where uuid='A'")).isZero(); assertThat(dbTester.countRowsOfTable("projects")).isZero(); } @Test public void delete_view_sub_view_and_tech_project() { dbTester.prepareDbUnit(getClass(), "view_sub_view_and_tech_project.xml"); // technical project underTest.deleteProject(dbSession, "D"); dbSession.commit(); assertThat(dbTester.countSql("select count(1) from projects where uuid='D'")).isZero(); // sub view underTest.deleteProject(dbSession, "B"); dbSession.commit(); assertThat(dbTester.countSql("select count(1) from projects where uuid='B'")).isZero(); // view underTest.deleteProject(dbSession, "A"); dbSession.commit(); assertThat(dbTester.countSql("select count(1) from projects where uuid='A'")).isZero(); } @Test public void should_delete_old_closed_issues() { PurgeListener purgeListener = mock(PurgeListener.class); dbTester.prepareDbUnit(getClass(), "should_delete_old_closed_issues.xml"); underTest.purge(dbSession, newConfigurationWith30Days(), purgeListener, new PurgeProfiler()); dbSession.commit(); dbTester.assertDbUnit(getClass(), "should_delete_old_closed_issues-result.xml", "issues", "issue_changes"); Class<ArrayList<String>> listClass = (Class<ArrayList<String>>) (Class) ArrayList.class; ArgumentCaptor<ArrayList<String>> issueKeys = ArgumentCaptor.forClass(listClass); ArgumentCaptor<String> projectUuid = ArgumentCaptor.forClass(String.class); verify(purgeListener).onIssuesRemoval(projectUuid.capture(), issueKeys.capture()); assertThat(projectUuid.getValue()).isEqualTo(THE_PROJECT_UUID); assertThat(issueKeys.getValue()).containsOnly("ISSUE-1", "ISSUE-2"); } @Test public void should_delete_all_closed_issues() { dbTester.prepareDbUnit(getClass(), "should_delete_all_closed_issues.xml"); PurgeConfiguration conf = new PurgeConfiguration(new IdUuidPair(THE_PROJECT_ID, "1"), new String[0], 0, System2.INSTANCE, Collections.emptyList()); underTest.purge(dbSession, conf, PurgeListener.EMPTY, new PurgeProfiler()); dbSession.commit(); dbTester.assertDbUnit(getClass(), "should_delete_all_closed_issues-result.xml", "issues", "issue_changes"); } @Test public void deleteProject_deletes_webhook_deliveries() { dbClient.webhookDeliveryDao().insert(dbSession, newWebhookDeliveryDto().setComponentUuid("P1").setUuid("D1")); dbClient.webhookDeliveryDao().insert(dbSession, newWebhookDeliveryDto().setComponentUuid("P2").setUuid("D2")); underTest.deleteProject(dbSession, "P1"); assertThat(selectAllDeliveryUuids(dbTester, dbSession)).containsOnly("D2"); } private CeQueueDto createCeQueue(ComponentDto component, Status status) { CeQueueDto queueDto = new CeQueueDto(); queueDto.setUuid(Uuids.create()); queueDto.setTaskType(REPORT); queueDto.setComponentUuid(component.uuid()); queueDto.setSubmitterLogin("henri"); queueDto.setCreatedAt(1_300_000_000_000L); queueDto.setStatus(status); return queueDto; } private CeActivityDto insertCeActivity(ComponentDto component) { Status unusedStatus = Status.values()[RandomUtils.nextInt(Status.values().length)]; CeQueueDto queueDto = createCeQueue(component, unusedStatus); CeActivityDto dto = new CeActivityDto(queueDto); dto.setStatus(CeActivityDto.Status.SUCCESS); dto.setStartedAt(1_500_000_000_000L); dto.setExecutedAt(1_500_000_000_500L); dto.setExecutionTimeMs(500L); dbClient.ceActivityDao().insert(dbSession, dto); return dto; } private static PurgeableAnalysisDto getById(List<PurgeableAnalysisDto> snapshots, String uuid) { return snapshots.stream() .filter(snapshot -> uuid.equals(snapshot.getAnalysisUuid())) .findFirst() .orElse(null); } private static PurgeConfiguration newConfigurationWith30Days() { return new PurgeConfiguration(new IdUuidPair(THE_PROJECT_ID, THE_PROJECT_UUID), new String[0], 30, System2.INSTANCE, Collections.emptyList()); } private static PurgeConfiguration newConfigurationWith30Days(System2 system2, String... disabledComponentUuids) { return new PurgeConfiguration(new IdUuidPair(THE_PROJECT_ID, THE_PROJECT_UUID), new String[0], 30, system2, Arrays.asList(disabledComponentUuids)); } }