]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18679 Move remaining ITs
authorLéo Geoffroy <leo.geoffroy@sonarsource.com>
Tue, 21 Mar 2023 14:39:42 +0000 (15:39 +0100)
committersonartech <sonartech@sonarsource.com>
Tue, 21 Mar 2023 20:02:50 +0000 (20:02 +0000)
88 files changed:
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/component/BranchPersisterImplIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/filemove/PullRequestFileMoveDetectionStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/metric/MetricRepositoryImplIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/scm/ScmInfoDbLoaderIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/LoadCrossProjectDuplicationsRepositoryStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisPropertiesStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistCrossProjectDuplicationIndexStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistEventsStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/taskprocessor/IgnoreOrphanBranchStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/component/ExportComponentsStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/ExportMeasuresStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1235_add_component_uuid_to_duplications_index.rb [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1236_populate_component_uuid_of_duplications_index.rb [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1237_delete_orphan_duplications_index_rows_without_component.rb [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1238_make_component_uuid_not_null_on_duplications_index.rb [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1239_add_analysis_uuid_to_duplications_index.rb [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1240_populate_analysis_uuid_of_duplications_index.rb [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1241_delete_orphan_duplications_index_rows_without_analysis.rb [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1242_make_analysis_uuid_not_null_on_duplications_index.rb [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/AddAnalysisUuidColumnToDuplicationsIndex.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/AddComponentUuidColumnToDuplicationsIndex.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/DeleteOrphanDuplicationsIndexRowsWithoutComponent.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/MakeComponentUuidNotNullOnDuplicationsIndex.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/1235_add_component_uuid_and_analysis_uuid_to_duplications_index.rb [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/1236_populate_component_uuid_and_analysis_uuid_of_duplications_index.rb [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/1237_delete_orphan_duplications_index_rows_without_component_or_analysis.rb [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/1238_make_component_uuid_and_analysis_uuid_not_null_on_duplications_index.rb [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/BranchPersisterImplTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/filemove/PullRequestFileMoveDetectionStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/metric/MetricRepositoryImplTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/scm/ScmInfoDbLoaderTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadCrossProjectDuplicationsRepositoryStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisPropertiesStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistCrossProjectDuplicationIndexStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistEventsStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/taskprocessor/IgnoreOrphanBranchStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/component/ExportComponentsStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/ExportMeasuresStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1235_add_component_uuid_to_duplications_index.rb [deleted file]
server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1236_populate_component_uuid_of_duplications_index.rb [deleted file]
server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1237_delete_orphan_duplications_index_rows_without_component.rb [deleted file]
server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1238_make_component_uuid_not_null_on_duplications_index.rb [deleted file]
server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1239_add_analysis_uuid_to_duplications_index.rb [deleted file]
server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1240_populate_analysis_uuid_of_duplications_index.rb [deleted file]
server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1241_delete_orphan_duplications_index_rows_without_analysis.rb [deleted file]
server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1242_make_analysis_uuid_not_null_on_duplications_index.rb [deleted file]
server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/AddAnalysisUuidColumnToDuplicationsIndex.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/AddComponentUuidColumnToDuplicationsIndex.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/DeleteOrphanDuplicationsIndexRowsWithoutComponent.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/MakeComponentUuidNotNullOnDuplicationsIndex.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/1235_add_component_uuid_and_analysis_uuid_to_duplications_index.rb [deleted file]
server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/1236_populate_component_uuid_and_analysis_uuid_of_duplications_index.rb [deleted file]
server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/1237_delete_orphan_duplications_index_rows_without_component_or_analysis.rb [deleted file]
server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/1238_make_component_uuid_and_analysis_uuid_not_null_on_duplications_index.rb [deleted file]
server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex.java [deleted file]
server/sonar-webserver-api/src/it/java/org/sonar/server/plugins/DetectPluginChangeIT.java [new file with mode: 0644]
server/sonar-webserver-api/src/it/java/org/sonar/server/rule/CachingRuleFinderIT.java [new file with mode: 0644]
server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/DetectPluginChangeTest.java [deleted file]
server/sonar-webserver-api/src/test/java/org/sonar/server/rule/CachingRuleFinderTest.java [deleted file]
server/sonar-webserver-core/src/it/java/org/sonar/server/platform/PersistentSettingsIT.java [new file with mode: 0644]
server/sonar-webserver-core/src/it/java/org/sonar/server/platform/StartupMetadataPersisterIT.java [new file with mode: 0644]
server/sonar-webserver-core/src/it/java/org/sonar/server/platform/serverid/ServerIdManagerIT.java [new file with mode: 0644]
server/sonar-webserver-core/src/it/java/org/sonar/server/startup/RegisterMetricsIT.java [new file with mode: 0644]
server/sonar-webserver-core/src/it/java/org/sonar/server/startup/RegisterPluginsIT.java [new file with mode: 0644]
server/sonar-webserver-core/src/it/java/org/sonar/server/startup/UpgradeSuggestionsCleanerIT.java [new file with mode: 0644]
server/sonar-webserver-core/src/it/java/org/sonar/server/webhook/WebhookQGChangeEventListenerIT.java [new file with mode: 0644]
server/sonar-webserver-core/src/test/java/org/sonar/server/platform/PersistentSettingsTest.java [deleted file]
server/sonar-webserver-core/src/test/java/org/sonar/server/platform/StartupMetadataPersisterTest.java [deleted file]
server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/DbConnectionSectionIT.java [new file with mode: 0644]
server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/DbConnectionSectionTest.java [deleted file]
server/sonar-webserver-core/src/test/java/org/sonar/server/platform/serverid/ServerIdManagerTest.java [deleted file]
server/sonar-webserver-core/src/test/java/org/sonar/server/startup/RegisterMetricsTest.java [deleted file]
server/sonar-webserver-core/src/test/java/org/sonar/server/startup/RegisterPluginsTest.java [deleted file]
server/sonar-webserver-core/src/test/java/org/sonar/server/startup/UpgradeSuggestionsCleanerTest.java [deleted file]
server/sonar-webserver-core/src/test/java/org/sonar/server/webhook/WebhookQGChangeEventListenerTest.java [deleted file]
server/sonar-webserver-es/src/it/java/org/sonar/server/permission/index/PermissionIndexerDaoIT.java [new file with mode: 0644]
server/sonar-webserver-es/src/test/java/org/sonar/server/permission/index/PermissionIndexerDaoTest.java [deleted file]
server/sonar-webserver-pushapi/src/it/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerAndExecutorIT.java [new file with mode: 0644]
server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerAndExecutorTest.java [deleted file]
server/sonar-webserver/src/it/java/org/sonar/server/platform/web/SonarLintConnectionFilterIT.java [new file with mode: 0644]
server/sonar-webserver/src/test/java/org/sonar/server/platform/web/SonarLintConnectionFilterTest.java [deleted file]

diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/component/BranchPersisterImplIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/component/BranchPersisterImplIT.java
new file mode 100644 (file)
index 0000000..9c3c041
--- /dev/null
@@ -0,0 +1,310 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.ce.task.projectanalysis.component;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Collections;
+import java.util.Optional;
+import javax.annotation.Nullable;
+import org.assertj.core.api.ThrowableAssert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.sonar.api.config.internal.ConfigurationBridge;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.ce.task.projectanalysis.analysis.Branch;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchDto;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.protobuf.DbProjectBranches;
+import org.sonar.server.project.Project;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
+import static org.sonar.core.config.PurgeConstants.BRANCHES_TO_KEEP_WHEN_INACTIVE;
+import static org.sonar.db.component.BranchType.BRANCH;
+import static org.sonar.db.component.BranchType.PULL_REQUEST;
+
+@RunWith(DataProviderRunner.class)
+public class BranchPersisterImplIT {
+  private final static Component MAIN = builder(Component.Type.PROJECT, 1, "PROJECT_KEY").setUuid("PROJECT_UUID").setName("p1").build();
+  private final static Component BRANCH1 = builder(Component.Type.PROJECT, 2, "BRANCH_KEY").setUuid("BRANCH_UUID").build();
+  private final static Component PR1 = builder(Component.Type.PROJECT, 3, "develop").setUuid("PR_UUID").build();
+  private static final Project PROJECT = new Project(MAIN.getUuid(), MAIN.getKey(), MAIN.getName(), null, Collections.emptyList());
+
+  @Rule
+  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+
+  private final MapSettings settings = new MapSettings();
+  private final ConfigurationRepository configurationRepository = new TestSettingsRepository(new ConfigurationBridge(settings));
+  private final BranchPersister underTest = new BranchPersisterImpl(dbTester.getDbClient(), treeRootHolder, analysisMetadataHolder, configurationRepository);
+
+  @Test
+  public void persist_fails_with_ISE_if_no_component_for_main_branches() {
+    analysisMetadataHolder.setBranch(createBranch(BRANCH, true, "master"));
+    treeRootHolder.setRoot(MAIN);
+    DbSession dbSession = dbTester.getSession();
+
+    expectMissingComponentISE(() -> underTest.persist(dbSession));
+  }
+
+  @Test
+  public void persist_fails_with_ISE_if_no_component_for_branches() {
+    analysisMetadataHolder.setBranch(createBranch(BRANCH, false, "foo"));
+    treeRootHolder.setRoot(BRANCH1);
+    DbSession dbSession = dbTester.getSession();
+
+    expectMissingComponentISE(() -> underTest.persist(dbSession));
+  }
+
+  @Test
+  public void persist_fails_with_ISE_if_no_component_for_pull_request() {
+    analysisMetadataHolder.setBranch(createBranch(BranchType.PULL_REQUEST, false, "12"));
+    treeRootHolder.setRoot(BRANCH1);
+    DbSession dbSession = dbTester.getSession();
+
+    expectMissingComponentISE(() -> underTest.persist(dbSession));
+  }
+
+  @Test
+  @UseDataProvider("nullOrNotNullString")
+  public void persist_creates_row_in_PROJECTS_BRANCHES_for_branch(@Nullable String mergeBranchUuid) {
+    String branchName = "branch";
+
+    // add project and branch in table PROJECTS
+    ComponentDto mainComponent = ComponentTesting.newPrivateProjectDto(MAIN.getUuid()).setKey(MAIN.getKey());
+    ComponentDto component = ComponentTesting.newBranchComponent(mainComponent,
+      new BranchDto().setUuid(BRANCH1.getUuid()).setKey(BRANCH1.getKey()).setBranchType(BRANCH));
+    dbTester.components().insertComponents(mainComponent, component);
+    // set project in metadata
+    treeRootHolder.setRoot(BRANCH1);
+    analysisMetadataHolder.setBranch(createBranch(BRANCH, false, branchName, mergeBranchUuid));
+    analysisMetadataHolder.setProject(Project.from(mainComponent));
+
+    underTest.persist(dbTester.getSession());
+
+    dbTester.getSession().commit();
+
+    assertThat(dbTester.countRowsOfTable("components")).isEqualTo(2);
+    Optional<BranchDto> branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH1.getUuid());
+    assertThat(branchDto).isPresent();
+    assertThat(branchDto.get().getBranchType()).isEqualTo(BRANCH);
+    assertThat(branchDto.get().getKey()).isEqualTo(branchName);
+    assertThat(branchDto.get().getMergeBranchUuid()).isEqualTo(mergeBranchUuid);
+    assertThat(branchDto.get().getProjectUuid()).isEqualTo(MAIN.getUuid());
+    assertThat(branchDto.get().getPullRequestData()).isNull();
+  }
+
+  @Test
+  public void main_branch_is_excluded_from_branch_purge_by_default() {
+    analysisMetadataHolder.setBranch(createBranch(BRANCH, true, "master"));
+    treeRootHolder.setRoot(MAIN);
+    dbTester.components().insertPublicProject(p -> p.setKey(MAIN.getKey()).setUuid(MAIN.getUuid()));
+    dbTester.commit();
+
+    underTest.persist(dbTester.getSession());
+
+    Optional<BranchDto> branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), MAIN.getUuid());
+    assertThat(branchDto).isPresent();
+    assertThat(branchDto.get().isExcludeFromPurge()).isTrue();
+  }
+
+  @Test
+  public void non_main_branch_is_excluded_from_branch_purge_if_matches_sonar_dbcleaner_keepFromPurge_property() {
+    settings.setProperty(BRANCHES_TO_KEEP_WHEN_INACTIVE, "BRANCH.*");
+    analysisMetadataHolder.setProject(PROJECT);
+    analysisMetadataHolder.setBranch(createBranch(BRANCH, false, "BRANCH_KEY"));
+    treeRootHolder.setRoot(BRANCH1);
+    ComponentDto mainComponent = dbTester.components().insertPublicProject(p -> p.setKey(MAIN.getKey()).setUuid(MAIN.getUuid()));
+    ComponentDto component = ComponentTesting.newBranchComponent(mainComponent,
+      new BranchDto().setUuid(BRANCH1.getUuid()).setKey(BRANCH1.getKey()).setBranchType(BRANCH));
+    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), component);
+    dbTester.commit();
+
+    underTest.persist(dbTester.getSession());
+
+    Optional<BranchDto> branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH1.getUuid());
+    assertThat(branchDto).isPresent();
+    assertThat(branchDto.get().isExcludeFromPurge()).isTrue();
+  }
+
+  @Test
+  public void branch_is_excluded_from_purge_when_it_matches_setting() {
+    analysisMetadataHolder.setProject(PROJECT);
+    analysisMetadataHolder.setBranch(createBranch(BRANCH, false, "BRANCH_KEY"));
+    treeRootHolder.setRoot(BRANCH1);
+    ComponentDto mainComponent = dbTester.components().insertPublicProject(p -> p.setKey(MAIN.getKey()).setUuid(MAIN.getUuid()));
+    ComponentDto component = ComponentTesting.newBranchComponent(mainComponent,
+      new BranchDto().setUuid(BRANCH1.getUuid()).setKey(BRANCH1.getKey()).setBranchType(BRANCH));
+    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), component);
+    settings.setProperty(BRANCHES_TO_KEEP_WHEN_INACTIVE, "BRANCH.*");
+    dbTester.commit();
+
+    underTest.persist(dbTester.getSession());
+
+    Optional<BranchDto> branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH1.getUuid());
+    assertThat(branchDto).isPresent();
+    assertThat(branchDto.get().isExcludeFromPurge()).isTrue();
+  }
+
+  @Test
+  public void branch_is_not_excluded_from_purge_when_it_does_not_match_setting() {
+    analysisMetadataHolder.setProject(PROJECT);
+    analysisMetadataHolder.setBranch(createBranch(BRANCH, false, "BRANCH_KEY"));
+    treeRootHolder.setRoot(BRANCH1);
+    ComponentDto mainComponent = dbTester.components().insertPublicProject(p -> p.setKey(MAIN.getKey()).setUuid(MAIN.getUuid()));
+    ComponentDto component = ComponentTesting.newBranchComponent(mainComponent,
+      new BranchDto().setUuid(BRANCH1.getUuid()).setKey(BRANCH1.getKey()).setBranchType(BRANCH));
+    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), component);
+    settings.setProperty(BRANCHES_TO_KEEP_WHEN_INACTIVE, "abc.*");
+
+    dbTester.commit();
+
+    underTest.persist(dbTester.getSession());
+
+    Optional<BranchDto> branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH1.getUuid());
+    assertThat(branchDto).isPresent();
+    assertThat(branchDto.get().isExcludeFromPurge()).isFalse();
+  }
+
+  @Test
+  public void pull_request_is_never_excluded_from_branch_purge_even_if_its_source_branch_name_matches_sonar_dbcleaner_keepFromPurge_property() {
+    settings.setProperty(BRANCHES_TO_KEEP_WHEN_INACTIVE, "develop");
+    analysisMetadataHolder.setBranch(createPullRequest(PR1.getKey(), MAIN.getUuid()));
+    analysisMetadataHolder.setPullRequestKey(PR1.getKey());
+    treeRootHolder.setRoot(PR1);
+    ComponentDto mainComponent = dbTester.components().insertPublicProject(p -> p.setKey(MAIN.getKey()).setUuid(MAIN.getUuid()));
+    ComponentDto component = ComponentTesting.newBranchComponent(mainComponent, new BranchDto()
+      .setUuid(PR1.getUuid())
+      .setKey(PR1.getKey())
+      .setProjectUuid(MAIN.getUuid())
+      .setBranchType(PULL_REQUEST)
+      .setMergeBranchUuid(MAIN.getUuid()));
+    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), component);
+    dbTester.commit();
+
+    underTest.persist(dbTester.getSession());
+
+    Optional<BranchDto> branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), PR1.getUuid());
+    assertThat(branchDto).isPresent();
+    assertThat(branchDto.get().isExcludeFromPurge()).isFalse();
+  }
+
+  @Test
+  public void non_main_branch_is_included_in_branch_purge_if_branch_name_does_not_match_sonar_dbcleaner_keepFromPurge_property() {
+    settings.setProperty(BRANCHES_TO_KEEP_WHEN_INACTIVE, "foobar-.*");
+    analysisMetadataHolder.setProject(PROJECT);
+    analysisMetadataHolder.setBranch(createBranch(BRANCH, false, "BRANCH_KEY"));
+    treeRootHolder.setRoot(BRANCH1);
+    ComponentDto mainComponent = dbTester.components().insertPublicProject(p -> p.setKey(MAIN.getKey()).setUuid(MAIN.getUuid()));
+    ComponentDto component = ComponentTesting.newBranchComponent(mainComponent,
+      new BranchDto().setUuid(BRANCH1.getUuid()).setKey(BRANCH1.getKey()).setBranchType(BRANCH));
+    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), component);
+    dbTester.commit();
+
+    underTest.persist(dbTester.getSession());
+
+    Optional<BranchDto> branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH1.getUuid());
+    assertThat(branchDto).isPresent();
+    assertThat(branchDto.get().isExcludeFromPurge()).isFalse();
+  }
+
+  @DataProvider
+  public static Object[][] nullOrNotNullString() {
+    return new Object[][] {
+      {null},
+      {randomAlphabetic(12)}
+    };
+  }
+
+  @Test
+  public void persist_creates_row_in_PROJECTS_BRANCHES_for_pull_request() {
+    String pullRequestId = "pr-123";
+
+    // add project and branch in table PROJECTS
+    ComponentDto mainComponent = ComponentTesting.newPrivateProjectDto(MAIN.getUuid()).setKey(MAIN.getKey());
+    ComponentDto component = ComponentTesting.newBranchComponent(mainComponent,
+      new BranchDto().setUuid(BRANCH1.getUuid()).setKey(BRANCH1.getKey()).setBranchType(PULL_REQUEST));
+    dbTester.components().insertComponents(mainComponent, component);
+    // set project in metadata
+    treeRootHolder.setRoot(BRANCH1);
+    analysisMetadataHolder.setBranch(createBranch(PULL_REQUEST, false, pullRequestId, "mergeBanchUuid"));
+    analysisMetadataHolder.setProject(Project.from(mainComponent));
+    analysisMetadataHolder.setPullRequestKey(pullRequestId);
+
+    underTest.persist(dbTester.getSession());
+
+    dbTester.getSession().commit();
+
+    assertThat(dbTester.countRowsOfTable("components")).isEqualTo(2);
+    Optional<BranchDto> branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH1.getUuid());
+    assertThat(branchDto).isPresent();
+    assertThat(branchDto.get().getBranchType()).isEqualTo(PULL_REQUEST);
+    assertThat(branchDto.get().getKey()).isEqualTo(pullRequestId);
+    assertThat(branchDto.get().getMergeBranchUuid()).isEqualTo("mergeBanchUuid");
+    assertThat(branchDto.get().getProjectUuid()).isEqualTo(MAIN.getUuid());
+    assertThat(branchDto.get().getPullRequestData()).isEqualTo(DbProjectBranches.PullRequestData.newBuilder()
+      .setBranch(pullRequestId)
+      .setTarget("mergeBanchUuid")
+      .setTitle(pullRequestId)
+      .build());
+  }
+
+  private static Branch createBranch(BranchType type, boolean isMain, String name) {
+    return createBranch(type, isMain, name, null);
+  }
+
+  private static Branch createPullRequest(String key, String mergeBranchUuid) {
+    Branch branch = createBranch(PULL_REQUEST, false, key, mergeBranchUuid);
+    when(branch.getPullRequestKey()).thenReturn(key);
+    return branch;
+  }
+
+  private static Branch createBranch(BranchType type, boolean isMain, String name, @Nullable String mergeBranchUuid) {
+    Branch branch = mock(Branch.class);
+    when(branch.getType()).thenReturn(type);
+    when(branch.getName()).thenReturn(name);
+    when(branch.isMain()).thenReturn(isMain);
+    when(branch.getReferenceBranchUuid()).thenReturn(mergeBranchUuid);
+    when(branch.getTargetBranchName()).thenReturn(mergeBranchUuid);
+    return branch;
+  }
+
+  private void expectMissingComponentISE(ThrowableAssert.ThrowingCallable callable) {
+    assertThatThrownBy(callable)
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Component has been deleted by end-user during analysis");
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT.java
new file mode 100644 (file)
index 0000000..a8e9be9
--- /dev/null
@@ -0,0 +1,710 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.ce.task.projectanalysis.filemove;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.IntStream;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.ce.task.projectanalysis.analysis.Analysis;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.FileAttributes;
+import org.sonar.ce.task.projectanalysis.component.ReportComponent;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
+import org.sonar.ce.task.projectanalysis.source.SourceLinesHashRepository;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.core.hash.SourceLineHashesComputer;
+import org.sonar.core.util.Uuids;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.source.FileSourceDto;
+
+import static java.util.Arrays.stream;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
+import static org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStep.MIN_REQUIRED_SCORE;
+import static org.sonar.db.component.BranchType.*;
+
+public class FileMoveDetectionStepIT {
+
+  private static final String SNAPSHOT_UUID = "uuid_1";
+  private static final Analysis ANALYSIS = new Analysis.Builder()
+    .setUuid(SNAPSHOT_UUID)
+    .setCreatedAt(86521)
+    .build();
+  private static final int ROOT_REF = 1;
+  private static final int FILE_1_REF = 2;
+  private static final int FILE_2_REF = 3;
+  private static final int FILE_3_REF = 4;
+  private static final String[] CONTENT1 = {
+    "package org.sonar.ce.task.projectanalysis.filemove;",
+    "",
+    "public class Foo {",
+    "  public String bar() {",
+    "    return \"Doh!\";",
+    "  }",
+    "}"
+  };
+
+  private static final String[] LESS_CONTENT1 = {
+    "package org.sonar.ce.task.projectanalysis.filemove;",
+    "",
+    "public class Foo {",
+    "  public String foo() {",
+    "    return \"Donut!\";",
+    "  }",
+    "}"
+  };
+  private static final String[] CONTENT_EMPTY = {
+    ""
+  };
+  private static final String[] CONTENT2 = {
+    "package org.sonar.ce.queue;",
+    "",
+    "import com.google.common.base.MoreObjects;",
+    "import javax.annotation.CheckForNull;",
+    "import javax.annotation.Nullable;",
+    "import javax.annotation.concurrent.Immutable;",
+    "",
+    "import static com.google.common.base.Strings.emptyToNull;",
+    "import static java.util.Objects.requireNonNull;",
+    "",
+    "@Immutable",
+    "public class CeTask {",
+    "",
+    ",  private final String type;",
+    ",  private final String uuid;",
+    ",  private final String componentUuid;",
+    ",  private final String componentKey;",
+    ",  private final String componentName;",
+    ",  private final String submitterLogin;",
+    "",
+    ",  private CeTask(Builder builder) {",
+    ",    this.uuid = requireNonNull(emptyToNull(builder.uuid));",
+    ",    this.type = requireNonNull(emptyToNull(builder.type));",
+    ",    this.componentUuid = emptyToNull(builder.componentUuid);",
+    ",    this.componentKey = emptyToNull(builder.componentKey);",
+    ",    this.componentName = emptyToNull(builder.componentName);",
+    ",    this.submitterLogin = emptyToNull(builder.submitterLogin);",
+    ",  }",
+    "",
+    ",  public String getUuid() {",
+    ",    return uuid;",
+    ",  }",
+    "",
+    ",  public String getType() {",
+    ",    return type;",
+    ",  }",
+    "",
+    ",  @CheckForNull",
+    ",  public String getComponentUuid() {",
+    ",    return componentUuid;",
+    ",  }",
+    "",
+    ",  @CheckForNull",
+    ",  public String getComponentKey() {",
+    ",    return componentKey;",
+    ",  }",
+    "",
+    ",  @CheckForNull",
+    ",  public String getComponentName() {",
+    ",    return componentName;",
+    ",  }",
+    "",
+    ",  @CheckForNull",
+    ",  public String getSubmitterLogin() {",
+    ",    return submitterLogin;",
+    ",  }",
+    ",}",
+  };
+  // removed immutable annotation
+  private static final String[] LESS_CONTENT2 = {
+    "package org.sonar.ce.queue;",
+    "",
+    "import com.google.common.base.MoreObjects;",
+    "import javax.annotation.CheckForNull;",
+    "import javax.annotation.Nullable;",
+    "",
+    "import static com.google.common.base.Strings.emptyToNull;",
+    "import static java.util.Objects.requireNonNull;",
+    "",
+    "public class CeTask {",
+    "",
+    ",  private final String type;",
+    ",  private final String uuid;",
+    ",  private final String componentUuid;",
+    ",  private final String componentKey;",
+    ",  private final String componentName;",
+    ",  private final String submitterLogin;",
+    "",
+    ",  private CeTask(Builder builder) {",
+    ",    this.uuid = requireNonNull(emptyToNull(builder.uuid));",
+    ",    this.type = requireNonNull(emptyToNull(builder.type));",
+    ",    this.componentUuid = emptyToNull(builder.componentUuid);",
+    ",    this.componentKey = emptyToNull(builder.componentKey);",
+    ",    this.componentName = emptyToNull(builder.componentName);",
+    ",    this.submitterLogin = emptyToNull(builder.submitterLogin);",
+    ",  }",
+    "",
+    ",  public String getUuid() {",
+    ",    return uuid;",
+    ",  }",
+    "",
+    ",  public String getType() {",
+    ",    return type;",
+    ",  }",
+    "",
+    ",  @CheckForNull",
+    ",  public String getComponentUuid() {",
+    ",    return componentUuid;",
+    ",  }",
+    "",
+    ",  @CheckForNull",
+    ",  public String getComponentKey() {",
+    ",    return componentKey;",
+    ",  }",
+    "",
+    ",  @CheckForNull",
+    ",  public String getComponentName() {",
+    ",    return componentName;",
+    ",  }",
+    "",
+    ",  @CheckForNull",
+    ",  public String getSubmitterLogin() {",
+    ",    return submitterLogin;",
+    ",  }",
+    ",}",
+  };
+
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+  @Rule
+  public MutableMovedFilesRepositoryRule movedFilesRepository = new MutableMovedFilesRepositoryRule();
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  private final DbClient dbClient = dbTester.getDbClient();
+  private ComponentDto project;
+
+  private final AnalysisMetadataHolderRule analysisMetadataHolder = mock(AnalysisMetadataHolderRule.class);
+  private final SourceLinesHashRepository sourceLinesHash = mock(SourceLinesHashRepository.class);
+  private final FileSimilarity fileSimilarity = new FileSimilarityImpl(new SourceSimilarityImpl());
+  private final CapturingScoreMatrixDumper scoreMatrixDumper = new CapturingScoreMatrixDumper();
+  private final RecordingMutableAddedFileRepository addedFileRepository = new RecordingMutableAddedFileRepository();
+
+  private final FileMoveDetectionStep underTest = new FileMoveDetectionStep(analysisMetadataHolder, treeRootHolder, dbClient,
+    fileSimilarity, movedFilesRepository, sourceLinesHash, scoreMatrixDumper, addedFileRepository);
+
+  @Before
+  public void setUp() throws Exception {
+    project = dbTester.components().insertPrivateProject();
+    treeRootHolder.setRoot(builder(Component.Type.PROJECT, ROOT_REF).setUuid(project.uuid()).build());
+  }
+
+  @Test
+  public void getDescription_returns_description() {
+    assertThat(underTest.getDescription()).isEqualTo("Detect file moves");
+  }
+
+  @Test
+  public void execute_detects_no_move_if_in_pull_request_scope() {
+    prepareAnalysis(PULL_REQUEST, ANALYSIS);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
+    verifyStatistics(context, null, null, null, null);
+  }
+
+  @Test
+  public void execute_detects_no_move_on_first_analysis() {
+    prepareAnalysis(BRANCH, null);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
+    verifyStatistics(context, 0, null, null, null);
+  }
+
+  @Test
+  public void execute_detects_no_move_if_baseSnapshot_has_no_file_and_report_has_no_file() {
+    prepareBranchAnalysis(ANALYSIS);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
+    assertThat(addedFileRepository.getComponents()).isEmpty();
+    verifyStatistics(context, 0, null, null, null);
+  }
+
+  @Test
+  public void execute_detects_no_move_if_baseSnapshot_has_no_file() {
+    prepareBranchAnalysis(ANALYSIS);
+    Component file1 = fileComponent(FILE_1_REF, null);
+    Component file2 = fileComponent(FILE_2_REF, null);
+    setFilesInReport(file1, file2);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
+    assertThat(addedFileRepository.getComponents()).containsOnly(file1, file2);
+    verifyStatistics(context, 2, 0, 2, null);
+  }
+
+  @Test
+  public void execute_detects_no_move_if_there_is_no_file_in_report() {
+    prepareBranchAnalysis(ANALYSIS);
+    insertFiles( /* no components */);
+    setFilesInReport();
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
+    assertThat(addedFileRepository.getComponents()).isEmpty();
+    verifyStatistics(context, 0, null, null, null);
+  }
+
+  @Test
+  public void execute_detects_no_move_if_file_key_exists_in_both_DB_and_report() {
+    prepareBranchAnalysis(ANALYSIS);
+    Component file1 = fileComponent(FILE_1_REF, null);
+    Component file2 = fileComponent(FILE_2_REF, null);
+    insertFiles(file1.getUuid(), file2.getUuid());
+    insertContentOfFileInDb(file1.getUuid(), CONTENT1);
+    insertContentOfFileInDb(file2.getUuid(), CONTENT2);
+    setFilesInReport(file2, file1);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
+    assertThat(addedFileRepository.getComponents()).isEmpty();
+    verifyStatistics(context, 2, 2, 0, null);
+  }
+
+  @Test
+  public void execute_detects_move_if_content_of_file_is_same_in_DB_and_report() {
+    prepareBranchAnalysis(ANALYSIS);
+    Component file1 = fileComponent(FILE_1_REF, null);
+    Component file2 = fileComponent(FILE_2_REF, CONTENT1);
+    ComponentDto[] dtos = insertFiles(file1.getUuid());
+    insertContentOfFileInDb(file1.getUuid(), CONTENT1);
+    setFilesInReport(file2);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).containsExactly(file2);
+    MovedFilesRepository.OriginalFile originalFile = movedFilesRepository.getOriginalFile(file2).get();
+    assertThat(originalFile.key()).isEqualTo(dtos[0].getKey());
+    assertThat(originalFile.uuid()).isEqualTo(dtos[0].uuid());
+    assertThat(addedFileRepository.getComponents()).isEmpty();
+    verifyStatistics(context, 1, 1, 1, 1);
+  }
+
+  @Test
+  public void execute_detects_no_move_if_content_of_file_is_not_similar_enough() {
+    prepareBranchAnalysis(ANALYSIS);
+    Component file1 = fileComponent(FILE_1_REF, null);
+    Component file2 = fileComponent(FILE_2_REF, LESS_CONTENT1);
+    insertFiles(file1.getKey());
+    insertContentOfFileInDb(file1.getKey(), CONTENT1);
+    setFilesInReport(file2);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
+    assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore())
+      .isPositive()
+      .isLessThan(MIN_REQUIRED_SCORE);
+    assertThat(addedFileRepository.getComponents()).contains(file2);
+    verifyStatistics(context, 1, 1, 1, 0);
+  }
+
+  @Test
+  public void execute_detects_no_move_if_content_of_file_is_empty_in_DB() {
+    prepareBranchAnalysis(ANALYSIS);
+    Component file1 = fileComponent(FILE_1_REF, null);
+    Component file2 = fileComponent(FILE_2_REF, CONTENT1);
+    insertFiles(file1.getKey());
+    insertContentOfFileInDb(file1.getKey(), CONTENT_EMPTY);
+    setFilesInReport(file2);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
+    assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isZero();
+    assertThat(addedFileRepository.getComponents()).contains(file2);
+    verifyStatistics(context, 1, 1, 1, 0);
+  }
+
+  @Test
+  public void execute_detects_no_move_if_content_of_file_has_no_path_in_DB() {
+    prepareBranchAnalysis(ANALYSIS);
+    Component file1 = fileComponent(FILE_1_REF, null);
+    Component file2 = fileComponent(FILE_2_REF, CONTENT1);
+    insertFiles(key -> newComponentDto(key).setPath(null), file1.getKey());
+    insertContentOfFileInDb(file1.getKey(), CONTENT1);
+    setFilesInReport(file2);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
+    assertThat(scoreMatrixDumper.scoreMatrix).isNull();
+    assertThat(addedFileRepository.getComponents()).containsOnly(file2);
+    verifyStatistics(context, 1, 0, 1, null);
+  }
+
+  @Test
+  public void execute_detects_no_move_if_content_of_file_is_empty_in_report() {
+    prepareBranchAnalysis(ANALYSIS);
+    Component file1 = fileComponent(FILE_1_REF, null);
+    Component file2 = fileComponent(FILE_2_REF, CONTENT_EMPTY);
+    insertFiles(file1.getKey());
+    insertContentOfFileInDb(file1.getKey(), CONTENT1);
+    setFilesInReport(file2);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
+    assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isZero();
+    assertThat(addedFileRepository.getComponents()).contains(file2);
+    verifyStatistics(context, 1, 1, 1, 0);
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("max score in matrix is less than min required score (85). Do nothing.");
+  }
+
+  @Test
+  public void execute_detects_no_move_if_two_added_files_have_same_content_as_the_one_in_db() {
+    prepareBranchAnalysis(ANALYSIS);
+    Component file1 = fileComponent(FILE_1_REF, null);
+    Component file2 = fileComponent(FILE_2_REF, CONTENT1);
+    Component file3 = fileComponent(FILE_3_REF, CONTENT1);
+    insertFiles(file1.getKey());
+    insertContentOfFileInDb(file1.getKey(), CONTENT1);
+    setFilesInReport(file2, file3);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
+    assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isEqualTo(100);
+    assertThat(addedFileRepository.getComponents()).containsOnly(file2, file3);
+    verifyStatistics(context, 2, 1, 2, 0);
+  }
+
+  @Test
+  public void execute_detects_no_move_if_two_deleted_files_have_same_content_as_the_one_added() {
+    prepareBranchAnalysis(ANALYSIS);
+    Component file1 = fileComponent(FILE_1_REF, null);
+    Component file2 = fileComponent(FILE_2_REF, null);
+    Component file3 = fileComponent(FILE_3_REF, CONTENT1);
+    insertFiles(file1.getUuid(), file2.getUuid());
+    insertContentOfFileInDb(file1.getUuid(), CONTENT1);
+    insertContentOfFileInDb(file2.getUuid(), CONTENT1);
+    setFilesInReport(file3);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
+    assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isEqualTo(100);
+    assertThat(addedFileRepository.getComponents()).containsOnly(file3);
+    verifyStatistics(context, 1, 2, 1, 0);
+  }
+
+  @Test
+  public void execute_detects_no_move_if_two_files_are_empty_in_DB() {
+    prepareBranchAnalysis(ANALYSIS);
+    Component file1 = fileComponent(FILE_1_REF, null);
+    Component file2 = fileComponent(FILE_2_REF, null);
+    insertFiles(file1.getUuid(), file2.getUuid());
+    insertContentOfFileInDb(file1.getUuid(), null);
+    insertContentOfFileInDb(file2.getUuid(), null);
+    setFilesInReport(file1, file2);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
+    assertThat(scoreMatrixDumper.scoreMatrix).isNull();
+    assertThat(addedFileRepository.getComponents()).isEmpty();
+    verifyStatistics(context, 2, 2, 0, null);
+  }
+
+  @Test
+  public void execute_detects_several_moves() {
+    // testing:
+    // - file1 renamed to file3
+    // - file2 deleted
+    // - file4 untouched
+    // - file5 renamed to file6 with a small change
+    prepareBranchAnalysis(ANALYSIS);
+    Component file1 = fileComponent(FILE_1_REF, null);
+    Component file2 = fileComponent(FILE_2_REF, null);
+    Component file3 = fileComponent(FILE_3_REF, CONTENT1);
+    Component file4 = fileComponent(5, new String[] {"a", "b"});
+    Component file5 = fileComponent(6, null);
+    Component file6 = fileComponent(7, LESS_CONTENT2);
+    ComponentDto[] dtos = insertFiles(file1.getUuid(), file2.getUuid(), file4.getUuid(), file5.getUuid());
+    insertContentOfFileInDb(file1.getUuid(), CONTENT1);
+    insertContentOfFileInDb(file2.getUuid(), LESS_CONTENT1);
+    insertContentOfFileInDb(file4.getUuid(), new String[] {"e", "f", "g", "h", "i"});
+    insertContentOfFileInDb(file5.getUuid(), CONTENT2);
+    setFilesInReport(file3, file4, file6);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).containsOnly(file3, file6);
+    MovedFilesRepository.OriginalFile originalFile2 = movedFilesRepository.getOriginalFile(file3).get();
+    assertThat(originalFile2.key()).isEqualTo(dtos[0].getKey());
+    assertThat(originalFile2.uuid()).isEqualTo(dtos[0].uuid());
+    MovedFilesRepository.OriginalFile originalFile5 = movedFilesRepository.getOriginalFile(file6).get();
+    assertThat(originalFile5.key()).isEqualTo(dtos[3].getKey());
+    assertThat(originalFile5.uuid()).isEqualTo(dtos[3].uuid());
+    assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isGreaterThan(MIN_REQUIRED_SCORE);
+    assertThat(addedFileRepository.getComponents()).isEmpty();
+    verifyStatistics(context, 3, 4, 2, 2);
+  }
+
+  @Test
+  public void execute_does_not_compute_any_distance_if_all_files_sizes_are_all_too_different() {
+    prepareBranchAnalysis(ANALYSIS);
+    Component file1 = fileComponent(FILE_1_REF, null);
+    Component file2 = fileComponent(FILE_2_REF, null);
+    Component file3 = fileComponent(FILE_3_REF, arrayOf(118));
+    Component file4 = fileComponent(5, arrayOf(25));
+    insertFiles(file1.getKey(), file2.getKey());
+    insertContentOfFileInDb(file1.getKey(), arrayOf(100));
+    insertContentOfFileInDb(file2.getKey(), arrayOf(30));
+    setFilesInReport(file3, file4);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
+    assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isZero();
+    verifyStatistics(context, 2, 2, 2, 0);
+  }
+
+  /**
+   * Creates an array of {@code numberOfElements} int values as String, starting with zero.
+   */
+  private static String[] arrayOf(int numberOfElements) {
+    return IntStream.range(0, numberOfElements).mapToObj(String::valueOf).toArray(String[]::new);
+  }
+
+  /**
+   * JH: A bug was encountered in the algorithm and I didn't manage to forge a simpler test case.
+   */
+  @Test
+  public void real_life_use_case() throws Exception {
+    prepareBranchAnalysis(ANALYSIS);
+    for (File f : FileUtils.listFiles(new File("src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1"), null, false)) {
+      insertFiles("uuid_" + f.getName().hashCode());
+      insertContentOfFileInDb("uuid_" + f.getName().hashCode(), readLines(f));
+    }
+
+    Map<String, Component> comps = new HashMap<>();
+    int i = 1;
+    for (File f : FileUtils.listFiles(new File("src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2"), null, false)) {
+      String[] lines = readLines(f);
+      Component c = builder(Component.Type.FILE, i++)
+        .setUuid("uuid_" + f.getName().hashCode())
+        .setKey(f.getName())
+        .setName(f.getName())
+        .setFileAttributes(new FileAttributes(false, null, lines.length))
+        .build();
+
+      comps.put(f.getName(), c);
+      setFileLineHashesInReport(c, lines);
+    }
+
+    setFilesInReport(comps.values().toArray(new Component[0]));
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    Component makeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex = comps.get("MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex.java");
+    Component migrationRb1238 = comps.get("1238_make_component_uuid_and_analysis_uuid_not_null_on_duplications_index.rb");
+    Component addComponentUuidAndAnalysisUuidColumnToDuplicationsIndex = comps.get("AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex.java");
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).containsOnly(
+      makeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex,
+      migrationRb1238,
+      addComponentUuidAndAnalysisUuidColumnToDuplicationsIndex);
+
+    assertThat(movedFilesRepository.getOriginalFile(makeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex).get().uuid())
+      .isEqualTo("uuid_" + "MakeComponentUuidNotNullOnDuplicationsIndex.java".hashCode());
+    assertThat(movedFilesRepository.getOriginalFile(migrationRb1238).get().uuid())
+      .isEqualTo("uuid_" + "1242_make_analysis_uuid_not_null_on_duplications_index.rb".hashCode());
+    assertThat(movedFilesRepository.getOriginalFile(addComponentUuidAndAnalysisUuidColumnToDuplicationsIndex).get().uuid())
+      .isEqualTo("uuid_" + "AddComponentUuidColumnToDuplicationsIndex.java".hashCode());
+    verifyStatistics(context, comps.values().size(), 12, 6, 3);
+  }
+
+  private String[] readLines(File filename) throws IOException {
+    return FileUtils
+      .readLines(filename, StandardCharsets.UTF_8)
+      .toArray(new String[0]);
+  }
+
+  @CheckForNull
+  private FileSourceDto insertContentOfFileInDb(String uuid, @Nullable String[] content) {
+    return dbTester.getDbClient().componentDao().selectByUuid(dbTester.getSession(), uuid)
+      .map(file -> {
+        SourceLineHashesComputer linesHashesComputer = new SourceLineHashesComputer();
+        if (content != null) {
+          stream(content).forEach(linesHashesComputer::addLine);
+        }
+        FileSourceDto fileSourceDto = new FileSourceDto()
+          .setUuid(Uuids.createFast())
+          .setFileUuid(file.uuid())
+          .setProjectUuid(file.branchUuid())
+          .setLineHashes(linesHashesComputer.getLineHashes());
+        dbTester.getDbClient().fileSourceDao().insert(dbTester.getSession(), fileSourceDto);
+        dbTester.commit();
+        return fileSourceDto;
+      }).orElse(null);
+  }
+
+  private void setFilesInReport(Component... files) {
+    treeRootHolder.setRoot(builder(Component.Type.PROJECT, ROOT_REF)
+      .setUuid(project.uuid())
+      .addChildren(files)
+      .build());
+  }
+
+  private ComponentDto[] insertFiles(String... uuids) {
+    return insertFiles(this::newComponentDto, uuids);
+  }
+
+  private ComponentDto[] insertFiles(Function<String, ComponentDto> newComponentDto, String... uuids) {
+    return stream(uuids)
+      .map(newComponentDto)
+      .map(dto -> dbTester.components().insertComponent(dto))
+      .toArray(ComponentDto[]::new);
+  }
+
+  private ComponentDto newComponentDto(String uuid) {
+    return ComponentTesting.newFileDto(project)
+      .setKey("key_" + uuid)
+      .setUuid(uuid)
+      .setPath("path_" + uuid);
+  }
+
+  private Component fileComponent(int ref, @Nullable String[] content) {
+    ReportComponent component = builder(Component.Type.FILE, ref)
+      .setName("report_path" + ref)
+      .setFileAttributes(new FileAttributes(false, null, content == null ? 1 : content.length))
+      .build();
+    if (content != null) {
+      setFileLineHashesInReport(component, content);
+    }
+    return component;
+  }
+
+  private void setFileLineHashesInReport(Component file, String[] content) {
+    SourceLineHashesComputer computer = new SourceLineHashesComputer();
+    for (String line : content) {
+      computer.addLine(line);
+    }
+    when(sourceLinesHash.getLineHashesMatchingDBVersion(file)).thenReturn(computer.getLineHashes());
+  }
+
+  private static class CapturingScoreMatrixDumper implements ScoreMatrixDumper {
+    private ScoreMatrix scoreMatrix;
+
+    @Override
+    public void dumpAsCsv(ScoreMatrix scoreMatrix) {
+      this.scoreMatrix = scoreMatrix;
+    }
+  }
+
+  private void prepareBranchAnalysis(Analysis analysis) {
+    prepareAnalysis(BRANCH, analysis);
+  }
+
+  private void prepareAnalysis(BranchType branch, Analysis analysis) {
+    mockBranchType(branch);
+    analysisMetadataHolder.setBaseAnalysis(analysis);
+  }
+
+  private void mockBranchType(BranchType branchType) {
+    when(analysisMetadataHolder.isPullRequest()).thenReturn(branchType == PULL_REQUEST);
+  }
+
+  public static void verifyStatistics(TestComputationStepContext context,
+    @Nullable Integer expectedReportFiles, @Nullable Integer expectedDbFiles,
+    @Nullable Integer expectedAddedFiles, @Nullable Integer expectedMovedFiles) {
+    context.getStatistics().assertValue("reportFiles", expectedReportFiles);
+    context.getStatistics().assertValue("dbFiles", expectedDbFiles);
+    context.getStatistics().assertValue("addedFiles", expectedAddedFiles);
+    context.getStatistics().assertValue("movedFiles", expectedMovedFiles);
+  }
+
+  public static class RecordingMutableAddedFileRepository implements MutableAddedFileRepository {
+    private final List<Component> components = new ArrayList<>();
+
+    @Override
+    public void register(Component file) {
+      components.add(file);
+    }
+
+    @Override
+    public boolean isAdded(Component component) {
+      throw new UnsupportedOperationException("isAdded should not be called");
+    }
+
+    public List<Component> getComponents() {
+      return components;
+    }
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/filemove/PullRequestFileMoveDetectionStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/filemove/PullRequestFileMoveDetectionStepIT.java
new file mode 100644 (file)
index 0000000..044c69f
--- /dev/null
@@ -0,0 +1,385 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.ce.task.projectanalysis.filemove;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.ce.task.projectanalysis.analysis.Analysis;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.ce.task.projectanalysis.analysis.Branch;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.FileAttributes;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
+import org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStepIT.RecordingMutableAddedFileRepository;
+import org.sonar.ce.task.projectanalysis.filemove.MovedFilesRepository.OriginalFile;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.core.util.Uuids;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.source.FileSourceDto;
+import org.sonar.server.project.Project;
+
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toMap;
+import static java.util.stream.Collectors.toSet;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.ce.task.projectanalysis.component.Component.Type.FILE;
+import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT;
+import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
+import static org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStepIT.verifyStatistics;
+import static org.sonar.db.component.BranchType.BRANCH;
+import static org.sonar.db.component.BranchType.PULL_REQUEST;
+
+public class PullRequestFileMoveDetectionStepIT {
+  private static final String ROOT_REF = "0";
+  private static final String FILE_1_REF = "1";
+  private static final String FILE_2_REF = "2";
+  private static final String FILE_3_REF = "3";
+  private static final String FILE_4_REF = "4";
+  private static final String FILE_5_REF = "5";
+  private static final String FILE_6_REF = "6";
+  private static final String FILE_7_REF = "7";
+  private static final String TARGET_BRANCH = "target_branch";
+  private static final String BRANCH_UUID = "branch_uuid";
+  private static final String SNAPSHOT_UUID = "uuid_1";
+
+  private static final Analysis ANALYSIS = new Analysis.Builder()
+    .setUuid(SNAPSHOT_UUID)
+    .setCreatedAt(86521)
+    .build();
+
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+  @Rule
+  public MutableMovedFilesRepositoryRule movedFilesRepository = new MutableMovedFilesRepositoryRule();
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  private ComponentDto branch;
+  private ComponentDto project;
+
+  private final DbClient dbClient = dbTester.getDbClient();
+  private final AnalysisMetadataHolderRule analysisMetadataHolder = mock(AnalysisMetadataHolderRule.class);
+  private final RecordingMutableAddedFileRepository addedFileRepository = new RecordingMutableAddedFileRepository();
+  private final PullRequestFileMoveDetectionStep underTest = new PullRequestFileMoveDetectionStep(analysisMetadataHolder, treeRootHolder, dbClient, movedFilesRepository, addedFileRepository);
+
+  @Before
+  public void setUp() throws Exception {
+    project = dbTester.components().insertPrivateProject();
+    branch = dbTester.components().insertProjectBranch(project, branchDto -> branchDto.setUuid(BRANCH_UUID).setKey(TARGET_BRANCH));
+    treeRootHolder.setRoot(builder(Component.Type.PROJECT, Integer.parseInt(ROOT_REF)).setUuid(project.uuid()).build());
+  }
+
+  @Test
+  public void getDescription_returns_description() {
+    assertThat(underTest.getDescription()).isEqualTo("Detect file moves in Pull Request scope");
+  }
+
+  @Test
+  public void execute_does_not_detect_any_files_if_not_in_pull_request_scope() {
+    prepareAnalysis(BRANCH, ANALYSIS);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
+    verifyStatistics(context, null, null, null, null);
+  }
+
+  @Test
+  public void execute_detects_no_move_if_report_has_no_file() {
+    preparePullRequestAnalysis(ANALYSIS);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
+    assertThat(addedFileRepository.getComponents()).isEmpty();
+    verifyStatistics(context, 0, null, null, null);
+  }
+
+  @Test
+  public void execute_detects_no_move_if_target_branch_has_no_files() {
+    preparePullRequestAnalysis(ANALYSIS);
+    Set<FileReference> fileReferences = Set.of(FileReference.of(FILE_1_REF), FileReference.of(FILE_2_REF));
+    Map<String, Component> reportFilesByUuid = initializeAnalysisReportComponents(fileReferences);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
+    assertThat(addedFileRepository.getComponents()).containsOnlyOnceElementsOf(reportFilesByUuid.values());
+    verifyStatistics(context, 2, 0, 2, null);
+  }
+
+  @Test
+  public void execute_detects_no_move_if_there_are_no_files_in_report() {
+    preparePullRequestAnalysis(ANALYSIS);
+    initializeAnalysisReportComponents(Set.of());
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
+    assertThat(addedFileRepository.getComponents()).isEmpty();
+    verifyStatistics(context, 0, null, null, null);
+  }
+
+  @Test
+  public void execute_detects_no_move_if_file_key_exists_in_both_database_and_report() {
+    preparePullRequestAnalysis(ANALYSIS);
+
+    Set<FileReference> fileReferences = Set.of(FileReference.of(FILE_1_REF), FileReference.of(FILE_2_REF));
+    initializeAnalysisReportComponents(fileReferences);
+    initializeTargetBranchDatabaseComponents(fileReferences);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
+    assertThat(addedFileRepository.getComponents()).isEmpty();
+    verifyStatistics(context, 2, 2, 0, 0);
+  }
+
+  @Test
+  public void execute_detects_renamed_file() {
+    // - FILE_1_REF on target branch is renamed to FILE_2_REF on Pull Request
+    preparePullRequestAnalysis(ANALYSIS);
+
+    Set<FileReference> reportFileReferences = Set.of(FileReference.of(FILE_2_REF, FILE_1_REF));
+    Set<FileReference> databaseFileReferences = Set.of(FileReference.of(FILE_1_REF));
+
+    Map<String, Component> reportFilesByUuid = initializeAnalysisReportComponents(reportFileReferences);
+    Map<String, Component> databaseFilesByUuid = initializeTargetBranchDatabaseComponents(databaseFileReferences);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(addedFileRepository.getComponents()).isEmpty();
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).hasSize(1);
+    assertThatFileRenameHasBeenDetected(reportFilesByUuid, databaseFilesByUuid, FILE_2_REF, FILE_1_REF);
+    verifyStatistics(context, 1, 1, 0, 1);
+  }
+
+  @Test
+  public void execute_detects_several_renamed_file() {
+    // - FILE_1_REF has been renamed to FILE_3_REF on Pull Request
+    // - FILE_2_REF has been deleted on Pull Request
+    // - FILE_4_REF has been left untouched
+    // - FILE_5_REF has been renamed to FILE_6_REF on Pull Request
+    // - FILE_7_REF has been added on Pull Request
+    preparePullRequestAnalysis(ANALYSIS);
+
+    Set<FileReference> reportFileReferences = Set.of(
+      FileReference.of(FILE_3_REF, FILE_1_REF),
+      FileReference.of(FILE_4_REF),
+      FileReference.of(FILE_6_REF, FILE_5_REF),
+      FileReference.of(FILE_7_REF));
+
+    Set<FileReference> databaseFileReferences = Set.of(
+      FileReference.of(FILE_1_REF),
+      FileReference.of(FILE_2_REF),
+      FileReference.of(FILE_4_REF),
+      FileReference.of(FILE_5_REF));
+
+    Map<String, Component> reportFilesByUuid = initializeAnalysisReportComponents(reportFileReferences);
+    Map<String, Component> databaseFilesByUuid = initializeTargetBranchDatabaseComponents(databaseFileReferences);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(addedFileRepository.getComponents()).hasSize(1);
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).hasSize(2);
+    assertThatFileAdditionHasBeenDetected(reportFilesByUuid, FILE_7_REF);
+    assertThatFileRenameHasBeenDetected(reportFilesByUuid, databaseFilesByUuid, FILE_3_REF, FILE_1_REF);
+    assertThatFileRenameHasBeenDetected(reportFilesByUuid, databaseFilesByUuid, FILE_6_REF, FILE_5_REF);
+    verifyStatistics(context, 4, 4, 1, 2);
+  }
+
+  private void assertThatFileAdditionHasBeenDetected(Map<String, Component> reportFilesByUuid, String fileInReportReference) {
+    Component fileInReport = reportFilesByUuid.get(fileInReportReference);
+
+    assertThat(addedFileRepository.getComponents()).contains(fileInReport);
+    assertThat(movedFilesRepository.getOriginalPullRequestFile(fileInReport)).isEmpty();
+  }
+
+
+  private void assertThatFileRenameHasBeenDetected(Map<String, Component> reportFilesByUuid, Map<String, Component> databaseFilesByUuid, String fileInReportReference, String originalFileInDatabaseReference) {
+    Component fileInReport = reportFilesByUuid.get(fileInReportReference);
+    Component originalFileInDatabase = databaseFilesByUuid.get(originalFileInDatabaseReference);
+
+    assertThat(movedFilesRepository.getComponentsWithOriginal()).contains(fileInReport);
+    assertThat(movedFilesRepository.getOriginalPullRequestFile(fileInReport)).isPresent();
+
+    OriginalFile detectedOriginalFile = movedFilesRepository.getOriginalPullRequestFile(fileInReport).get();
+    assertThat(detectedOriginalFile.key()).isEqualTo(originalFileInDatabase.getKey());
+    assertThat(detectedOriginalFile.uuid()).isEqualTo(originalFileInDatabase.getUuid());
+  }
+
+  private Map<String, Component> initializeTargetBranchDatabaseComponents(Set<FileReference> references) {
+    Set<Component> fileComponents = createFileComponents(references);
+    insertFileComponentsInDatabase(fileComponents);
+    return toFileComponentsByUuidMap(fileComponents);
+  }
+
+  private Map<String, Component> initializeAnalysisReportComponents(Set<FileReference> refs) {
+    Set<Component> fileComponents = createFileComponents(refs);
+    insertFileComponentsInReport(fileComponents);
+    return toFileComponentsByUuidMap(fileComponents);
+  }
+
+  private Map<String, Component> toFileComponentsByUuidMap(Set<Component> fileComponents) {
+    return fileComponents
+      .stream()
+      .collect(toMap(Component::getUuid, identity()));
+  }
+
+  private static Set<Component> createFileComponents(Set<FileReference> references) {
+    return references
+      .stream()
+      .map(PullRequestFileMoveDetectionStepIT::createReportFileComponent)
+      .collect(toSet());
+  }
+
+  private static Component createReportFileComponent(FileReference fileReference) {
+    return builder(FILE, Integer.parseInt(fileReference.getReference()))
+      .setUuid(fileReference.getReference())
+      .setName("report_path" + fileReference.getReference())
+      .setFileAttributes(new FileAttributes(false, null, 1, false, composeComponentPath(fileReference.getPastReference())))
+      .build();
+  }
+
+  private void insertFileComponentsInReport(Set<Component> files) {
+    treeRootHolder
+      .setRoot(builder(PROJECT, Integer.parseInt(ROOT_REF))
+      .setUuid(project.uuid())
+      .addChildren(files.toArray(Component[]::new))
+      .build());
+  }
+
+  private Set<ComponentDto> insertFileComponentsInDatabase(Set<Component> files) {
+    return files
+      .stream()
+      .map(Component::getUuid)
+      .map(this::composeComponentDto)
+      .peek(this::insertComponentDto)
+      .peek(this::insertContentOfFileInDatabase)
+      .collect(toSet());
+  }
+
+  private void insertComponentDto(ComponentDto component) {
+    dbTester.components().insertComponent(component);
+  }
+
+  private ComponentDto composeComponentDto(String uuid) {
+    return ComponentTesting
+      .newFileDto(project)
+      .setBranchUuid(branch.uuid())
+      .setKey("key_" + uuid)
+      .setUuid(uuid)
+      .setPath(composeComponentPath(uuid));
+  }
+
+  @CheckForNull
+  private static String composeComponentPath(@Nullable String reference) {
+    return Optional.ofNullable(reference)
+      .map(r -> String.join("_", "path", r))
+      .orElse(null);
+  }
+
+  private FileSourceDto insertContentOfFileInDatabase(ComponentDto file) {
+    FileSourceDto fileSourceDto = composeFileSourceDto(file);
+    persistFileSourceDto(fileSourceDto);
+    return fileSourceDto;
+  }
+
+  private static FileSourceDto composeFileSourceDto(ComponentDto file) {
+    return new FileSourceDto()
+      .setUuid(Uuids.createFast())
+      .setFileUuid(file.uuid())
+      .setProjectUuid(file.branchUuid());
+  }
+
+  private void persistFileSourceDto(FileSourceDto fileSourceDto) {
+    dbTester.getDbClient().fileSourceDao().insert(dbTester.getSession(), fileSourceDto);
+    dbTester.commit();
+  }
+
+  private void preparePullRequestAnalysis(Analysis analysis) {
+    prepareAnalysis(PULL_REQUEST, analysis);
+  }
+
+  private void prepareAnalysis(BranchType branch, Analysis analysis) {
+    mockBranchType(branch);
+    analysisMetadataHolder.setBaseAnalysis(analysis);
+  }
+
+  private void mockBranchType(BranchType branchType) {
+    Branch branch = mock(Branch.class);
+    when(analysisMetadataHolder.getBranch()).thenReturn(branch);
+    when(analysisMetadataHolder.getBranch().getTargetBranchName()).thenReturn(TARGET_BRANCH);
+    when(analysisMetadataHolder.isPullRequest()).thenReturn(branchType == PULL_REQUEST);
+    when(analysisMetadataHolder.getProject()).thenReturn(Project.from(project));
+  }
+
+  @Immutable
+  private static class FileReference {
+    private final String reference;
+    private final String pastReference;
+
+    private FileReference(String reference, @Nullable String pastReference) {
+      this.reference = reference;
+      this.pastReference = pastReference;
+    }
+
+    public String getReference() {
+      return reference;
+    }
+
+    @CheckForNull
+    public String getPastReference() {
+      return pastReference;
+    }
+
+    public static FileReference of(String reference, String pastReference) {
+      return new FileReference(reference, pastReference);
+    }
+
+    public static FileReference of(String reference) {
+      return new FileReference(reference, null);
+    }
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/metric/MetricRepositoryImplIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/metric/MetricRepositoryImplIT.java
new file mode 100644 (file)
index 0000000..5f0aecb
--- /dev/null
@@ -0,0 +1,214 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.ce.task.projectanalysis.metric;
+
+import java.util.List;
+import java.util.Random;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.metric.MetricDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+public class MetricRepositoryImplIT {
+  private static final String SOME_KEY = "some_key";
+  private static final String SOME_UUID = "uuid";
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private DbClient dbClient = dbTester.getDbClient();
+  private MetricRepositoryImpl underTest = new MetricRepositoryImpl(dbClient);
+
+  @Test
+  public void getByKey_throws_NPE_if_arg_is_null() {
+    assertThatThrownBy(() -> underTest.getByKey(null))
+      .isInstanceOf(NullPointerException.class);
+  }
+
+  @Test
+  public void getByKey_throws_ISE_if_start_has_not_been_called() {
+    assertThatThrownBy(() -> underTest.getByKey(SOME_KEY))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Metric cache has not been initialized");
+  }
+
+  @Test
+  public void getByKey_throws_ISE_of_Metric_does_not_exist() {
+    assertThatThrownBy(() -> {
+      underTest.start();
+      underTest.getByKey(SOME_KEY);
+    })
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage(String.format("Metric with key '%s' does not exist", SOME_KEY));
+  }
+
+  @Test
+  public void getByKey_throws_ISE_of_Metric_is_disabled() {
+    dbTester.measures().insertMetric(t -> t.setKey("complexity").setEnabled(false));
+
+    underTest.start();
+
+    assertThatThrownBy(() -> underTest.getByKey("complexity"))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage(String.format("Metric with key '%s' does not exist", "complexity"));
+  }
+
+  @Test
+  public void getByKey_find_enabled_Metrics() {
+    MetricDto ncloc = dbTester.measures().insertMetric(t -> t.setKey("ncloc").setEnabled(true));
+    MetricDto coverage = dbTester.measures().insertMetric(t -> t.setKey("coverage").setEnabled(true));
+
+    underTest.start();
+
+    assertThat(underTest.getByKey("ncloc").getUuid()).isEqualTo(ncloc.getUuid());
+    assertThat(underTest.getByKey("coverage").getUuid()).isEqualTo(coverage.getUuid());
+  }
+
+  @Test
+  public void getById_throws_ISE_if_start_has_not_been_called() {
+    assertThatThrownBy(() -> underTest.getByUuid(SOME_UUID))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Metric cache has not been initialized");
+  }
+
+  @Test
+  public void getById_throws_ISE_of_Metric_does_not_exist() {
+    underTest.start();
+
+    assertThatThrownBy(() -> underTest.getByUuid(SOME_UUID))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage(String.format("Metric with uuid '%s' does not exist", SOME_UUID));
+  }
+
+  @Test
+  public void getById_throws_ISE_of_Metric_is_disabled() {
+    dbTester.measures().insertMetric(t -> t.setKey("complexity").setEnabled(false));
+
+    underTest.start();
+
+    assertThatThrownBy(() -> underTest.getByUuid(SOME_UUID))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage(String.format("Metric with uuid '%s' does not exist", SOME_UUID));
+  }
+
+  @Test
+  public void getById_find_enabled_Metrics() {
+    MetricDto ncloc = dbTester.measures().insertMetric(t -> t.setKey("ncloc").setEnabled(true));
+    MetricDto coverage = dbTester.measures().insertMetric(t -> t.setKey("coverage").setEnabled(true));
+
+    underTest.start();
+
+    assertThat(underTest.getByUuid(ncloc.getUuid()).getKey()).isEqualTo("ncloc");
+    assertThat(underTest.getByUuid(coverage.getUuid()).getKey()).isEqualTo("coverage");
+  }
+
+  @Test
+  public void getOptionalById_throws_ISE_if_start_has_not_been_called() {
+    assertThatThrownBy(() -> underTest.getOptionalByUuid(SOME_UUID))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Metric cache has not been initialized");
+  }
+
+  @Test
+  public void getOptionalById_returns_empty_of_Metric_does_not_exist() {
+    underTest.start();
+
+    assertThat(underTest.getOptionalByUuid(SOME_UUID)).isEmpty();
+  }
+
+  @Test
+  public void getOptionalById_returns_empty_of_Metric_is_disabled() {
+    dbTester.measures().insertMetric(t -> t.setKey("complexity").setEnabled(false));
+
+    underTest.start();
+
+    assertThat(underTest.getOptionalByUuid(SOME_UUID)).isEmpty();
+  }
+
+  @Test
+  public void getOptionalById_find_enabled_Metrics() {
+    MetricDto ncloc = dbTester.measures().insertMetric(t -> t.setKey("ncloc").setEnabled(true));
+    MetricDto coverage = dbTester.measures().insertMetric(t -> t.setKey("coverage").setEnabled(true));
+
+    underTest.start();
+
+    assertThat(underTest.getOptionalByUuid(ncloc.getUuid()).get().getKey()).isEqualTo("ncloc");
+    assertThat(underTest.getOptionalByUuid(coverage.getUuid()).get().getKey()).isEqualTo("coverage");
+  }
+
+  @Test
+  public void get_all_metrics() {
+    List<MetricDto> enabledMetrics = IntStream.range(0, 1 + new Random().nextInt(12))
+      .mapToObj(i -> dbTester.measures().insertMetric(t -> t.setKey("key_enabled_" + i).setEnabled(true)))
+      .toList();
+    IntStream.range(0, 1 + new Random().nextInt(12))
+      .forEach(i -> dbTester.measures().insertMetric(t -> t.setKey("key_disabled_" + i).setEnabled(false)));
+
+    underTest.start();
+    assertThat(underTest.getAll())
+      .extracting(Metric::getKey)
+      .containsOnly(enabledMetrics.stream().map(MetricDto::getKey).toArray(String[]::new));
+  }
+
+  @Test
+  public void getMetricsByType_givenRatingType_returnRatingMetrics() {
+    List<MetricDto> enabledMetrics = IntStream.range(0, 1 + new Random().nextInt(12))
+      .mapToObj(i -> dbTester.measures().insertMetric(t -> t.setKey("key_enabled_" + i).setEnabled(true).setValueType("RATING")))
+      .toList();
+
+    underTest.start();
+    assertThat(underTest.getMetricsByType(Metric.MetricType.RATING))
+      .extracting(Metric::getKey)
+      .containsOnly(enabledMetrics.stream().map(MetricDto::getKey).toArray(String[]::new));
+  }
+
+  @Test
+  public void getMetricsByType_givenRatingTypeAndWantedMilisecType_returnEmptyList() {
+    IntStream.range(0, 1 + new Random().nextInt(12))
+      .mapToObj(i -> dbTester.measures().insertMetric(t -> t.setKey("key_enabled_" + i).setEnabled(true).setValueType("RATING")))
+      .collect(Collectors.toList());
+
+    underTest.start();
+    assertThat(underTest.getMetricsByType(Metric.MetricType.MILLISEC)).isEmpty();
+  }
+
+  @Test
+  public void getMetricsByType_givenOnlyMilisecTypeAndWantedRatingMetrics_returnEmptyList() {
+    IntStream.range(0, 1 + new Random().nextInt(12))
+      .mapToObj(i -> dbTester.measures().insertMetric(t -> t.setKey("key_enabled_" + i).setEnabled(true).setValueType("MILISEC")));
+
+    underTest.start();
+    assertThat(underTest.getMetricsByType(Metric.MetricType.RATING)).isEmpty();
+  }
+
+  @Test
+  public void getMetricsByType_givenMetricsAreNull_throwException() {
+    assertThatThrownBy(() -> underTest.getMetricsByType(Metric.MetricType.RATING))
+      .isInstanceOf(IllegalStateException.class);
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/scm/ScmInfoDbLoaderIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/scm/ScmInfoDbLoaderIT.java
new file mode 100644 (file)
index 0000000..1591410
--- /dev/null
@@ -0,0 +1,249 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.ce.task.projectanalysis.scm;
+
+import com.google.common.collect.ImmutableList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+import javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.ce.task.projectanalysis.analysis.Analysis;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.ce.task.projectanalysis.analysis.Branch;
+import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.ReferenceBranchComponentUuids;
+import org.sonar.ce.task.projectanalysis.filemove.MutableMovedFilesRepositoryRule;
+import org.sonar.ce.task.projectanalysis.period.NewCodeReferenceBranchComponentUuids;
+import org.sonar.ce.task.projectanalysis.period.Period;
+import org.sonar.ce.task.projectanalysis.period.PeriodHolderRule;
+import org.sonar.core.hash.SourceHashComputer;
+import org.sonar.core.util.Uuids;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.newcodeperiod.NewCodePeriodType;
+import org.sonar.db.protobuf.DbFileSources;
+import org.sonar.db.source.FileSourceDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.utils.log.LoggerLevel.TRACE;
+import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
+
+public class ScmInfoDbLoaderIT {
+  static final int FILE_REF = 1;
+  static final Component FILE = builder(Component.Type.FILE, FILE_REF).setKey("FILE_KEY").setUuid("FILE_UUID").build();
+  static final long DATE_1 = 123456789L;
+
+  static Analysis baseProjectAnalysis = new Analysis.Builder()
+    .setUuid("uuid_1")
+    .setCreatedAt(123456789L)
+    .build();
+
+  @Rule
+  public LogTester logTester = new LogTester();
+  @Rule
+  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  @Rule
+  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
+  @Rule
+  public MutableMovedFilesRepositoryRule movedFiles = new MutableMovedFilesRepositoryRule();
+  @Rule
+  public PeriodHolderRule periodHolder = new PeriodHolderRule();
+
+  private final Branch branch = mock(Branch.class);
+  private final ReferenceBranchComponentUuids referenceBranchComponentUuids = mock(ReferenceBranchComponentUuids.class);
+  private final NewCodeReferenceBranchComponentUuids newCodeReferenceBranchComponentUuids = mock(NewCodeReferenceBranchComponentUuids.class);
+
+  private final ScmInfoDbLoader underTest = new ScmInfoDbLoader(analysisMetadataHolder, movedFiles, dbTester.getDbClient(), referenceBranchComponentUuids,
+      newCodeReferenceBranchComponentUuids, periodHolder);
+
+  @Before
+  public void before() {
+    periodHolder.setPeriod(new Period(NewCodePeriodType.PREVIOUS_VERSION.name(), null, null));
+  }
+
+  @Test
+  public void returns_ScmInfo_from_DB() {
+    analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis);
+    analysisMetadataHolder.setBranch(null);
+
+    String hash = computeSourceHash(1);
+    addFileSourceInDb("henry", DATE_1, "rev-1", hash);
+
+    DbScmInfo scmInfo = underTest.getScmInfo(FILE).get();
+    assertThat(scmInfo.getAllChangesets()).hasSize(1);
+    assertThat(scmInfo.fileHash()).isEqualTo(hash);
+
+    assertThat(logTester.logs(TRACE)).containsOnly("Reading SCM info from DB for file 'FILE_UUID'");
+  }
+
+  @Test
+  public void read_from_reference_branch_if_no_base() {
+    analysisMetadataHolder.setBaseAnalysis(null);
+    analysisMetadataHolder.setBranch(branch);
+
+    String referenceFileUuid = "referenceFileUuid";
+    String hash = computeSourceHash(1);
+
+    when(referenceBranchComponentUuids.getComponentUuid(FILE.getKey())).thenReturn(referenceFileUuid);
+    addFileSourceInDb("henry", DATE_1, "rev-1", hash, referenceFileUuid);
+
+    DbScmInfo scmInfo = underTest.getScmInfo(FILE).get();
+    assertThat(scmInfo.getAllChangesets()).hasSize(1);
+    assertThat(scmInfo.fileHash()).isEqualTo(hash);
+    assertThat(logTester.logs(TRACE)).containsOnly("Reading SCM info from DB for file 'referenceFileUuid'");
+  }
+
+  @Test
+  public void read_from_target_if_pullrequest() {
+    Branch branch = mock(Branch.class);
+    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
+    analysisMetadataHolder.setBaseAnalysis(null);
+    analysisMetadataHolder.setBranch(branch);
+
+    String targetBranchFileUuid = "targetBranchFileUuid";
+    String hash = computeSourceHash(1);
+
+    when(referenceBranchComponentUuids.getComponentUuid(FILE.getKey())).thenReturn(targetBranchFileUuid);
+    addFileSourceInDb("henry", DATE_1, "rev-1", hash, targetBranchFileUuid);
+
+    DbScmInfo scmInfo = underTest.getScmInfo(FILE).get();
+    assertThat(scmInfo.getAllChangesets()).hasSize(1);
+    assertThat(scmInfo.fileHash()).isEqualTo(hash);
+    assertThat(logTester.logs(TRACE)).containsOnly("Reading SCM info from DB for file 'targetBranchFileUuid'");
+  }
+
+  @Test
+  public void read_from_target_if_reference_branch() {
+    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), null, null));
+
+    Branch branch = mock(Branch.class);
+    when(branch.getType()).thenReturn(BranchType.BRANCH);
+    analysisMetadataHolder.setBaseAnalysis(null);
+    analysisMetadataHolder.setBranch(branch);
+
+    String targetBranchFileUuid = "targetBranchFileUuid";
+    String hash = computeSourceHash(1);
+
+    when(newCodeReferenceBranchComponentUuids.getComponentUuid(FILE.getKey())).thenReturn(targetBranchFileUuid);
+    addFileSourceInDb("henry", DATE_1, "rev-1", hash, targetBranchFileUuid);
+
+    DbScmInfo scmInfo = underTest.getScmInfo(FILE).get();
+    assertThat(scmInfo.getAllChangesets()).hasSize(1);
+    assertThat(scmInfo.fileHash()).isEqualTo(hash);
+    assertThat(logTester.logs(TRACE)).containsOnly("Reading SCM info from DB for file 'targetBranchFileUuid'");
+  }
+
+  @Test
+  public void read_from_db_if_not_exist_in_reference_branch() {
+    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), null, null));
+
+    Branch branch = mock(Branch.class);
+    when(branch.getType()).thenReturn(BranchType.BRANCH);
+    analysisMetadataHolder.setBaseAnalysis(null);
+    analysisMetadataHolder.setBranch(branch);
+
+    String hash = computeSourceHash(1);
+
+    addFileSourceInDb("henry", DATE_1, "rev-1", hash, FILE.getUuid());
+
+    DbScmInfo scmInfo = underTest.getScmInfo(FILE).get();
+    assertThat(scmInfo.getAllChangesets()).hasSize(1);
+    assertThat(scmInfo.fileHash()).isEqualTo(hash);
+    assertThat(logTester.logs(TRACE)).containsOnly("Reading SCM info from DB for file 'FILE_UUID'");
+  }
+
+  @Test
+  public void return_empty_if_no_dto_available() {
+    analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis);
+    analysisMetadataHolder.setBranch(null);
+
+    Optional<DbScmInfo> scmInfo = underTest.getScmInfo(FILE);
+
+    assertThat(logTester.logs(TRACE)).containsOnly("Reading SCM info from DB for file 'FILE_UUID'");
+    assertThat(scmInfo).isEmpty();
+  }
+
+  @Test
+  public void do_not_read_from_db_on_first_analysis_if_there_is_no_reference_branch() {
+    Branch branch = mock(Branch.class);
+    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
+    analysisMetadataHolder.setBaseAnalysis(null);
+    analysisMetadataHolder.setBranch(branch);
+
+    assertThat(underTest.getScmInfo(FILE)).isEmpty();
+    assertThat(logTester.logs(TRACE)).isEmpty();
+  }
+
+  private static List<String> generateLines(int lineCount) {
+    ImmutableList.Builder<String> builder = ImmutableList.builder();
+    for (int i = 0; i < lineCount; i++) {
+      builder.add("line " + i);
+    }
+    return builder.build();
+  }
+
+  private static String computeSourceHash(int lineCount) {
+    SourceHashComputer sourceHashComputer = new SourceHashComputer();
+    Iterator<String> lines = generateLines(lineCount).iterator();
+    while (lines.hasNext()) {
+      sourceHashComputer.addLine(lines.next(), lines.hasNext());
+    }
+    return sourceHashComputer.getHash();
+  }
+
+  private void addFileSourceInDb(@Nullable String author, @Nullable Long date, @Nullable String revision, String srcHash) {
+    addFileSourceInDb(author, date, revision, srcHash, FILE.getUuid());
+  }
+
+  private void addFileSourceInDb(@Nullable String author, @Nullable Long date, @Nullable String revision, String srcHash, String fileUuid) {
+    DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder();
+    DbFileSources.Line.Builder builder = fileDataBuilder.addLinesBuilder()
+      .setLine(1);
+    if (author != null) {
+      builder.setScmAuthor(author);
+    }
+    if (date != null) {
+      builder.setScmDate(date);
+    }
+    if (revision != null) {
+      builder.setScmRevision(revision);
+    }
+    dbTester.getDbClient().fileSourceDao().insert(dbTester.getSession(), new FileSourceDto()
+      .setUuid(Uuids.createFast())
+      .setLineHashes(Collections.singletonList("lineHash"))
+      .setFileUuid(fileUuid)
+      .setProjectUuid("PROJECT_UUID")
+      .setSourceData(fileDataBuilder.build())
+      .setSrcHash(srcHash));
+    dbTester.commit();
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStepIT.java
new file mode 100644 (file)
index 0000000..141a461
--- /dev/null
@@ -0,0 +1,586 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.ce.task.projectanalysis.step;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.projectanalysis.analysis.Branch;
+import org.sonar.ce.task.projectanalysis.analysis.MutableAnalysisMetadataHolderRule;
+import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.DefaultBranchImpl;
+import org.sonar.ce.task.projectanalysis.component.MutableTreeRootHolderRule;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.scanner.protocol.output.ScannerReport;
+import org.sonar.scanner.protocol.output.ScannerReport.Component.ComponentType;
+import org.sonar.scanner.protocol.output.ScannerReport.Component.FileStatus;
+import org.sonar.server.project.Project;
+
+import static java.util.Optional.ofNullable;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME;
+import static org.sonar.db.component.ComponentTesting.newDirectory;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
+import static org.sonar.db.component.SnapshotTesting.newAnalysis;
+import static org.sonar.scanner.protocol.output.ScannerReport.Component.ComponentType.FILE;
+import static org.sonar.scanner.protocol.output.ScannerReport.Component.ComponentType.PROJECT;
+
+@RunWith(DataProviderRunner.class)
+public class BuildComponentTreeStepIT {
+  private static final String NO_SCANNER_PROJECT_VERSION = null;
+  private static final String NO_SCANNER_BUILD_STRING = null;
+
+  private static final int ROOT_REF = 1;
+  private static final int FILE_1_REF = 4;
+  private static final int FILE_2_REF = 5;
+  private static final int FILE_3_REF = 7;
+  private static final int UNCHANGED_FILE_REF = 10;
+
+  private static final String REPORT_PROJECT_KEY = "REPORT_PROJECT_KEY";
+  private static final String REPORT_DIR_PATH_1 = "src/main/java/dir1";
+  private static final String REPORT_FILE_PATH_1 = "src/main/java/dir1/File1.java";
+  private static final String REPORT_FILE_NAME_1 = "File1.java";
+  private static final String REPORT_DIR_PATH_2 = "src/main/java/dir2";
+  private static final String REPORT_FILE_PATH_2 = "src/main/java/dir2/File2.java";
+  private static final String REPORT_FILE_PATH_3 = "src/main/java/dir2/File3.java";
+  private static final String REPORT_UNCHANGED_FILE_PATH = "src/main/File3.java";
+
+  private static final long ANALYSIS_DATE = 123456789L;
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  @Rule
+  public BatchReportReaderRule reportReader = new BatchReportReaderRule().setMetadata(createReportMetadata(NO_SCANNER_PROJECT_VERSION, NO_SCANNER_BUILD_STRING));
+  @Rule
+  public MutableTreeRootHolderRule treeRootHolder = new MutableTreeRootHolderRule();
+  @Rule
+  public MutableAnalysisMetadataHolderRule analysisMetadataHolder = new MutableAnalysisMetadataHolderRule();
+
+  private DbClient dbClient = dbTester.getDbClient();
+  private BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder);
+
+  @Test
+  public void fails_if_root_component_does_not_exist_in_reportReader() {
+    setAnalysisMetadataHolder();
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(NullPointerException.class);
+  }
+
+  @Test
+  public void verify_tree_is_correctly_built() {
+    setAnalysisMetadataHolder();
+    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF, FILE_2_REF, FILE_3_REF));
+    reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1));
+    reportReader.putComponent(componentWithPath(FILE_2_REF, FILE, REPORT_FILE_PATH_2));
+    reportReader.putComponent(componentWithPath(FILE_3_REF, FILE, REPORT_FILE_PATH_3));
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    Component root = treeRootHolder.getRoot();
+    assertThat(root).isNotNull();
+    verifyComponent(root, Component.Type.PROJECT, ROOT_REF, 1);
+
+    Component dir = root.getChildren().iterator().next();
+    verifyComponent(dir, Component.Type.DIRECTORY, null, 2);
+
+    Component dir1 = dir.getChildren().get(0);
+    verifyComponent(dir1, Component.Type.DIRECTORY, null, 1);
+    verifyComponent(dir1.getChildren().get(0), Component.Type.FILE, FILE_1_REF, 0);
+
+    Component dir2 = dir.getChildren().get(1);
+    verifyComponent(dir2, Component.Type.DIRECTORY, null, 2);
+    verifyComponent(dir2.getChildren().get(0), Component.Type.FILE, FILE_2_REF, 0);
+    verifyComponent(dir2.getChildren().get(1), Component.Type.FILE, FILE_3_REF, 0);
+
+    context.getStatistics().assertValue("components", 7);
+  }
+
+  @Test
+  public void verify_tree_is_correctly_built_in_prs() {
+    setAnalysisMetadataHolder(true);
+    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF, FILE_2_REF, FILE_3_REF, UNCHANGED_FILE_REF));
+    reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1));
+    reportReader.putComponent(componentWithPath(FILE_2_REF, FILE, REPORT_FILE_PATH_2));
+    reportReader.putComponent(componentWithPath(FILE_3_REF, FILE, REPORT_FILE_PATH_3));
+    reportReader.putComponent(unchangedComponentWithPath(UNCHANGED_FILE_REF, FILE, REPORT_UNCHANGED_FILE_PATH));
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    // modified root
+    Component mRoot = treeRootHolder.getRoot();
+    verifyComponent(mRoot, Component.Type.PROJECT, ROOT_REF, 1);
+
+    Component mDir = mRoot.getChildren().get(0);
+    assertThat(mDir.getName()).isEqualTo("src/main/java");
+    verifyComponent(mDir, Component.Type.DIRECTORY, null, 2);
+
+    Component mDir1 = mDir.getChildren().get(0);
+    assertThat(mDir1.getName()).isEqualTo("src/main/java/dir1");
+    verifyComponent(mDir1, Component.Type.DIRECTORY, null, 1);
+    verifyComponent(mDir1.getChildren().get(0), Component.Type.FILE, FILE_1_REF, 0);
+
+    Component mDir2 = mDir.getChildren().get(1);
+    assertThat(mDir2.getName()).isEqualTo("src/main/java/dir2");
+    verifyComponent(mDir2, Component.Type.DIRECTORY, null, 2);
+    verifyComponent(mDir2.getChildren().get(0), Component.Type.FILE, FILE_2_REF, 0);
+    verifyComponent(mDir2.getChildren().get(1), Component.Type.FILE, FILE_3_REF, 0);
+
+    // root
+    Component root = treeRootHolder.getReportTreeRoot();
+    verifyComponent(root, Component.Type.PROJECT, ROOT_REF, 1);
+
+    Component dir = root.getChildren().get(0);
+    assertThat(dir.getName()).isEqualTo("src/main");
+    verifyComponent(dir, Component.Type.DIRECTORY, null, 2);
+
+    Component dir1 = dir.getChildren().get(0);
+    assertThat(dir1.getName()).isEqualTo("src/main/java");
+    verifyComponent(dir1, Component.Type.DIRECTORY, null, 2);
+    verifyComponent(dir1.getChildren().get(0), Component.Type.DIRECTORY, null, 1);
+    verifyComponent(dir1.getChildren().get(1), Component.Type.DIRECTORY, null, 2);
+
+    Component dir2 = dir1.getChildren().get(0);
+    assertThat(dir2.getName()).isEqualTo("src/main/java/dir1");
+    verifyComponent(dir2, Component.Type.DIRECTORY, null, 1);
+    verifyComponent(dir2.getChildren().get(0), Component.Type.FILE, FILE_1_REF, 0);
+
+    Component dir3 = dir1.getChildren().get(1);
+    assertThat(dir3.getName()).isEqualTo("src/main/java/dir2");
+    verifyComponent(dir3, Component.Type.DIRECTORY, null, 2);
+    verifyComponent(dir3.getChildren().get(0), Component.Type.FILE, FILE_2_REF, 0);
+    verifyComponent(dir3.getChildren().get(1), Component.Type.FILE, FILE_3_REF, 0);
+
+    context.getStatistics().assertValue("components", 7);
+  }
+
+  /**
+   * SONAR-13262
+   */
+  @Test
+  public void verify_tree_is_correctly_built_in_prs_with_repeated_names() {
+    setAnalysisMetadataHolder(true);
+    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF));
+    reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_PROJECT_KEY + "/file.js"));
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    // modified root
+    Component mRoot = treeRootHolder.getRoot();
+    verifyComponent(mRoot, Component.Type.PROJECT, ROOT_REF, 1);
+
+    Component dir = mRoot.getChildren().get(0);
+    assertThat(dir.getName()).isEqualTo(REPORT_PROJECT_KEY);
+    assertThat(dir.getShortName()).isEqualTo(REPORT_PROJECT_KEY);
+
+    verifyComponent(dir, Component.Type.DIRECTORY, null, 1);
+  }
+
+  @Test
+  public void compute_keys_and_uuids() {
+    setAnalysisMetadataHolder();
+    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF));
+    reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1));
+
+    underTest.execute(new TestComputationStepContext());
+
+    verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, analysisMetadataHolder.getProject().getName());
+    verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, REPORT_DIR_PATH_1);
+    verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, REPORT_FILE_NAME_1);
+  }
+
+  @Test
+  public void return_existing_uuids() {
+    setAnalysisMetadataHolder();
+    ComponentDto project = insertComponent(newPrivateProjectDto("ABCD").setKey(REPORT_PROJECT_KEY));
+    ComponentDto directory = newDirectory(project, "CDEF", REPORT_DIR_PATH_1);
+    insertComponent(directory.setKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1));
+    insertComponent(newFileDto(project, directory, "DEFG")
+      .setKey(REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1)
+      .setPath(REPORT_FILE_PATH_1));
+
+    // new structure, without modules
+    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF));
+    reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1));
+
+    underTest.execute(new TestComputationStepContext());
+
+    verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, analysisMetadataHolder.getProject().getName(), "ABCD");
+    verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, REPORT_DIR_PATH_1, "CDEF");
+    verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, REPORT_FILE_NAME_1, "DEFG");
+  }
+
+  @Test
+  public void generate_keys_when_using_new_branch() {
+    Branch branch = mock(Branch.class);
+    when(branch.getName()).thenReturn("origin/feature");
+    when(branch.isMain()).thenReturn(false);
+    when(branch.generateKey(any(), any())).thenReturn("generated");
+    analysisMetadataHolder.setRootComponentRef(ROOT_REF)
+      .setAnalysisDate(ANALYSIS_DATE)
+      .setProject(Project.from(newPrivateProjectDto().setKey(REPORT_PROJECT_KEY)))
+      .setBranch(branch);
+    BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder);
+    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF));
+    reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1));
+
+    underTest.execute(new TestComputationStepContext());
+
+    verifyComponentByRef(ROOT_REF, "generated", analysisMetadataHolder.getProject().getName(), null);
+    verifyComponentByRef(FILE_1_REF, "generated", REPORT_FILE_NAME_1, null);
+  }
+
+  @Test
+  public void generate_keys_when_using_existing_branch() {
+    ComponentDto projectDto = dbTester.components().insertPublicProject();
+    String branchName = randomAlphanumeric(248);
+    ComponentDto componentDto = dbTester.components().insertProjectBranch(projectDto, b -> b.setKey(branchName));
+    Branch branch = mock(Branch.class);
+    when(branch.getName()).thenReturn(branchName);
+    when(branch.isMain()).thenReturn(false);
+    when(branch.generateKey(any(), any())).thenReturn(componentDto.getKey());
+    analysisMetadataHolder.setRootComponentRef(ROOT_REF)
+      .setAnalysisDate(ANALYSIS_DATE)
+      .setProject(Project.from(projectDto))
+      .setBranch(branch);
+    BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder);
+    reportReader.putComponent(component(ROOT_REF, PROJECT, componentDto.getKey()));
+
+    underTest.execute(new TestComputationStepContext());
+
+    verifyComponentByRef(ROOT_REF, componentDto.getKey(), analysisMetadataHolder.getProject().getName(), componentDto.uuid());
+  }
+
+  @Test
+  public void generate_keys_when_using_main_branch() {
+    setAnalysisMetadataHolder();
+    BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder);
+    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF));
+    reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1));
+
+    underTest.execute(new TestComputationStepContext());
+
+    verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, analysisMetadataHolder.getProject().getName(), null);
+    verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, REPORT_DIR_PATH_1);
+    verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, REPORT_FILE_NAME_1, null);
+  }
+
+  @Test
+  public void compute_keys_and_uuids_on_project_having_module_and_directory() {
+    setAnalysisMetadataHolder();
+    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF, FILE_2_REF));
+    reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1));
+    reportReader.putComponent(componentWithPath(FILE_2_REF, FILE, REPORT_FILE_PATH_2));
+
+    underTest.execute(new TestComputationStepContext());
+
+    verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, analysisMetadataHolder.getProject().getName());
+    verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, "dir1");
+    verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, REPORT_FILE_NAME_1);
+    verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_2, "dir2");
+    verifyComponentByRef(FILE_2_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_2, "File2.java");
+  }
+
+  @Test
+  public void compute_keys_and_uuids_on_multi_modules() {
+    setAnalysisMetadataHolder();
+    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF));
+    reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1));
+
+    underTest.execute(new TestComputationStepContext());
+
+    verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, analysisMetadataHolder.getProject().getName());
+    verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, REPORT_DIR_PATH_1);
+    verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, REPORT_FILE_NAME_1);
+  }
+
+  @Test
+  public void set_no_base_project_snapshot_when_no_snapshot() {
+    setAnalysisMetadataHolder();
+    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY));
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(analysisMetadataHolder.isFirstAnalysis()).isTrue();
+  }
+
+  @Test
+  public void set_no_base_project_snapshot_when_no_last_snapshot() {
+    setAnalysisMetadataHolder();
+    ComponentDto project = insertComponent(newPrivateProjectDto("ABCD").setKey(REPORT_PROJECT_KEY));
+    insertSnapshot(newAnalysis(project).setLast(false));
+
+    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY));
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(analysisMetadataHolder.isFirstAnalysis()).isTrue();
+  }
+
+  @Test
+  public void set_base_project_snapshot_when_last_snapshot_exist() {
+    setAnalysisMetadataHolder();
+    ComponentDto project = insertComponent(newPrivateProjectDto("ABCD").setKey(REPORT_PROJECT_KEY));
+    insertSnapshot(newAnalysis(project).setLast(true));
+
+    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY));
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(analysisMetadataHolder.isFirstAnalysis()).isFalse();
+  }
+
+  @Test
+  public void set_projectVersion_to_not_provided_when_not_set_on_first_analysis() {
+    setAnalysisMetadataHolder();
+    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY));
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(treeRootHolder.getReportTreeRoot().getProjectAttributes().getProjectVersion()).isEqualTo("not provided");
+  }
+
+  @Test
+  @UseDataProvider("oneParameterNullNonNullCombinations")
+  public void set_projectVersion_to_previous_analysis_when_not_set(@Nullable String previousAnalysisProjectVersion) {
+    setAnalysisMetadataHolder();
+    ComponentDto project = insertComponent(newPrivateProjectDto("ABCD").setKey(REPORT_PROJECT_KEY));
+    insertSnapshot(newAnalysis(project).setProjectVersion(previousAnalysisProjectVersion).setLast(true));
+    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY));
+
+    underTest.execute(new TestComputationStepContext());
+
+    String projectVersion = treeRootHolder.getReportTreeRoot().getProjectAttributes().getProjectVersion();
+    if (previousAnalysisProjectVersion == null) {
+      assertThat(projectVersion).isEqualTo("not provided");
+    } else {
+      assertThat(projectVersion).isEqualTo(previousAnalysisProjectVersion);
+    }
+  }
+
+  @Test
+  public void set_projectVersion_when_it_is_set_on_first_analysis() {
+    String scannerProjectVersion = randomAlphabetic(12);
+    setAnalysisMetadataHolder();
+    reportReader.setMetadata(createReportMetadata(scannerProjectVersion, NO_SCANNER_BUILD_STRING));
+    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY));
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(treeRootHolder.getReportTreeRoot().getProjectAttributes().getProjectVersion())
+      .isEqualTo(scannerProjectVersion);
+  }
+
+  @Test
+  @UseDataProvider("oneParameterNullNonNullCombinations")
+  public void set_projectVersion_when_it_is_set_on_later_analysis(@Nullable String previousAnalysisProjectVersion) {
+    String scannerProjectVersion = randomAlphabetic(12);
+    setAnalysisMetadataHolder();
+    reportReader.setMetadata(createReportMetadata(scannerProjectVersion, NO_SCANNER_BUILD_STRING));
+    ComponentDto project = insertComponent(newPrivateProjectDto("ABCD").setKey(REPORT_PROJECT_KEY));
+    insertSnapshot(newAnalysis(project).setProjectVersion(previousAnalysisProjectVersion).setLast(true));
+    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY));
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(treeRootHolder.getReportTreeRoot().getProjectAttributes().getProjectVersion())
+      .isEqualTo(scannerProjectVersion);
+  }
+
+  @Test
+  @UseDataProvider("oneParameterNullNonNullCombinations")
+  public void set_buildString(@Nullable String buildString) {
+    String projectVersion = randomAlphabetic(7);
+    setAnalysisMetadataHolder();
+    reportReader.setMetadata(createReportMetadata(projectVersion, buildString));
+    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY));
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(treeRootHolder.getReportTreeRoot().getProjectAttributes().getBuildString()).isEqualTo(Optional.ofNullable(buildString));
+  }
+
+  @DataProvider
+  public static Object[][] oneParameterNullNonNullCombinations() {
+    return new Object[][] {
+      {null},
+      {randomAlphabetic(7)}
+    };
+  }
+
+  private void verifyComponent(Component component, Component.Type type, @Nullable Integer componentRef, int size) {
+    assertThat(component.getType()).isEqualTo(type);
+    assertThat(component.getReportAttributes().getRef()).isEqualTo(componentRef);
+    assertThat(component.getChildren()).hasSize(size);
+  }
+
+  private void verifyComponentByRef(int ref, String key, String shortName) {
+    verifyComponentByRef(ref, key, shortName, null);
+  }
+
+  private void verifyComponentByKey(String key, String shortName) {
+    verifyComponentByKey(key, shortName, null);
+  }
+
+  private void verifyComponentByKey(String key, String shortName, @Nullable String uuid) {
+    Map<String, Component> componentsByKey = indexAllComponentsInTreeByKey(treeRootHolder.getRoot());
+    Component component = componentsByKey.get(key);
+    assertThat(component.getKey()).isEqualTo(key);
+    assertThat(component.getReportAttributes().getRef()).isNull();
+    assertThat(component.getShortName()).isEqualTo(shortName);
+    if (uuid != null) {
+      assertThat(component.getUuid()).isEqualTo(uuid);
+    } else {
+      assertThat(component.getUuid()).isNotNull();
+    }
+  }
+
+  private void verifyComponentByRef(int ref, String key, String shortName, @Nullable String uuid) {
+    Map<Integer, Component> componentsByRef = indexAllComponentsInTreeByRef(treeRootHolder.getRoot());
+    Component component = componentsByRef.get(ref);
+    assertThat(component.getKey()).isEqualTo(key);
+    assertThat(component.getShortName()).isEqualTo(shortName);
+    if (uuid != null) {
+      assertThat(component.getUuid()).isEqualTo(uuid);
+    } else {
+      assertThat(component.getUuid()).isNotNull();
+    }
+  }
+
+  private static ScannerReport.Component component(int componentRef, ComponentType componentType, String key, int... children) {
+    return component(componentRef, componentType, key, FileStatus.CHANGED, null, children);
+  }
+
+  private static ScannerReport.Component unchangedComponentWithPath(int componentRef, ComponentType componentType, String path, int... children) {
+    return component(componentRef, componentType, REPORT_PROJECT_KEY + ":" + path, FileStatus.SAME, path, children);
+  }
+
+  private static ScannerReport.Component componentWithPath(int componentRef, ComponentType componentType, String path, int... children) {
+    return component(componentRef, componentType, REPORT_PROJECT_KEY + ":" + path, FileStatus.CHANGED, path, children);
+  }
+
+  private static ScannerReport.Component component(int componentRef, ComponentType componentType, String key, FileStatus status, @Nullable String path, int... children) {
+    ScannerReport.Component.Builder builder = ScannerReport.Component.newBuilder()
+      .setType(componentType)
+      .setRef(componentRef)
+      .setName(key)
+      .setStatus(status)
+      .setLines(1)
+      .setKey(key);
+    if (path != null) {
+      builder.setProjectRelativePath(path);
+    }
+    for (int child : children) {
+      builder.addChildRef(child);
+    }
+    return builder.build();
+  }
+
+  private static Map<Integer, Component> indexAllComponentsInTreeByRef(Component root) {
+    Map<Integer, Component> componentsByRef = new HashMap<>();
+    feedComponentByRef(root, componentsByRef);
+    return componentsByRef;
+  }
+
+  private static Map<String, Component> indexAllComponentsInTreeByKey(Component root) {
+    Map<String, Component> componentsByKey = new HashMap<>();
+    feedComponentByKey(root, componentsByKey);
+    return componentsByKey;
+  }
+
+  private static void feedComponentByKey(Component component, Map<String, Component> map) {
+    map.put(component.getKey(), component);
+    for (Component child : component.getChildren()) {
+      feedComponentByKey(child, map);
+    }
+  }
+
+  private static void feedComponentByRef(Component component, Map<Integer, Component> map) {
+    if (component.getReportAttributes().getRef() != null) {
+      map.put(component.getReportAttributes().getRef(), component);
+    }
+    for (Component child : component.getChildren()) {
+      feedComponentByRef(child, map);
+    }
+  }
+
+  private ComponentDto insertComponent(ComponentDto component) {
+    return dbTester.components().insertComponent(component);
+  }
+
+  private SnapshotDto insertSnapshot(SnapshotDto snapshot) {
+    dbClient.snapshotDao().insert(dbTester.getSession(), snapshot);
+    dbTester.getSession().commit();
+    return snapshot;
+  }
+
+  private void setAnalysisMetadataHolder() {
+    setAnalysisMetadataHolder(false);
+  }
+
+  private void setAnalysisMetadataHolder(boolean isPr) {
+    Branch branch = isPr ? new PrBranch(DEFAULT_MAIN_BRANCH_NAME) : new DefaultBranchImpl(DEFAULT_MAIN_BRANCH_NAME);
+    analysisMetadataHolder.setRootComponentRef(ROOT_REF)
+      .setAnalysisDate(ANALYSIS_DATE)
+      .setBranch(branch)
+      .setProject(Project.from(newPrivateProjectDto().setKey(REPORT_PROJECT_KEY).setName(REPORT_PROJECT_KEY)));
+  }
+
+  public static ScannerReport.Metadata createReportMetadata(@Nullable String projectVersion, @Nullable String buildString) {
+    ScannerReport.Metadata.Builder builder = ScannerReport.Metadata.newBuilder()
+      .setProjectKey(REPORT_PROJECT_KEY)
+      .setRootComponentRef(ROOT_REF);
+    ofNullable(projectVersion).ifPresent(builder::setProjectVersion);
+    ofNullable(buildString).ifPresent(builder::setBuildString);
+    return builder.build();
+  }
+
+  private static class PrBranch extends DefaultBranchImpl {
+    public PrBranch(String branch) {
+      super(branch);
+    }
+
+    @Override
+    public BranchType getType() {
+      return BranchType.PULL_REQUEST;
+    }
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/LoadCrossProjectDuplicationsRepositoryStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/LoadCrossProjectDuplicationsRepositoryStepIT.java
new file mode 100644 (file)
index 0000000..1816523
--- /dev/null
@@ -0,0 +1,346 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.ce.task.projectanalysis.step;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.projectanalysis.analysis.Analysis;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.FileAttributes;
+import org.sonar.ce.task.projectanalysis.component.ReportComponent;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
+import org.sonar.ce.task.projectanalysis.duplication.CrossProjectDuplicationStatusHolder;
+import org.sonar.ce.task.projectanalysis.duplication.IntegrateCrossProjectDuplications;
+import org.sonar.ce.task.step.ComputationStep;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.component.SnapshotTesting;
+import org.sonar.db.duplication.DuplicationUnitDto;
+import org.sonar.duplications.block.Block;
+import org.sonar.duplications.block.ByteArray;
+import org.sonar.scanner.protocol.output.ScannerReport;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.ce.task.projectanalysis.component.Component.Type.FILE;
+import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT;
+
+public class LoadCrossProjectDuplicationsRepositoryStepIT {
+
+
+  private static final String XOO_LANGUAGE = "xoo";
+  private static final int PROJECT_REF = 1;
+  private static final int FILE_REF = 2;
+  private static final String CURRENT_FILE_KEY = "FILE_KEY";
+
+  private static final Component CURRENT_FILE = ReportComponent.builder(FILE, FILE_REF)
+    .setKey(CURRENT_FILE_KEY)
+    .setFileAttributes(new FileAttributes(false, XOO_LANGUAGE, 1))
+    .build();
+
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule().setRoot(
+    ReportComponent.builder(PROJECT, PROJECT_REF)
+      .addChildren(CURRENT_FILE).build());
+
+  @Rule
+  public BatchReportReaderRule batchReportReader = new BatchReportReaderRule();
+
+  @Rule
+  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
+
+  private CrossProjectDuplicationStatusHolder crossProjectDuplicationStatusHolder = mock(CrossProjectDuplicationStatusHolder.class);
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private DbClient dbClient = dbTester.getDbClient();
+  private DbSession dbSession = dbTester.getSession();
+  private IntegrateCrossProjectDuplications integrateCrossProjectDuplications = mock(IntegrateCrossProjectDuplications.class);
+  private Analysis baseProjectAnalysis;
+
+  private ComputationStep underTest = new LoadCrossProjectDuplicationsRepositoryStep(treeRootHolder, batchReportReader, analysisMetadataHolder, crossProjectDuplicationStatusHolder,
+    integrateCrossProjectDuplications, dbClient);
+
+  @Before
+  public void setUp() {
+    ComponentDto project = ComponentTesting.newPrivateProjectDto();
+    dbTester.components().insertComponent(project);
+    SnapshotDto projectSnapshot = SnapshotTesting.newAnalysis(project);
+    dbClient.snapshotDao().insert(dbSession, projectSnapshot);
+    dbSession.commit();
+
+    baseProjectAnalysis = new Analysis.Builder()
+      .setUuid(projectSnapshot.getUuid())
+      .setCreatedAt(projectSnapshot.getCreatedAt())
+      .build();
+  }
+
+  @Test
+  public void call_compute_cpd_on_one_duplication() {
+    when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true);
+    analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis);
+
+    ComponentDto otherProject = createProject("OTHER_PROJECT_KEY");
+    SnapshotDto otherProjectSnapshot = createProjectSnapshot(otherProject);
+
+    ComponentDto otherFile = createFile("OTHER_FILE_KEY", otherProject);
+
+    String hash = "a8998353e96320ec";
+    DuplicationUnitDto duplicate = new DuplicationUnitDto()
+      .setHash(hash)
+      .setStartLine(40)
+      .setEndLine(55)
+      .setIndexInFile(0)
+      .setAnalysisUuid(otherProjectSnapshot.getUuid())
+      .setComponentUuid(otherFile.uuid());
+    dbClient.duplicationDao().insert(dbSession, duplicate);
+    dbSession.commit();
+
+    ScannerReport.CpdTextBlock originBlock = ScannerReport.CpdTextBlock.newBuilder()
+      .setHash(hash)
+      .setStartLine(30)
+      .setEndLine(45)
+      .setStartTokenIndex(0)
+      .setEndTokenIndex(10)
+      .build();
+    batchReportReader.putDuplicationBlocks(FILE_REF, singletonList(originBlock));
+
+    underTest.execute(new TestComputationStepContext());
+
+    verify(integrateCrossProjectDuplications).computeCpd(CURRENT_FILE,
+      singletonList(
+        new Block.Builder()
+          .setResourceId(CURRENT_FILE_KEY)
+          .setBlockHash(new ByteArray(hash))
+          .setIndexInFile(0)
+          .setLines(originBlock.getStartLine(), originBlock.getEndLine())
+          .setUnit(originBlock.getStartTokenIndex(), originBlock.getEndTokenIndex())
+          .build()),
+      singletonList(
+        new Block.Builder()
+          .setResourceId(otherFile.getKey())
+          .setBlockHash(new ByteArray(hash))
+          .setIndexInFile(duplicate.getIndexInFile())
+          .setLines(duplicate.getStartLine(), duplicate.getEndLine())
+          .build()));
+  }
+
+  @Test
+  public void call_compute_cpd_on_many_duplication() {
+    when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true);
+    analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis);
+
+    ComponentDto otherProject = createProject("OTHER_PROJECT_KEY");
+    SnapshotDto otherProjectSnapshot = createProjectSnapshot(otherProject);
+
+    ComponentDto otherFile = createFile("OTHER_FILE_KEY", otherProject);
+
+    ScannerReport.CpdTextBlock originBlock1 = ScannerReport.CpdTextBlock.newBuilder()
+      .setHash("a8998353e96320ec")
+      .setStartLine(30)
+      .setEndLine(45)
+      .setStartTokenIndex(0)
+      .setEndTokenIndex(10)
+      .build();
+    ScannerReport.CpdTextBlock originBlock2 = ScannerReport.CpdTextBlock.newBuilder()
+      .setHash("b1234353e96320ff")
+      .setStartLine(10)
+      .setEndLine(25)
+      .setStartTokenIndex(5)
+      .setEndTokenIndex(15)
+      .build();
+    batchReportReader.putDuplicationBlocks(FILE_REF, asList(originBlock1, originBlock2));
+
+    DuplicationUnitDto duplicate1 = new DuplicationUnitDto()
+      .setHash(originBlock1.getHash())
+      .setStartLine(40)
+      .setEndLine(55)
+      .setIndexInFile(0)
+      .setAnalysisUuid(otherProjectSnapshot.getUuid())
+      .setComponentUuid(otherFile.uuid());
+
+    DuplicationUnitDto duplicate2 = new DuplicationUnitDto()
+      .setHash(originBlock2.getHash())
+      .setStartLine(20)
+      .setEndLine(35)
+      .setIndexInFile(1)
+      .setAnalysisUuid(otherProjectSnapshot.getUuid())
+      .setComponentUuid(otherFile.uuid());
+    dbClient.duplicationDao().insert(dbSession, duplicate1);
+    dbClient.duplicationDao().insert(dbSession, duplicate2);
+    dbSession.commit();
+
+    underTest.execute(new TestComputationStepContext());
+
+    ArgumentCaptor<List<Block>> originBlocks = ArgumentCaptor.forClass(List.class);
+    ArgumentCaptor<List<Block>> duplicationBlocks = ArgumentCaptor.forClass(List.class);
+
+    verify(integrateCrossProjectDuplications).computeCpd(eq(CURRENT_FILE), originBlocks.capture(), duplicationBlocks.capture());
+
+    Map<Integer, Block> originBlocksByIndex = blocksByIndexInFile(originBlocks.getValue());
+    assertThat(originBlocksByIndex).containsExactly(
+      Map.entry(0, new Block.Builder()
+        .setResourceId(CURRENT_FILE_KEY)
+        .setBlockHash(new ByteArray(originBlock1.getHash()))
+        .setIndexInFile(0)
+        .setLines(originBlock1.getStartLine(), originBlock1.getEndLine())
+        .setUnit(originBlock1.getStartTokenIndex(), originBlock1.getEndTokenIndex())
+        .build()),
+      Map.entry(1, new Block.Builder()
+        .setResourceId(CURRENT_FILE_KEY)
+        .setBlockHash(new ByteArray(originBlock2.getHash()))
+        .setIndexInFile(1)
+        .setLines(originBlock2.getStartLine(), originBlock2.getEndLine())
+        .setUnit(originBlock2.getStartTokenIndex(), originBlock2.getEndTokenIndex())
+        .build()));
+
+    Map<Integer, Block> duplicationBlocksByIndex = blocksByIndexInFile(duplicationBlocks.getValue());
+    assertThat(duplicationBlocksByIndex).containsExactly(
+      Map.entry(0, new Block.Builder()
+        .setResourceId(otherFile.getKey())
+        .setBlockHash(new ByteArray(originBlock1.getHash()))
+        .setIndexInFile(duplicate1.getIndexInFile())
+        .setLines(duplicate1.getStartLine(), duplicate1.getEndLine())
+        .build()),
+      Map.entry(1, new Block.Builder()
+        .setResourceId(otherFile.getKey())
+        .setBlockHash(new ByteArray(originBlock2.getHash()))
+        .setIndexInFile(duplicate2.getIndexInFile())
+        .setLines(duplicate2.getStartLine(), duplicate2.getEndLine())
+        .build()));
+  }
+
+  @Test
+  public void nothing_to_do_when_cross_project_duplication_is_disabled() {
+    when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(false);
+    analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis);
+
+    ComponentDto otherProject = createProject("OTHER_PROJECT_KEY");
+    SnapshotDto otherProjectSnapshot = createProjectSnapshot(otherProject);
+
+    ComponentDto otherFIle = createFile("OTHER_FILE_KEY", otherProject);
+
+    String hash = "a8998353e96320ec";
+    DuplicationUnitDto duplicate = new DuplicationUnitDto()
+      .setHash(hash)
+      .setStartLine(40)
+      .setEndLine(55)
+      .setIndexInFile(0)
+      .setAnalysisUuid(otherProjectSnapshot.getUuid())
+      .setComponentUuid(otherFIle.uuid());
+    dbClient.duplicationDao().insert(dbSession, duplicate);
+    dbSession.commit();
+
+    ScannerReport.CpdTextBlock originBlock = ScannerReport.CpdTextBlock.newBuilder()
+      .setHash(hash)
+      .setStartLine(30)
+      .setEndLine(45)
+      .setStartTokenIndex(0)
+      .setEndTokenIndex(10)
+      .build();
+    batchReportReader.putDuplicationBlocks(FILE_REF, singletonList(originBlock));
+
+    underTest.execute(new TestComputationStepContext());
+
+    verifyNoInteractions(integrateCrossProjectDuplications);
+  }
+
+  @Test
+  public void nothing_to_do_when_no_cpd_text_blocks_found() {
+    when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true);
+    analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis);
+
+    batchReportReader.putDuplicationBlocks(FILE_REF, Collections.emptyList());
+
+    underTest.execute(new TestComputationStepContext());
+
+    verifyNoInteractions(integrateCrossProjectDuplications);
+  }
+
+  @Test
+  public void nothing_to_do_when_cpd_text_blocks_exists_but_no_duplicated_found() {
+    when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true);
+    analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis);
+
+    ScannerReport.CpdTextBlock originBlock = ScannerReport.CpdTextBlock.newBuilder()
+      .setHash("a8998353e96320ec")
+      .setStartLine(30)
+      .setEndLine(45)
+      .setStartTokenIndex(0)
+      .setEndTokenIndex(10)
+      .build();
+    batchReportReader.putDuplicationBlocks(FILE_REF, singletonList(originBlock));
+
+    underTest.execute(new TestComputationStepContext());
+
+    verifyNoInteractions(integrateCrossProjectDuplications);
+  }
+
+  private ComponentDto createProject(String projectKey) {
+    ComponentDto project = ComponentTesting.newPrivateProjectDto().setKey(projectKey);
+    return dbTester.components().insertComponent(project);
+  }
+
+  private SnapshotDto createProjectSnapshot(ComponentDto project) {
+    SnapshotDto projectSnapshot = SnapshotTesting.newAnalysis(project);
+    dbClient.snapshotDao().insert(dbSession, projectSnapshot);
+    dbSession.commit();
+    return projectSnapshot;
+  }
+
+  private ComponentDto createFile(String fileKey, ComponentDto project) {
+    ComponentDto file = ComponentTesting.newFileDto(project, null)
+      .setKey(fileKey)
+      .setLanguage(XOO_LANGUAGE);
+    dbClient.componentDao().insert(dbSession, file);
+    dbSession.commit();
+    return file;
+  }
+
+  private static Map<Integer, Block> blocksByIndexInFile(List<Block> blocks) {
+    Map<Integer, Block> blocksByIndexInFile = new HashMap<>();
+    for (Block block : blocks) {
+      blocksByIndexInFile.put(block.getIndexInFile(), block);
+    }
+    return blocksByIndexInFile;
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisPropertiesStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisPropertiesStepIT.java
new file mode 100644 (file)
index 0000000..0430fa3
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.ce.task.projectanalysis.step;
+
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
+import org.sonar.ce.task.projectanalysis.batch.BatchReportReader;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.core.util.CloseableIterator;
+import org.sonar.core.util.UuidFactoryFast;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.AnalysisPropertyDto;
+import org.sonar.scanner.protocol.output.ScannerReport;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class PersistAnalysisPropertiesStepIT {
+  private static final String SNAPSHOT_UUID = randomAlphanumeric(40);
+  private static final String SMALL_VALUE1 = randomAlphanumeric(50);
+  private static final String SMALL_VALUE2 = randomAlphanumeric(50);
+  private static final String SMALL_VALUE3 = randomAlphanumeric(50);
+  private static final String BIG_VALUE = randomAlphanumeric(5000);
+  private static final String VALUE_PREFIX_FOR_PR_PROPERTIES = "pr_";
+  private static final List<ScannerReport.ContextProperty> PROPERTIES = Arrays.asList(
+    newContextProperty("key1", "value1"),
+    newContextProperty("key2", "value1"),
+    newContextProperty("sonar.analysis", SMALL_VALUE1),
+    newContextProperty("sonar.analysis.branch", SMALL_VALUE2),
+    newContextProperty("sonar.analysis.empty_string", ""),
+    newContextProperty("sonar.analysis.big_value", BIG_VALUE),
+    newContextProperty("sonar.analysis.", SMALL_VALUE3),
+    newContextProperty("sonar.pullrequest", VALUE_PREFIX_FOR_PR_PROPERTIES + SMALL_VALUE1),
+    newContextProperty("sonar.pullrequest.branch", VALUE_PREFIX_FOR_PR_PROPERTIES + SMALL_VALUE2),
+    newContextProperty("sonar.pullrequest.empty_string", ""),
+    newContextProperty("sonar.pullrequest.big_value", VALUE_PREFIX_FOR_PR_PROPERTIES + BIG_VALUE),
+    newContextProperty("sonar.pullrequest.", VALUE_PREFIX_FOR_PR_PROPERTIES + SMALL_VALUE3));
+  private static final String SCM_REV_ID = "sha1";
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private BatchReportReader batchReportReader = mock(BatchReportReader.class);
+  private AnalysisMetadataHolder analysisMetadataHolder = mock(AnalysisMetadataHolder.class);
+  private PersistAnalysisPropertiesStep underTest = new PersistAnalysisPropertiesStep(dbTester.getDbClient(), analysisMetadataHolder, batchReportReader,
+    UuidFactoryFast.getInstance());
+
+  @Test
+  public void persist_should_stores_sonarDotAnalysisDot_and_sonarDotPullRequestDot_properties() {
+    when(batchReportReader.readContextProperties()).thenReturn(CloseableIterator.from(PROPERTIES.iterator()));
+    when(analysisMetadataHolder.getUuid()).thenReturn(SNAPSHOT_UUID);
+    when(analysisMetadataHolder.getScmRevision()).thenReturn(Optional.of(SCM_REV_ID));
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbTester.countRowsOfTable("analysis_properties")).isEqualTo(8);
+    List<AnalysisPropertyDto> propertyDtos = dbTester.getDbClient()
+      .analysisPropertiesDao().selectByAnalysisUuid(dbTester.getSession(), SNAPSHOT_UUID);
+
+    assertThat(propertyDtos)
+      .extracting(AnalysisPropertyDto::getAnalysisUuid, AnalysisPropertyDto::getKey, AnalysisPropertyDto::getValue)
+      .containsExactlyInAnyOrder(
+        tuple(SNAPSHOT_UUID, "sonar.analysis.branch", SMALL_VALUE2),
+        tuple(SNAPSHOT_UUID, "sonar.analysis.empty_string", ""),
+        tuple(SNAPSHOT_UUID, "sonar.analysis.big_value", BIG_VALUE),
+        tuple(SNAPSHOT_UUID, "sonar.analysis.", SMALL_VALUE3),
+        tuple(SNAPSHOT_UUID, "sonar.pullrequest.branch", VALUE_PREFIX_FOR_PR_PROPERTIES + SMALL_VALUE2),
+        tuple(SNAPSHOT_UUID, "sonar.pullrequest.empty_string", ""),
+        tuple(SNAPSHOT_UUID, "sonar.pullrequest.big_value", VALUE_PREFIX_FOR_PR_PROPERTIES + BIG_VALUE),
+        tuple(SNAPSHOT_UUID, "sonar.pullrequest.", VALUE_PREFIX_FOR_PR_PROPERTIES + SMALL_VALUE3));
+  }
+
+  @Test
+  public void persist_filtering_of_properties_is_case_sensitive() {
+    when(analysisMetadataHolder.getScmRevision()).thenReturn(Optional.of(SCM_REV_ID));
+    when(batchReportReader.readContextProperties()).thenReturn(CloseableIterator.from(ImmutableList.of(
+      newContextProperty("sonar.ANALYSIS.foo", "foo"),
+      newContextProperty("sonar.anaLysis.bar", "bar"),
+      newContextProperty("sonar.anaLYSIS.doo", "doh"),
+      newContextProperty("sonar.PULLREQUEST.foo", "foo"),
+      newContextProperty("sonar.pullRequest.bar", "bar"),
+      newContextProperty("sonar.pullREQUEST.doo", "doh")).iterator()));
+    when(analysisMetadataHolder.getUuid()).thenReturn(SNAPSHOT_UUID);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbTester.countRowsOfTable("analysis_properties")).isZero();
+  }
+
+  @Test
+  public void persist_should_store_nothing_if_there_are_no_context_properties() {
+    when(analysisMetadataHolder.getScmRevision()).thenReturn(Optional.of(SCM_REV_ID));
+    when(batchReportReader.readContextProperties()).thenReturn(CloseableIterator.emptyCloseableIterator());
+    when(analysisMetadataHolder.getUuid()).thenReturn(SNAPSHOT_UUID);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbTester.countRowsOfTable("analysis_properties")).isZero();
+  }
+
+  @Test
+  public void verify_description_value() {
+    assertThat(underTest.getDescription()).isEqualTo("Persist analysis properties");
+  }
+
+  private static ScannerReport.ContextProperty newContextProperty(String key, String value) {
+    return ScannerReport.ContextProperty.newBuilder()
+      .setKey(key)
+      .setValue(value)
+      .build();
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistCrossProjectDuplicationIndexStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistCrossProjectDuplicationIndexStepIT.java
new file mode 100644 (file)
index 0000000..003826e
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.ce.task.projectanalysis.step;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.projectanalysis.analysis.Analysis;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.ReportComponent;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
+import org.sonar.ce.task.projectanalysis.duplication.CrossProjectDuplicationStatusHolder;
+import org.sonar.ce.task.step.ComputationStep;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.scanner.protocol.output.ScannerReport;
+
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class PersistCrossProjectDuplicationIndexStepIT {
+
+  private static final int FILE_1_REF = 2;
+  private static final int FILE_2_REF = 3;
+  private static final String FILE_2_UUID = "file2";
+
+  private static final Component FILE_1 = ReportComponent.builder(Component.Type.FILE, FILE_1_REF).build();
+  private static final Component FILE_2 = ReportComponent.builder(Component.Type.FILE, FILE_2_REF)
+    .setStatus(Component.Status.SAME).setUuid(FILE_2_UUID).build();
+
+  private static final Component PROJECT = ReportComponent.builder(Component.Type.PROJECT, 1)
+    .addChildren(FILE_1)
+    .addChildren(FILE_2)
+    .build();
+
+  private static final ScannerReport.CpdTextBlock CPD_TEXT_BLOCK = ScannerReport.CpdTextBlock.newBuilder()
+    .setHash("a8998353e96320ec")
+    .setStartLine(30)
+    .setEndLine(45)
+    .build();
+  private static final String ANALYSIS_UUID = "analysis uuid";
+  private static final String BASE_ANALYSIS_UUID = "base analysis uuid";
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  @Rule
+  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule().setRoot(PROJECT);
+  @Rule
+  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
+
+  private CrossProjectDuplicationStatusHolder crossProjectDuplicationStatusHolder = mock(CrossProjectDuplicationStatusHolder.class);
+  private Analysis baseAnalysis = mock(Analysis.class);
+  private DbClient dbClient = dbTester.getDbClient();
+
+  private ComputationStep underTest;
+
+  @Before
+  public void setUp() {
+    when(baseAnalysis.getUuid()).thenReturn(BASE_ANALYSIS_UUID);
+    analysisMetadataHolder.setUuid(ANALYSIS_UUID);
+    analysisMetadataHolder.setBaseAnalysis(baseAnalysis);
+    underTest = new PersistCrossProjectDuplicationIndexStep(crossProjectDuplicationStatusHolder, dbClient, treeRootHolder, analysisMetadataHolder, reportReader);
+  }
+
+  @Test
+  public void persist_cpd_text_block() {
+    when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true);
+    reportReader.putDuplicationBlocks(FILE_1_REF, singletonList(CPD_TEXT_BLOCK));
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    Map<String, Object> dto = dbTester.selectFirst("select HASH, START_LINE, END_LINE, INDEX_IN_FILE, COMPONENT_UUID, ANALYSIS_UUID from duplications_index");
+    assertThat(dto)
+      .containsEntry("HASH", CPD_TEXT_BLOCK.getHash())
+      .containsEntry("START_LINE", 30L)
+      .containsEntry("END_LINE", 45L)
+      .containsEntry("INDEX_IN_FILE", 0L)
+      .containsEntry("COMPONENT_UUID", FILE_1.getUuid())
+      .containsEntry("ANALYSIS_UUID", ANALYSIS_UUID);
+    context.getStatistics().assertValue("inserts", 1);
+  }
+
+  @Test
+  public void persist_many_cpd_text_blocks() {
+    when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true);
+    reportReader.putDuplicationBlocks(FILE_1_REF, Arrays.asList(
+      CPD_TEXT_BLOCK,
+      ScannerReport.CpdTextBlock.newBuilder()
+        .setHash("b1234353e96320ff")
+        .setStartLine(20)
+        .setEndLine(15)
+        .build()));
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    List<Map<String, Object>> dtos = dbTester.select("select HASH, START_LINE, END_LINE, INDEX_IN_FILE, COMPONENT_UUID, ANALYSIS_UUID from duplications_index");
+    assertThat(dtos).extracting("HASH").containsOnly(CPD_TEXT_BLOCK.getHash(), "b1234353e96320ff");
+    assertThat(dtos).extracting("START_LINE").containsOnly(30L, 20L);
+    assertThat(dtos).extracting("END_LINE").containsOnly(45L, 15L);
+    assertThat(dtos).extracting("INDEX_IN_FILE").containsOnly(0L, 1L);
+    assertThat(dtos).extracting("COMPONENT_UUID").containsOnly(FILE_1.getUuid());
+    assertThat(dtos).extracting("ANALYSIS_UUID").containsOnly(ANALYSIS_UUID);
+    context.getStatistics().assertValue("inserts", 2);
+  }
+
+  @Test
+  public void nothing_to_persist_when_no_cpd_text_blocks_in_report() {
+    when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true);
+    reportReader.putDuplicationBlocks(FILE_1_REF, Collections.emptyList());
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(dbTester.countRowsOfTable("duplications_index")).isZero();
+    context.getStatistics().assertValue("inserts", 0);
+  }
+
+  @Test
+  public void nothing_to_do_when_cross_project_duplication_is_disabled() {
+    when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(false);
+    reportReader.putDuplicationBlocks(FILE_1_REF, singletonList(CPD_TEXT_BLOCK));
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(dbTester.countRowsOfTable("duplications_index")).isZero();
+    context.getStatistics().assertValue("inserts", null);
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistEventsStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistEventsStepIT.java
new file mode 100644 (file)
index 0000000..b900fc3
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.ce.task.projectanalysis.step;
+
+import com.google.common.collect.ImmutableList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.ReportComponent;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
+import org.sonar.ce.task.projectanalysis.event.Event;
+import org.sonar.ce.task.projectanalysis.event.EventRepository;
+import org.sonar.ce.task.step.ComputationStep;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.core.util.UuidFactoryImpl;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.event.EventDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.ce.task.projectanalysis.component.Component.Type.DIRECTORY;
+import static org.sonar.ce.task.projectanalysis.component.Component.Type.FILE;
+import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT;
+import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
+import static org.sonar.db.event.EventDto.CATEGORY_ALERT;
+import static org.sonar.db.event.EventDto.CATEGORY_PROFILE;
+import static org.sonar.db.event.EventDto.CATEGORY_VERSION;
+
+public class PersistEventsStepIT extends BaseStepTest {
+
+  private static final long NOW = 1225630680000L;
+  private static final ReportComponent ROOT = builder(PROJECT, 1)
+    .setUuid("ABCD")
+    .setProjectVersion("version_1")
+    .addChildren(
+      builder(DIRECTORY, 2)
+        .setUuid("BCDE")
+        .addChildren(
+          builder(DIRECTORY, 3)
+            .setUuid("Q")
+            .addChildren(
+              builder(FILE, 4)
+                .setUuid("Z")
+                .build())
+            .build())
+        .build())
+    .build();
+  private static final String ANALYSIS_UUID = "uuid_1";
+
+  System2 system2 = mock(System2.class);
+
+  @Rule
+  public DbTester dbTester = DbTester.create(system2);
+  @Rule
+  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+
+  private Date someDate = new Date(150000000L);
+
+  private EventRepository eventRepository = mock(EventRepository.class);
+  private UuidFactory uuidFactory = UuidFactoryImpl.INSTANCE;
+
+  private PersistEventsStep underTest;
+
+  @Before
+  public void setup() {
+    analysisMetadataHolder.setAnalysisDate(someDate.getTime()).setUuid(ANALYSIS_UUID);
+    underTest = new PersistEventsStep(dbTester.getDbClient(), system2, treeRootHolder, analysisMetadataHolder, eventRepository, uuidFactory);
+    when(eventRepository.getEvents(any(Component.class))).thenReturn(Collections.emptyList());
+  }
+
+  @Override
+  protected ComputationStep step() {
+    return underTest;
+  }
+
+  @Test
+  public void create_version_event() {
+    when(system2.now()).thenReturn(NOW);
+    Component project = builder(PROJECT, 1)
+      .setUuid("ABCD")
+      .setProjectVersion("1.0")
+      .addChildren(
+        builder(DIRECTORY, 2)
+          .setUuid("BCDE")
+          .addChildren(
+            builder(DIRECTORY, 3)
+              .setUuid("Q")
+              .addChildren(
+                builder(FILE, 4)
+                  .setUuid("Z")
+                  .build())
+              .build())
+          .build())
+      .build();
+    treeRootHolder.setRoot(project);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbTester.countRowsOfTable(dbTester.getSession(), "events")).isOne();
+    List<EventDto> eventDtos = dbTester.getDbClient().eventDao().selectByComponentUuid(dbTester.getSession(), ROOT.getUuid());
+    assertThat(eventDtos).hasSize(1);
+    EventDto eventDto = eventDtos.iterator().next();
+    assertThat(eventDto.getComponentUuid()).isEqualTo(ROOT.getUuid());
+    assertThat(eventDto.getName()).isEqualTo("1.0");
+    assertThat(eventDto.getDescription()).isNull();
+    assertThat(eventDto.getCategory()).isEqualTo(CATEGORY_VERSION);
+    assertThat(eventDto.getData()).isNull();
+    assertThat(eventDto.getDate()).isEqualTo(analysisMetadataHolder.getAnalysisDate());
+    assertThat(eventDto.getCreatedAt()).isEqualTo(NOW);
+  }
+
+  @Test
+  public void persist_alert_events_on_root() {
+    when(system2.now()).thenReturn(NOW);
+    treeRootHolder.setRoot(ROOT);
+    Event alert = Event.createAlert("Failed", null, "Open issues > 0");
+    when(eventRepository.getEvents(ROOT)).thenReturn(ImmutableList.of(alert));
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbTester.countRowsOfTable(dbTester.getSession(), "events")).isEqualTo(2);
+    List<EventDto> eventDtos = dbTester.getDbClient().eventDao().selectByComponentUuid(dbTester.getSession(), ROOT.getUuid());
+    assertThat(eventDtos)
+      .extracting(EventDto::getCategory)
+      .containsOnly(CATEGORY_ALERT, CATEGORY_VERSION);
+    EventDto eventDto = eventDtos.stream().filter(t -> CATEGORY_ALERT.equals(t.getCategory())).findAny().get();
+    assertThat(eventDto.getComponentUuid()).isEqualTo(ROOT.getUuid());
+    assertThat(eventDto.getName()).isEqualTo(alert.getName());
+    assertThat(eventDto.getDescription()).isEqualTo(alert.getDescription());
+    assertThat(eventDto.getCategory()).isEqualTo(CATEGORY_ALERT);
+    assertThat(eventDto.getData()).isNull();
+    assertThat(eventDto.getDate()).isEqualTo(analysisMetadataHolder.getAnalysisDate());
+    assertThat(eventDto.getCreatedAt()).isEqualTo(NOW);
+  }
+
+  @Test
+  public void persist_profile_events_on_root() {
+    when(system2.now()).thenReturn(NOW);
+    treeRootHolder.setRoot(ROOT);
+    Event profile = Event.createProfile("foo", null, "bar");
+    when(eventRepository.getEvents(ROOT)).thenReturn(ImmutableList.of(profile));
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbTester.countRowsOfTable(dbTester.getSession(), "events")).isEqualTo(2);
+    List<EventDto> eventDtos = dbTester.getDbClient().eventDao().selectByComponentUuid(dbTester.getSession(), ROOT.getUuid());
+    assertThat(eventDtos)
+      .extracting(EventDto::getCategory)
+      .containsOnly(CATEGORY_PROFILE, CATEGORY_VERSION);
+    EventDto eventDto = eventDtos.stream().filter(t -> CATEGORY_PROFILE.equals(t.getCategory())).findAny().get();
+    assertThat(eventDto.getComponentUuid()).isEqualTo(ROOT.getUuid());
+    assertThat(eventDto.getName()).isEqualTo(profile.getName());
+    assertThat(eventDto.getDescription()).isEqualTo(profile.getDescription());
+    assertThat(eventDto.getCategory()).isEqualTo(EventDto.CATEGORY_PROFILE);
+    assertThat(eventDto.getData()).isNull();
+    assertThat(eventDto.getDate()).isEqualTo(analysisMetadataHolder.getAnalysisDate());
+    assertThat(eventDto.getCreatedAt()).isEqualTo(NOW);
+  }
+
+  @Test
+  public void keep_one_event_by_version() {
+    ComponentDto projectDto = dbTester.components().insertPublicProject();
+    EventDto[] existingEvents = new EventDto[] {
+      dbTester.events().insertEvent(newVersionEventDto(projectDto, 120_000_000L, "1.3-SNAPSHOT")),
+      dbTester.events().insertEvent(newVersionEventDto(projectDto, 130_000_000L, "1.4")),
+      dbTester.events().insertEvent(newVersionEventDto(projectDto, 140_000_000L, "1.5-SNAPSHOT"))
+    };
+
+    Component project = builder(PROJECT, 1)
+      .setUuid(projectDto.uuid())
+      .setProjectVersion("1.5-SNAPSHOT")
+      .addChildren(
+        builder(DIRECTORY, 2)
+          .setUuid("BCDE")
+          .addChildren(
+            builder(DIRECTORY, 3)
+              .setUuid("Q")
+              .addChildren(
+                builder(FILE, 4)
+                  .setUuid("Z")
+                  .build())
+              .build())
+          .build())
+      .build();
+    treeRootHolder.setRoot(project);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbTester.countRowsOfTable(dbTester.getSession(), "events")).isEqualTo(3);
+    List<EventDto> eventDtos = dbTester.getDbClient().eventDao().selectByComponentUuid(dbTester.getSession(), projectDto.uuid());
+    assertThat(eventDtos).hasSize(3);
+    assertThat(eventDtos)
+      .extracting(EventDto::getName)
+      .containsOnly("1.3-SNAPSHOT", "1.4", "1.5-SNAPSHOT");
+    assertThat(eventDtos)
+      .extracting(EventDto::getUuid)
+      .contains(existingEvents[0].getUuid(), existingEvents[1].getUuid())
+      .doesNotContain(existingEvents[2].getUuid());
+  }
+
+  private EventDto newVersionEventDto(ComponentDto project, long date, String name) {
+    return new EventDto().setUuid(uuidFactory.create()).setComponentUuid(project.uuid())
+      .setAnalysisUuid("analysis_uuid")
+      .setCategory(CATEGORY_VERSION)
+      .setName(name).setDate(date).setCreatedAt(date);
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/taskprocessor/IgnoreOrphanBranchStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/taskprocessor/IgnoreOrphanBranchStepIT.java
new file mode 100644 (file)
index 0000000..1ee3b62
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.ce.task.projectanalysis.taskprocessor;
+
+import java.util.Optional;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.CeTask;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.sonar.db.component.BranchType.BRANCH;
+
+public class IgnoreOrphanBranchStepIT {
+
+  private String BRANCH_UUID = "branch_uuid";
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private CeTask.Component component = new CeTask.Component(BRANCH_UUID, "component key", "component name");
+  private CeTask ceTask = new CeTask.Builder()
+    .setType("type")
+    .setUuid("uuid")
+    .setComponent(component)
+    .setMainComponent(component)
+    .build();
+
+  private DbClient dbClient = dbTester.getDbClient();
+  private IgnoreOrphanBranchStep underTest = new IgnoreOrphanBranchStep(ceTask, dbClient);
+
+  @Test
+  public void execute() {
+    BranchDto branchDto = new BranchDto()
+      .setBranchType(BRANCH)
+      .setKey("branchName")
+      .setUuid(BRANCH_UUID)
+      .setProjectUuid("project_uuid")
+      .setNeedIssueSync(true);
+    dbClient.branchDao().insert(dbTester.getSession(), branchDto);
+    dbTester.commit();
+
+    underTest.execute(() -> null);
+
+    Optional<BranchDto> branch = dbClient.branchDao().selectByUuid(dbTester.getSession(), BRANCH_UUID);
+    assertThat(branch.get().isNeedIssueSync()).isFalse();
+    assertThat(branch.get().isExcludeFromPurge()).isFalse();
+  }
+
+  @Test
+  public void execute_on_already_indexed_branch() {
+    BranchDto branchDto = new BranchDto()
+      .setBranchType(BRANCH)
+      .setKey("branchName")
+      .setUuid(BRANCH_UUID)
+      .setProjectUuid("project_uuid")
+      .setNeedIssueSync(false);
+    dbClient.branchDao().insert(dbTester.getSession(), branchDto);
+    dbTester.commit();
+
+    underTest.execute(() -> null);
+
+    Optional<BranchDto> branch = dbClient.branchDao().selectByUuid(dbTester.getSession(), BRANCH_UUID);
+    assertThat(branch.get().isNeedIssueSync()).isFalse();
+    assertThat(branch.get().isExcludeFromPurge()).isFalse();
+  }
+
+  @Test
+  public void fail_if_missing_main_component_in_task() {
+    CeTask ceTask = new CeTask.Builder()
+      .setType("type")
+      .setUuid("uuid")
+      .setComponent(null)
+      .setMainComponent(null)
+      .build();
+    IgnoreOrphanBranchStep underTest = new IgnoreOrphanBranchStep(ceTask, dbClient);
+
+    assertThatThrownBy(() -> underTest.execute(() -> null))
+      .isInstanceOf(UnsupportedOperationException.class)
+      .hasMessage("main component not found in task");
+  }
+
+  @Test
+  public void verify_step_description() {
+    assertThat(underTest.getDescription()).isEqualTo("Ignore orphan component");
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/component/ExportComponentsStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/component/ExportComponentsStepIT.java
new file mode 100644 (file)
index 0000000..4b3d12f
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.ce.task.projectexport.component;
+
+import com.google.common.collect.ImmutableSet;
+import com.sonarsource.governance.projectdump.protobuf.ProjectDump;
+import java.util.Date;
+import java.util.List;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.resources.Scopes;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.ce.task.projectexport.steps.DumpElement;
+import org.sonar.ce.task.projectexport.steps.FakeDumpWriter;
+import org.sonar.ce.task.projectexport.steps.ProjectHolder;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.db.component.ComponentDto.UUID_PATH_OF_ROOT;
+import static org.sonar.db.component.ComponentDto.UUID_PATH_SEPARATOR;
+
+public class ExportComponentsStepIT {
+
+  private static final String PROJECT_UUID = "PROJECT_UUID";
+  private static final ComponentDto PROJECT = new ComponentDto()
+    // no id yet
+    .setScope(Scopes.PROJECT)
+    .setQualifier(Qualifiers.PROJECT)
+    .setKey("the_project")
+    .setName("The Project")
+    .setDescription("The project description")
+    .setEnabled(true)
+    .setUuid(PROJECT_UUID)
+    .setUuidPath(UUID_PATH_OF_ROOT)
+    .setCreatedAt(new Date(1596749115856L))
+    .setBranchUuid(PROJECT_UUID);
+
+  private static final String FILE_UUID = "FILE_UUID";
+  private static final String FILE_UUID_PATH = PROJECT_UUID + FILE_UUID + UUID_PATH_SEPARATOR;
+  private static final ComponentDto FILE = new ComponentDto()
+    // no id yet
+    .setScope(Scopes.FILE)
+    .setQualifier(Qualifiers.FILE)
+    .setKey("the_file")
+    .setName("The File")
+    .setUuid(FILE_UUID)
+    .setUuidPath(FILE_UUID_PATH)
+    .setEnabled(true)
+    .setCreatedAt(new Date(1596749148406L))
+    .setBranchUuid(PROJECT_UUID);
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  private final FakeDumpWriter dumpWriter = new FakeDumpWriter();
+  private final ProjectHolder projectHolder = mock(ProjectHolder.class);
+  private final MutableComponentRepository componentRepository = new ComponentRepositoryImpl();
+  private final ExportComponentsStep underTest = new ExportComponentsStep(dbTester.getDbClient(), projectHolder, componentRepository, dumpWriter);
+
+  @After
+  public void tearDown() {
+    dbTester.getSession().close();
+  }
+
+  @Test
+  public void export_components_including_project() {
+    dbTester.components().insertPublicProject(PROJECT);
+    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), FILE);
+    dbTester.commit();
+    when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(PROJECT));
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("2 components exported");
+    List<ProjectDump.Component> components = dumpWriter.getWrittenMessagesOf(DumpElement.COMPONENTS);
+    assertThat(components).extracting(ProjectDump.Component::getQualifier, ProjectDump.Component::getUuid, ProjectDump.Component::getUuidPath)
+      .containsExactlyInAnyOrder(
+        tuple(Qualifiers.FILE, FILE_UUID, FILE_UUID_PATH),
+        tuple(Qualifiers.PROJECT, PROJECT_UUID, UUID_PATH_OF_ROOT));
+  }
+
+  @Test
+  public void execute_register_all_components_uuids_as_their_id_in_ComponentRepository() {
+    dbTester.components().insertPublicProject(PROJECT);
+    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), FILE);
+    dbTester.commit();
+    when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(PROJECT));
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(ImmutableSet.of(
+      componentRepository.getRef(PROJECT.uuid()),
+      componentRepository.getRef(FILE.uuid()))).containsExactlyInAnyOrder(1L, 2L);
+  }
+
+  @Test
+  public void throws_ISE_if_error() {
+    dbTester.components().insertPublicProject(PROJECT);
+    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), FILE);
+    dbTester.commit();
+    when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(PROJECT));
+    dumpWriter.failIfMoreThan(1, DumpElement.COMPONENTS);
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Component Export failed after processing 1 components successfully");
+  }
+
+  @Test
+  public void getDescription_is_defined() {
+    assertThat(underTest.getDescription()).isEqualTo("Export components");
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/ExportMeasuresStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/ExportMeasuresStepIT.java
new file mode 100644 (file)
index 0000000..4bed242
--- /dev/null
@@ -0,0 +1,259 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.ce.task.projectexport.steps;
+
+import com.sonarsource.governance.projectdump.protobuf.ProjectDump;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.ce.task.projectexport.component.ComponentRepositoryImpl;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchDto;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.measure.MeasureDto;
+import org.sonar.db.metric.MetricDto;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.db.component.ComponentDto.UUID_PATH_OF_ROOT;
+import static org.sonar.db.component.ComponentDto.UUID_PATH_SEPARATOR;
+import static org.sonar.db.component.SnapshotDto.STATUS_PROCESSED;
+import static org.sonar.db.component.SnapshotDto.STATUS_UNPROCESSED;
+
+public class ExportMeasuresStepIT {
+
+  private static final ComponentDto PROJECT = new ComponentDto()
+    .setKey("project_key")
+    .setUuid("project_uuid")
+    .setBranchUuid("project_uuid")
+    .setUuidPath(UUID_PATH_OF_ROOT)
+    .setEnabled(true);
+  private static final ComponentDto FILE = new ComponentDto()
+    .setKey("file_key")
+    .setUuid("file_uuid")
+    .setBranchUuid("project_uuid")
+    .setUuidPath(UUID_PATH_OF_ROOT + PROJECT.uuid() + UUID_PATH_SEPARATOR)
+    .setEnabled(true);
+  private static final ComponentDto ANOTHER_PROJECT = new ComponentDto()
+    .setKey("another_project_key")
+    .setUuid("another_project_uuid")
+    .setBranchUuid("another_project_uuid")
+    .setUuidPath(UUID_PATH_OF_ROOT)
+    .setEnabled(true);
+
+  private static final MetricDto NCLOC = new MetricDto()
+    .setUuid("3")
+    .setKey("ncloc")
+    .setShortName("Lines of code")
+    .setEnabled(true);
+
+  private static final MetricDto DISABLED_METRIC = new MetricDto()
+    .setUuid("4")
+    .setKey("coverage")
+    .setShortName("Coverage")
+    .setEnabled(false);
+
+  private static final MetricDto NEW_NCLOC = new MetricDto()
+    .setUuid("5")
+    .setKey("new_ncloc")
+    .setShortName("New Lines of code")
+    .setEnabled(true);
+
+  private static final List<BranchDto> BRANCHES = newArrayList(
+    new BranchDto()
+      .setBranchType(BranchType.BRANCH)
+      .setKey("master")
+      .setUuid(PROJECT.uuid())
+      .setProjectUuid(PROJECT.uuid()));
+
+
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private ComponentRepositoryImpl componentRepository = new ComponentRepositoryImpl();
+  private MutableMetricRepository metricRepository = new MutableMetricRepositoryImpl();
+  private ProjectHolder projectHolder = mock(ProjectHolder.class);
+  private FakeDumpWriter dumpWriter = new FakeDumpWriter();
+  private ExportMeasuresStep underTest = new ExportMeasuresStep(dbTester.getDbClient(), projectHolder, componentRepository, metricRepository, dumpWriter);
+
+  @Before
+  public void setUp() {
+    String projectUuid = dbTester.components().insertPublicProject(PROJECT).uuid();
+    componentRepository.register(1, projectUuid, false);
+    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), FILE, ANOTHER_PROJECT);
+    dbTester.getDbClient().metricDao().insert(dbTester.getSession(), NCLOC, DISABLED_METRIC, NEW_NCLOC);
+    dbTester.commit();
+    when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(PROJECT));
+    when(projectHolder.branches()).thenReturn(BRANCHES);
+  }
+
+  @Test
+  public void export_zero_measures() {
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES)).isEmpty();
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("0 measures exported");
+    assertThat(metricRepository.getRefByUuid()).isEmpty();
+  }
+
+  @Test
+  public void export_measures() {
+    SnapshotDto firstAnalysis = insertSnapshot("U_1", PROJECT, STATUS_PROCESSED);
+    insertMeasure(firstAnalysis, PROJECT, new MeasureDto().setValue(100.0).setMetricUuid(NCLOC.getUuid()));
+    SnapshotDto secondAnalysis = insertSnapshot("U_2", PROJECT, STATUS_PROCESSED);
+    insertMeasure(secondAnalysis, PROJECT, new MeasureDto().setValue(110.0).setMetricUuid(NCLOC.getUuid()));
+    SnapshotDto anotherProjectAnalysis = insertSnapshot("U_3", ANOTHER_PROJECT, STATUS_PROCESSED);
+    insertMeasure(anotherProjectAnalysis, ANOTHER_PROJECT, new MeasureDto().setValue(500.0).setMetricUuid(NCLOC.getUuid()));
+    dbTester.commit();
+
+    underTest.execute(new TestComputationStepContext());
+
+    List<ProjectDump.Measure> exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES);
+    assertThat(exportedMeasures).hasSize(2);
+    assertThat(exportedMeasures).extracting(ProjectDump.Measure::getAnalysisUuid).containsOnly(firstAnalysis.getUuid(), secondAnalysis.getUuid());
+    assertThat(exportedMeasures).extracting(ProjectDump.Measure::getMetricRef).containsOnly(0);
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("2 measures exported");
+    assertThat(metricRepository.getRefByUuid()).containsOnlyKeys(NCLOC.getUuid());
+  }
+
+  @Test
+  public void do_not_export_measures_on_unprocessed_snapshots() {
+    SnapshotDto firstAnalysis = insertSnapshot("U_1", PROJECT, STATUS_UNPROCESSED);
+    insertMeasure(firstAnalysis, PROJECT, new MeasureDto().setValue(100.0).setMetricUuid(NCLOC.getUuid()));
+    dbTester.commit();
+
+    underTest.execute(new TestComputationStepContext());
+
+    List<ProjectDump.Measure> exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES);
+    assertThat(exportedMeasures).isEmpty();
+  }
+
+  @Test
+  public void do_not_export_measures_on_disabled_metrics() {
+    SnapshotDto firstAnalysis = insertSnapshot("U_1", PROJECT, STATUS_PROCESSED);
+    insertMeasure(firstAnalysis, PROJECT, new MeasureDto().setValue(100.0).setMetricUuid(DISABLED_METRIC.getUuid()));
+    dbTester.commit();
+
+    underTest.execute(new TestComputationStepContext());
+
+    List<ProjectDump.Measure> exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES);
+    assertThat(exportedMeasures).isEmpty();
+  }
+
+  @Test
+  public void test_exported_fields() {
+    SnapshotDto analysis = insertSnapshot("U_1", PROJECT, STATUS_PROCESSED);
+    MeasureDto dto = new MeasureDto()
+      .setMetricUuid(NCLOC.getUuid())
+      .setValue(100.0)
+      .setData("data")
+      .setAlertStatus("OK")
+      .setAlertText("alert text");
+    insertMeasure(analysis, PROJECT, dto);
+    dbTester.commit();
+
+    underTest.execute(new TestComputationStepContext());
+
+    List<ProjectDump.Measure> exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES);
+    ProjectDump.Measure measure = exportedMeasures.get(0);
+    assertThat(measure.getAlertStatus()).isEqualTo(dto.getAlertStatus());
+    assertThat(measure.getAlertText()).isEqualTo(dto.getAlertText());
+    assertThat(measure.getDoubleValue().getValue()).isEqualTo(dto.getValue());
+    assertThat(measure.getTextValue()).isEqualTo(dto.getData());
+    assertThat(measure.getMetricRef()).isZero();
+    assertThat(measure.getAnalysisUuid()).isEqualTo(analysis.getUuid());
+    assertThat(measure.getVariation1().getValue()).isZero();
+  }
+
+  @Test
+  public void test_exported_fields_new_metric() {
+    SnapshotDto analysis = insertSnapshot("U_1", PROJECT, STATUS_PROCESSED);
+    MeasureDto dto = new MeasureDto()
+      .setMetricUuid(NEW_NCLOC.getUuid())
+      .setValue(100.0)
+      .setData("data")
+      .setAlertStatus("OK")
+      .setAlertText("alert text");
+    insertMeasure(analysis, PROJECT, dto);
+    dbTester.commit();
+
+    underTest.execute(new TestComputationStepContext());
+
+    List<ProjectDump.Measure> exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES);
+    ProjectDump.Measure measure = exportedMeasures.get(0);
+    assertThat(measure.getAlertStatus()).isEqualTo(dto.getAlertStatus());
+    assertThat(measure.getAlertText()).isEqualTo(dto.getAlertText());
+    assertThat(measure.getDoubleValue().getValue()).isZero();
+    assertThat(measure.getTextValue()).isEqualTo(dto.getData());
+    assertThat(measure.getMetricRef()).isZero();
+    assertThat(measure.getAnalysisUuid()).isEqualTo(analysis.getUuid());
+    assertThat(measure.getVariation1().getValue()).isEqualTo(dto.getValue());
+  }
+
+  @Test
+  public void test_null_exported_fields() {
+    SnapshotDto analysis = insertSnapshot("U_1", PROJECT, STATUS_PROCESSED);
+    insertMeasure(analysis, PROJECT, new MeasureDto().setMetricUuid(NCLOC.getUuid()));
+    dbTester.commit();
+
+    underTest.execute(new TestComputationStepContext());
+
+    ProjectDump.Measure measure = dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES).get(0);
+    assertThat(measure.getAlertStatus()).isEmpty();
+    assertThat(measure.getAlertText()).isEmpty();
+    assertThat(measure.hasDoubleValue()).isFalse();
+    assertThat(measure.getTextValue()).isEmpty();
+    assertThat(measure.hasVariation1()).isFalse();
+  }
+
+  @Test
+  public void test_getDescription() {
+    assertThat(underTest.getDescription()).isEqualTo("Export measures");
+  }
+
+  private SnapshotDto insertSnapshot(String snapshotUuid, ComponentDto project, String status) {
+    SnapshotDto snapshot = new SnapshotDto()
+      .setUuid(snapshotUuid)
+      .setComponentUuid(project.uuid())
+      .setStatus(status)
+      .setLast(true);
+    dbTester.getDbClient().snapshotDao().insert(dbTester.getSession(), snapshot);
+    return snapshot;
+  }
+
+  private void insertMeasure(SnapshotDto analysisDto, ComponentDto componentDto, MeasureDto measureDto) {
+    measureDto
+      .setAnalysisUuid(analysisDto.getUuid())
+      .setComponentUuid(componentDto.uuid());
+    dbTester.getDbClient().measureDao().insert(dbTester.getSession(), measureDto);
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1235_add_component_uuid_to_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1235_add_component_uuid_to_duplications_index.rb
new file mode 100644 (file)
index 0000000..34dfbce
--- /dev/null
@@ -0,0 +1,29 @@
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2016 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.
+#
+
+#
+# SonarQube 6.0
+#
+class AddComponentUuidToDuplicationsIndex < ActiveRecord::Migration
+
+  def self.up
+    execute_java_migration('org.sonar.db.version.v60.AddComponentUuidColumnToDuplicationsIndex')
+  end
+end
diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1236_populate_component_uuid_of_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1236_populate_component_uuid_of_duplications_index.rb
new file mode 100644 (file)
index 0000000..6f9d4dd
--- /dev/null
@@ -0,0 +1,29 @@
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2016 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.
+#
+
+#
+# SonarQube 6.0
+#
+class PopulateComponentUuidOfDuplicationsIndex < ActiveRecord::Migration
+
+  def self.up
+    execute_java_migration('org.sonar.db.version.v60.PopulateComponentUuidOfDuplicationsIndex')
+  end
+end
diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1237_delete_orphan_duplications_index_rows_without_component.rb b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1237_delete_orphan_duplications_index_rows_without_component.rb
new file mode 100644 (file)
index 0000000..f4f67a9
--- /dev/null
@@ -0,0 +1,29 @@
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2016 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.
+#
+
+#
+# SonarQube 6.0
+#
+class DeleteOrphanDuplicationsIndexRowsWithoutComponent < ActiveRecord::Migration
+
+  def self.up
+    execute_java_migration('org.sonar.db.version.v60.DeleteOrphanDuplicationsIndexRowsWithoutComponent')
+  end
+end
diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1238_make_component_uuid_not_null_on_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1238_make_component_uuid_not_null_on_duplications_index.rb
new file mode 100644 (file)
index 0000000..eece62d
--- /dev/null
@@ -0,0 +1,29 @@
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2016 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.
+#
+
+#
+# SonarQube 6.0
+#
+class MakeComponentUuidNotNullOnDuplicationsIndex < ActiveRecord::Migration
+
+  def self.up
+    execute_java_migration('org.sonar.db.version.v60.MakeComponentUuidNotNullOnDuplicationsIndex')
+  end
+end
diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1239_add_analysis_uuid_to_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1239_add_analysis_uuid_to_duplications_index.rb
new file mode 100644 (file)
index 0000000..bb5011b
--- /dev/null
@@ -0,0 +1,29 @@
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2016 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.
+#
+
+#
+# SonarQube 6.0
+#
+class AddAnalysisUuidToDuplicationsIndex < ActiveRecord::Migration
+
+  def self.up
+    execute_java_migration('org.sonar.db.version.v60.AddAnalysisUuidColumnToDuplicationsIndex')
+  end
+end
diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1240_populate_analysis_uuid_of_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1240_populate_analysis_uuid_of_duplications_index.rb
new file mode 100644 (file)
index 0000000..6451732
--- /dev/null
@@ -0,0 +1,29 @@
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2016 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.
+#
+
+#
+# SonarQube 6.0
+#
+class PopulateAnalysisUuidOfDuplicationsIndex < ActiveRecord::Migration
+
+  def self.up
+    execute_java_migration('org.sonar.db.version.v60.PopulateAnalysisUuidOfDuplicationsIndex')
+  end
+end
diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1241_delete_orphan_duplications_index_rows_without_analysis.rb b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1241_delete_orphan_duplications_index_rows_without_analysis.rb
new file mode 100644 (file)
index 0000000..5afedbe
--- /dev/null
@@ -0,0 +1,29 @@
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2016 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.
+#
+
+#
+# SonarQube 6.0
+#
+class DeleteOrphanDuplicationsIndexRowsWithoutAnalysis < ActiveRecord::Migration
+
+  def self.up
+    execute_java_migration('org.sonar.db.version.v60.DeleteOrphanDuplicationsIndexRowsWithoutAnalysis')
+  end
+end
diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1242_make_analysis_uuid_not_null_on_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1242_make_analysis_uuid_not_null_on_duplications_index.rb
new file mode 100644 (file)
index 0000000..961cd6e
--- /dev/null
@@ -0,0 +1,31 @@
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2016 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.
+#
+
+#
+# SonarQube 6.0
+#
+class MakeAnalysisUuidNotNullOnDuplicationsIndex < ActiveRecord::Migration
+
+  def self.up
+    execute_java_migration('org.sonar.db.version.v60.MakeAnalysisUuidNotNullOnDuplicationsIndex')
+
+    add_index :duplications_index, [:analysis_uuid, :component_uuid], :name => 'duplication_analysis_component'
+  end
+end
diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/AddAnalysisUuidColumnToDuplicationsIndex.java b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/AddAnalysisUuidColumnToDuplicationsIndex.java
new file mode 100644 (file)
index 0000000..f641c80
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.v1;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.AddColumnsBuilder;
+
+import static org.sonar.db.version.VarcharColumnDef.UUID_VARCHAR_SIZE;
+import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+public class AddAnalysisUuidColumnToDuplicationsIndex extends DdlChange {
+
+  private static final String TABLE_DUPLICATIONS_INDEX = "duplications_index";
+
+  public AddAnalysisUuidColumnToDuplicationsIndex(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    context.execute(new AddColumnsBuilder(getDatabase().getDialect(), TABLE_DUPLICATIONS_INDEX)
+      .addColumn(newVarcharColumnDefBuilder().setColumnName("analysis_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(true).build())
+      .build());
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/AddComponentUuidColumnToDuplicationsIndex.java b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/AddComponentUuidColumnToDuplicationsIndex.java
new file mode 100644 (file)
index 0000000..b6f6763
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.v1;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.AddColumnsBuilder;
+
+import static org.sonar.db.version.VarcharColumnDef.UUID_VARCHAR_SIZE;
+import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+public class AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex extends DdlChange {
+
+  private static final String TABLE_PUBLICATIONS_INDEX = "duplications_index";
+
+  public AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    context.execute(new AddColumnsBuilder(getDatabase().getDialect(), TABLE_PUBLICATIONS_INDEX)
+      .addColumn(newVarcharColumnDefBuilder().setColumnName("component_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(true).build())
+      .addColumn(newVarcharColumnDefBuilder().setColumnName("analysis_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(true).build())
+      .build());
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/DeleteOrphanDuplicationsIndexRowsWithoutComponent.java b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/DeleteOrphanDuplicationsIndexRowsWithoutComponent.java
new file mode 100644 (file)
index 0000000..eb7ed0b
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.v1;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.step.MassUpdate;
+
+public class DeleteOrphanDuplicationsIndexRowsWithoutComponent extends BaseDataChange {
+
+  public DeleteOrphanDuplicationsIndexRowsWithoutComponent(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    MassUpdate massUpdate = context.prepareMassUpdate();
+    massUpdate.select("SELECT id from duplications_index where component_uuid is null");
+    massUpdate.update("DELETE from duplications_index WHERE id=?");
+    massUpdate.rowPluralName("resources_index entries");
+    massUpdate.execute((row, update) -> {
+      update.setLong(1, row.getLong(1));
+      return true;
+    });
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/MakeComponentUuidNotNullOnDuplicationsIndex.java b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/MakeComponentUuidNotNullOnDuplicationsIndex.java
new file mode 100644 (file)
index 0000000..e4d1f35
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.v1;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.AlterColumnsBuilder;
+
+import static org.sonar.db.version.VarcharColumnDef.UUID_VARCHAR_SIZE;
+import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+public class MakeComponentUuidNotNullOnDuplicationsIndex extends DdlChange {
+
+  private static final String TABLE_DUPLICATIONS_INDEX = "duplications_index";
+
+  public MakeComponentUuidNotNullOnDuplicationsIndex(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    context.execute(new AlterColumnsBuilder(getDatabase().getDialect(), TABLE_DUPLICATIONS_INDEX)
+      .updateColumn(newVarcharColumnDefBuilder().setColumnName("component_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(false).build())
+      .build());
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/1235_add_component_uuid_and_analysis_uuid_to_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/1235_add_component_uuid_and_analysis_uuid_to_duplications_index.rb
new file mode 100644 (file)
index 0000000..c7ecc23
--- /dev/null
@@ -0,0 +1,29 @@
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2016 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.
+#
+
+#
+# SonarQube 6.0
+#
+class AddComponentUuidAndAnalysisUuidToDuplicationsIndex < ActiveRecord::Migration
+
+  def self.up
+    execute_java_migration('org.sonar.db.version.v60.AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex')
+  end
+end
diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/1236_populate_component_uuid_and_analysis_uuid_of_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/1236_populate_component_uuid_and_analysis_uuid_of_duplications_index.rb
new file mode 100644 (file)
index 0000000..024aa7f
--- /dev/null
@@ -0,0 +1,29 @@
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2016 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.
+#
+
+#
+# SonarQube 6.0
+#
+class PopulateComponentUuidAndAnalysisUuidOfDuplicationsIndex < ActiveRecord::Migration
+
+  def self.up
+    execute_java_migration('org.sonar.db.version.v60.PopulateComponentUuidAndAnalysisUuidOfDuplicationsIndex')
+  end
+end
diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/1237_delete_orphan_duplications_index_rows_without_component_or_analysis.rb b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/1237_delete_orphan_duplications_index_rows_without_component_or_analysis.rb
new file mode 100644 (file)
index 0000000..caa887b
--- /dev/null
@@ -0,0 +1,29 @@
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2016 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.
+#
+
+#
+# SonarQube 6.0
+#
+class DeleteOrphanDuplicationsIndexRowsWithoutComponentOrAnalysis < ActiveRecord::Migration
+
+  def self.up
+    execute_java_migration('org.sonar.db.version.v60.DeleteOrphanDuplicationsIndexRowsWithoutComponentOrAnalysis')
+  end
+end
diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/1238_make_component_uuid_and_analysis_uuid_not_null_on_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/1238_make_component_uuid_and_analysis_uuid_not_null_on_duplications_index.rb
new file mode 100644 (file)
index 0000000..72e090f
--- /dev/null
@@ -0,0 +1,31 @@
+#
+# SonarQube, open source software quality management tool.
+# Copyright (C) 2008-2016 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.
+#
+
+#
+# SonarQube 6.0
+#
+class MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex < ActiveRecord::Migration
+
+  def self.up
+    execute_java_migration('org.sonar.db.version.v60.MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex')
+
+    add_index :duplications_index, [:analysis_uuid, :component_uuid], :name => 'duplication_analysis_component'
+  end
+end
diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex.java b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex.java
new file mode 100644 (file)
index 0000000..d3df009
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.v2;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.AddColumnsBuilder;
+
+import static org.sonar.db.version.VarcharColumnDef.UUID_VARCHAR_SIZE;
+import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+public class AddComponentUuidColumnToDuplicationsIndex extends DdlChange {
+
+  private static final String TABLE_PUBLICATIONS_INDEX = "duplications_index";
+
+  public AddComponentUuidColumnToDuplicationsIndex(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    context.execute(new AddColumnsBuilder(getDatabase().getDialect(), TABLE_PUBLICATIONS_INDEX)
+      .addColumn(newVarcharColumnDefBuilder().setColumnName("component_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(true).build())
+      .build());
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex.java b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex.java
new file mode 100644 (file)
index 0000000..01f5610
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.v2;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.version.AlterColumnsBuilder;
+
+import static org.sonar.db.version.VarcharColumnDef.UUID_VARCHAR_SIZE;
+import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder;
+
+public class MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex extends DdlChange {
+
+  private static final String TABLE_DUPLICATIONS_INDEX = "duplications_index";
+
+  public MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex(Database db) {
+    super(db);
+  }
+
+  @Override
+  public void execute(Context context) throws SQLException {
+    context.execute(new AlterColumnsBuilder(getDatabase().getDialect(), TABLE_DUPLICATIONS_INDEX)
+      .updateColumn(newVarcharColumnDefBuilder().setColumnName("component_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(false).build())
+      .updateColumn(newVarcharColumnDefBuilder().setColumnName("analysis_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(false).build())
+      .build());
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/BranchPersisterImplTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/BranchPersisterImplTest.java
deleted file mode 100644 (file)
index 91e9155..0000000
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.ce.task.projectanalysis.component;
-
-import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import java.util.Collections;
-import java.util.Optional;
-import javax.annotation.Nullable;
-import org.assertj.core.api.ThrowableAssert;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.sonar.api.config.internal.ConfigurationBridge;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
-import org.sonar.ce.task.projectanalysis.analysis.Branch;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.BranchDto;
-import org.sonar.db.component.BranchType;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.protobuf.DbProjectBranches;
-import org.sonar.server.project.Project;
-
-import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
-import static org.sonar.core.config.PurgeConstants.BRANCHES_TO_KEEP_WHEN_INACTIVE;
-import static org.sonar.db.component.BranchType.BRANCH;
-import static org.sonar.db.component.BranchType.PULL_REQUEST;
-
-@RunWith(DataProviderRunner.class)
-public class BranchPersisterImplTest {
-  private final static Component MAIN = builder(Component.Type.PROJECT, 1, "PROJECT_KEY").setUuid("PROJECT_UUID").setName("p1").build();
-  private final static Component BRANCH1 = builder(Component.Type.PROJECT, 2, "BRANCH_KEY").setUuid("BRANCH_UUID").build();
-  private final static Component PR1 = builder(Component.Type.PROJECT, 3, "develop").setUuid("PR_UUID").build();
-  private static final Project PROJECT = new Project(MAIN.getUuid(), MAIN.getKey(), MAIN.getName(), null, Collections.emptyList());
-
-  @Rule
-  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-
-  private final MapSettings settings = new MapSettings();
-  private final ConfigurationRepository configurationRepository = new TestSettingsRepository(new ConfigurationBridge(settings));
-  private final BranchPersister underTest = new BranchPersisterImpl(dbTester.getDbClient(), treeRootHolder, analysisMetadataHolder, configurationRepository);
-
-  @Test
-  public void persist_fails_with_ISE_if_no_component_for_main_branches() {
-    analysisMetadataHolder.setBranch(createBranch(BRANCH, true, "master"));
-    treeRootHolder.setRoot(MAIN);
-    DbSession dbSession = dbTester.getSession();
-
-    expectMissingComponentISE(() -> underTest.persist(dbSession));
-  }
-
-  @Test
-  public void persist_fails_with_ISE_if_no_component_for_branches() {
-    analysisMetadataHolder.setBranch(createBranch(BRANCH, false, "foo"));
-    treeRootHolder.setRoot(BRANCH1);
-    DbSession dbSession = dbTester.getSession();
-
-    expectMissingComponentISE(() -> underTest.persist(dbSession));
-  }
-
-  @Test
-  public void persist_fails_with_ISE_if_no_component_for_pull_request() {
-    analysisMetadataHolder.setBranch(createBranch(BranchType.PULL_REQUEST, false, "12"));
-    treeRootHolder.setRoot(BRANCH1);
-    DbSession dbSession = dbTester.getSession();
-
-    expectMissingComponentISE(() -> underTest.persist(dbSession));
-  }
-
-  @Test
-  @UseDataProvider("nullOrNotNullString")
-  public void persist_creates_row_in_PROJECTS_BRANCHES_for_branch(@Nullable String mergeBranchUuid) {
-    String branchName = "branch";
-
-    // add project and branch in table PROJECTS
-    ComponentDto mainComponent = ComponentTesting.newPrivateProjectDto(MAIN.getUuid()).setKey(MAIN.getKey());
-    ComponentDto component = ComponentTesting.newBranchComponent(mainComponent,
-      new BranchDto().setUuid(BRANCH1.getUuid()).setKey(BRANCH1.getKey()).setBranchType(BRANCH));
-    dbTester.components().insertComponents(mainComponent, component);
-    // set project in metadata
-    treeRootHolder.setRoot(BRANCH1);
-    analysisMetadataHolder.setBranch(createBranch(BRANCH, false, branchName, mergeBranchUuid));
-    analysisMetadataHolder.setProject(Project.from(mainComponent));
-
-    underTest.persist(dbTester.getSession());
-
-    dbTester.getSession().commit();
-
-    assertThat(dbTester.countRowsOfTable("components")).isEqualTo(2);
-    Optional<BranchDto> branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH1.getUuid());
-    assertThat(branchDto).isPresent();
-    assertThat(branchDto.get().getBranchType()).isEqualTo(BRANCH);
-    assertThat(branchDto.get().getKey()).isEqualTo(branchName);
-    assertThat(branchDto.get().getMergeBranchUuid()).isEqualTo(mergeBranchUuid);
-    assertThat(branchDto.get().getProjectUuid()).isEqualTo(MAIN.getUuid());
-    assertThat(branchDto.get().getPullRequestData()).isNull();
-  }
-
-  @Test
-  public void main_branch_is_excluded_from_branch_purge_by_default() {
-    analysisMetadataHolder.setBranch(createBranch(BRANCH, true, "master"));
-    treeRootHolder.setRoot(MAIN);
-    dbTester.components().insertPublicProject(p -> p.setKey(MAIN.getKey()).setUuid(MAIN.getUuid()));
-    dbTester.commit();
-
-    underTest.persist(dbTester.getSession());
-
-    Optional<BranchDto> branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), MAIN.getUuid());
-    assertThat(branchDto).isPresent();
-    assertThat(branchDto.get().isExcludeFromPurge()).isTrue();
-  }
-
-  @Test
-  public void non_main_branch_is_excluded_from_branch_purge_if_matches_sonar_dbcleaner_keepFromPurge_property() {
-    settings.setProperty(BRANCHES_TO_KEEP_WHEN_INACTIVE, "BRANCH.*");
-    analysisMetadataHolder.setProject(PROJECT);
-    analysisMetadataHolder.setBranch(createBranch(BRANCH, false, "BRANCH_KEY"));
-    treeRootHolder.setRoot(BRANCH1);
-    ComponentDto mainComponent = dbTester.components().insertPublicProject(p -> p.setKey(MAIN.getKey()).setUuid(MAIN.getUuid()));
-    ComponentDto component = ComponentTesting.newBranchComponent(mainComponent,
-      new BranchDto().setUuid(BRANCH1.getUuid()).setKey(BRANCH1.getKey()).setBranchType(BRANCH));
-    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), component);
-    dbTester.commit();
-
-    underTest.persist(dbTester.getSession());
-
-    Optional<BranchDto> branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH1.getUuid());
-    assertThat(branchDto).isPresent();
-    assertThat(branchDto.get().isExcludeFromPurge()).isTrue();
-  }
-
-  @Test
-  public void branch_is_excluded_from_purge_when_it_matches_setting() {
-    analysisMetadataHolder.setProject(PROJECT);
-    analysisMetadataHolder.setBranch(createBranch(BRANCH, false, "BRANCH_KEY"));
-    treeRootHolder.setRoot(BRANCH1);
-    ComponentDto mainComponent = dbTester.components().insertPublicProject(p -> p.setKey(MAIN.getKey()).setUuid(MAIN.getUuid()));
-    ComponentDto component = ComponentTesting.newBranchComponent(mainComponent,
-      new BranchDto().setUuid(BRANCH1.getUuid()).setKey(BRANCH1.getKey()).setBranchType(BRANCH));
-    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), component);
-    settings.setProperty(BRANCHES_TO_KEEP_WHEN_INACTIVE, "BRANCH.*");
-    dbTester.commit();
-
-    underTest.persist(dbTester.getSession());
-
-    Optional<BranchDto> branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH1.getUuid());
-    assertThat(branchDto).isPresent();
-    assertThat(branchDto.get().isExcludeFromPurge()).isTrue();
-  }
-
-  @Test
-  public void branch_is_not_excluded_from_purge_when_it_does_not_match_setting() {
-    analysisMetadataHolder.setProject(PROJECT);
-    analysisMetadataHolder.setBranch(createBranch(BRANCH, false, "BRANCH_KEY"));
-    treeRootHolder.setRoot(BRANCH1);
-    ComponentDto mainComponent = dbTester.components().insertPublicProject(p -> p.setKey(MAIN.getKey()).setUuid(MAIN.getUuid()));
-    ComponentDto component = ComponentTesting.newBranchComponent(mainComponent,
-      new BranchDto().setUuid(BRANCH1.getUuid()).setKey(BRANCH1.getKey()).setBranchType(BRANCH));
-    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), component);
-    settings.setProperty(BRANCHES_TO_KEEP_WHEN_INACTIVE, "abc.*");
-
-    dbTester.commit();
-
-    underTest.persist(dbTester.getSession());
-
-    Optional<BranchDto> branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH1.getUuid());
-    assertThat(branchDto).isPresent();
-    assertThat(branchDto.get().isExcludeFromPurge()).isFalse();
-  }
-
-  @Test
-  public void pull_request_is_never_excluded_from_branch_purge_even_if_its_source_branch_name_matches_sonar_dbcleaner_keepFromPurge_property() {
-    settings.setProperty(BRANCHES_TO_KEEP_WHEN_INACTIVE, "develop");
-    analysisMetadataHolder.setBranch(createPullRequest(PR1.getKey(), MAIN.getUuid()));
-    analysisMetadataHolder.setPullRequestKey(PR1.getKey());
-    treeRootHolder.setRoot(PR1);
-    ComponentDto mainComponent = dbTester.components().insertPublicProject(p -> p.setKey(MAIN.getKey()).setUuid(MAIN.getUuid()));
-    ComponentDto component = ComponentTesting.newBranchComponent(mainComponent, new BranchDto()
-      .setUuid(PR1.getUuid())
-      .setKey(PR1.getKey())
-      .setProjectUuid(MAIN.getUuid())
-      .setBranchType(PULL_REQUEST)
-      .setMergeBranchUuid(MAIN.getUuid()));
-    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), component);
-    dbTester.commit();
-
-    underTest.persist(dbTester.getSession());
-
-    Optional<BranchDto> branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), PR1.getUuid());
-    assertThat(branchDto).isPresent();
-    assertThat(branchDto.get().isExcludeFromPurge()).isFalse();
-  }
-
-  @Test
-  public void non_main_branch_is_included_in_branch_purge_if_branch_name_does_not_match_sonar_dbcleaner_keepFromPurge_property() {
-    settings.setProperty(BRANCHES_TO_KEEP_WHEN_INACTIVE, "foobar-.*");
-    analysisMetadataHolder.setProject(PROJECT);
-    analysisMetadataHolder.setBranch(createBranch(BRANCH, false, "BRANCH_KEY"));
-    treeRootHolder.setRoot(BRANCH1);
-    ComponentDto mainComponent = dbTester.components().insertPublicProject(p -> p.setKey(MAIN.getKey()).setUuid(MAIN.getUuid()));
-    ComponentDto component = ComponentTesting.newBranchComponent(mainComponent,
-      new BranchDto().setUuid(BRANCH1.getUuid()).setKey(BRANCH1.getKey()).setBranchType(BRANCH));
-    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), component);
-    dbTester.commit();
-
-    underTest.persist(dbTester.getSession());
-
-    Optional<BranchDto> branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH1.getUuid());
-    assertThat(branchDto).isPresent();
-    assertThat(branchDto.get().isExcludeFromPurge()).isFalse();
-  }
-
-  @DataProvider
-  public static Object[][] nullOrNotNullString() {
-    return new Object[][] {
-      {null},
-      {randomAlphabetic(12)}
-    };
-  }
-
-  @Test
-  public void persist_creates_row_in_PROJECTS_BRANCHES_for_pull_request() {
-    String pullRequestId = "pr-123";
-
-    // add project and branch in table PROJECTS
-    ComponentDto mainComponent = ComponentTesting.newPrivateProjectDto(MAIN.getUuid()).setKey(MAIN.getKey());
-    ComponentDto component = ComponentTesting.newBranchComponent(mainComponent,
-      new BranchDto().setUuid(BRANCH1.getUuid()).setKey(BRANCH1.getKey()).setBranchType(PULL_REQUEST));
-    dbTester.components().insertComponents(mainComponent, component);
-    // set project in metadata
-    treeRootHolder.setRoot(BRANCH1);
-    analysisMetadataHolder.setBranch(createBranch(PULL_REQUEST, false, pullRequestId, "mergeBanchUuid"));
-    analysisMetadataHolder.setProject(Project.from(mainComponent));
-    analysisMetadataHolder.setPullRequestKey(pullRequestId);
-
-    underTest.persist(dbTester.getSession());
-
-    dbTester.getSession().commit();
-
-    assertThat(dbTester.countRowsOfTable("components")).isEqualTo(2);
-    Optional<BranchDto> branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH1.getUuid());
-    assertThat(branchDto).isPresent();
-    assertThat(branchDto.get().getBranchType()).isEqualTo(PULL_REQUEST);
-    assertThat(branchDto.get().getKey()).isEqualTo(pullRequestId);
-    assertThat(branchDto.get().getMergeBranchUuid()).isEqualTo("mergeBanchUuid");
-    assertThat(branchDto.get().getProjectUuid()).isEqualTo(MAIN.getUuid());
-    assertThat(branchDto.get().getPullRequestData()).isEqualTo(DbProjectBranches.PullRequestData.newBuilder()
-      .setBranch(pullRequestId)
-      .setTarget("mergeBanchUuid")
-      .setTitle(pullRequestId)
-      .build());
-  }
-
-  private static Branch createBranch(BranchType type, boolean isMain, String name) {
-    return createBranch(type, isMain, name, null);
-  }
-
-  private static Branch createPullRequest(String key, String mergeBranchUuid) {
-    Branch branch = createBranch(PULL_REQUEST, false, key, mergeBranchUuid);
-    when(branch.getPullRequestKey()).thenReturn(key);
-    return branch;
-  }
-
-  private static Branch createBranch(BranchType type, boolean isMain, String name, @Nullable String mergeBranchUuid) {
-    Branch branch = mock(Branch.class);
-    when(branch.getType()).thenReturn(type);
-    when(branch.getName()).thenReturn(name);
-    when(branch.isMain()).thenReturn(isMain);
-    when(branch.getReferenceBranchUuid()).thenReturn(mergeBranchUuid);
-    when(branch.getTargetBranchName()).thenReturn(mergeBranchUuid);
-    return branch;
-  }
-
-  private void expectMissingComponentISE(ThrowableAssert.ThrowingCallable callable) {
-    assertThatThrownBy(callable)
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Component has been deleted by end-user during analysis");
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest.java
deleted file mode 100644 (file)
index c311657..0000000
+++ /dev/null
@@ -1,710 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.ce.task.projectanalysis.filemove;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-import java.util.stream.IntStream;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import org.apache.commons.io.FileUtils;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.api.utils.log.LogTester;
-import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.ce.task.projectanalysis.analysis.Analysis;
-import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
-import org.sonar.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.component.FileAttributes;
-import org.sonar.ce.task.projectanalysis.component.ReportComponent;
-import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
-import org.sonar.ce.task.projectanalysis.source.SourceLinesHashRepository;
-import org.sonar.ce.task.step.TestComputationStepContext;
-import org.sonar.core.hash.SourceLineHashesComputer;
-import org.sonar.core.util.Uuids;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.BranchType;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.source.FileSourceDto;
-
-import static java.util.Arrays.stream;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
-import static org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStep.MIN_REQUIRED_SCORE;
-import static org.sonar.db.component.BranchType.*;
-
-public class FileMoveDetectionStepTest {
-
-  private static final String SNAPSHOT_UUID = "uuid_1";
-  private static final Analysis ANALYSIS = new Analysis.Builder()
-    .setUuid(SNAPSHOT_UUID)
-    .setCreatedAt(86521)
-    .build();
-  private static final int ROOT_REF = 1;
-  private static final int FILE_1_REF = 2;
-  private static final int FILE_2_REF = 3;
-  private static final int FILE_3_REF = 4;
-  private static final String[] CONTENT1 = {
-    "package org.sonar.ce.task.projectanalysis.filemove;",
-    "",
-    "public class Foo {",
-    "  public String bar() {",
-    "    return \"Doh!\";",
-    "  }",
-    "}"
-  };
-
-  private static final String[] LESS_CONTENT1 = {
-    "package org.sonar.ce.task.projectanalysis.filemove;",
-    "",
-    "public class Foo {",
-    "  public String foo() {",
-    "    return \"Donut!\";",
-    "  }",
-    "}"
-  };
-  private static final String[] CONTENT_EMPTY = {
-    ""
-  };
-  private static final String[] CONTENT2 = {
-    "package org.sonar.ce.queue;",
-    "",
-    "import com.google.common.base.MoreObjects;",
-    "import javax.annotation.CheckForNull;",
-    "import javax.annotation.Nullable;",
-    "import javax.annotation.concurrent.Immutable;",
-    "",
-    "import static com.google.common.base.Strings.emptyToNull;",
-    "import static java.util.Objects.requireNonNull;",
-    "",
-    "@Immutable",
-    "public class CeTask {",
-    "",
-    ",  private final String type;",
-    ",  private final String uuid;",
-    ",  private final String componentUuid;",
-    ",  private final String componentKey;",
-    ",  private final String componentName;",
-    ",  private final String submitterLogin;",
-    "",
-    ",  private CeTask(Builder builder) {",
-    ",    this.uuid = requireNonNull(emptyToNull(builder.uuid));",
-    ",    this.type = requireNonNull(emptyToNull(builder.type));",
-    ",    this.componentUuid = emptyToNull(builder.componentUuid);",
-    ",    this.componentKey = emptyToNull(builder.componentKey);",
-    ",    this.componentName = emptyToNull(builder.componentName);",
-    ",    this.submitterLogin = emptyToNull(builder.submitterLogin);",
-    ",  }",
-    "",
-    ",  public String getUuid() {",
-    ",    return uuid;",
-    ",  }",
-    "",
-    ",  public String getType() {",
-    ",    return type;",
-    ",  }",
-    "",
-    ",  @CheckForNull",
-    ",  public String getComponentUuid() {",
-    ",    return componentUuid;",
-    ",  }",
-    "",
-    ",  @CheckForNull",
-    ",  public String getComponentKey() {",
-    ",    return componentKey;",
-    ",  }",
-    "",
-    ",  @CheckForNull",
-    ",  public String getComponentName() {",
-    ",    return componentName;",
-    ",  }",
-    "",
-    ",  @CheckForNull",
-    ",  public String getSubmitterLogin() {",
-    ",    return submitterLogin;",
-    ",  }",
-    ",}",
-  };
-  // removed immutable annotation
-  private static final String[] LESS_CONTENT2 = {
-    "package org.sonar.ce.queue;",
-    "",
-    "import com.google.common.base.MoreObjects;",
-    "import javax.annotation.CheckForNull;",
-    "import javax.annotation.Nullable;",
-    "",
-    "import static com.google.common.base.Strings.emptyToNull;",
-    "import static java.util.Objects.requireNonNull;",
-    "",
-    "public class CeTask {",
-    "",
-    ",  private final String type;",
-    ",  private final String uuid;",
-    ",  private final String componentUuid;",
-    ",  private final String componentKey;",
-    ",  private final String componentName;",
-    ",  private final String submitterLogin;",
-    "",
-    ",  private CeTask(Builder builder) {",
-    ",    this.uuid = requireNonNull(emptyToNull(builder.uuid));",
-    ",    this.type = requireNonNull(emptyToNull(builder.type));",
-    ",    this.componentUuid = emptyToNull(builder.componentUuid);",
-    ",    this.componentKey = emptyToNull(builder.componentKey);",
-    ",    this.componentName = emptyToNull(builder.componentName);",
-    ",    this.submitterLogin = emptyToNull(builder.submitterLogin);",
-    ",  }",
-    "",
-    ",  public String getUuid() {",
-    ",    return uuid;",
-    ",  }",
-    "",
-    ",  public String getType() {",
-    ",    return type;",
-    ",  }",
-    "",
-    ",  @CheckForNull",
-    ",  public String getComponentUuid() {",
-    ",    return componentUuid;",
-    ",  }",
-    "",
-    ",  @CheckForNull",
-    ",  public String getComponentKey() {",
-    ",    return componentKey;",
-    ",  }",
-    "",
-    ",  @CheckForNull",
-    ",  public String getComponentName() {",
-    ",    return componentName;",
-    ",  }",
-    "",
-    ",  @CheckForNull",
-    ",  public String getSubmitterLogin() {",
-    ",    return submitterLogin;",
-    ",  }",
-    ",}",
-  };
-
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-  @Rule
-  public MutableMovedFilesRepositoryRule movedFilesRepository = new MutableMovedFilesRepositoryRule();
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-  @Rule
-  public LogTester logTester = new LogTester();
-
-  private final DbClient dbClient = dbTester.getDbClient();
-  private ComponentDto project;
-
-  private final AnalysisMetadataHolderRule analysisMetadataHolder = mock(AnalysisMetadataHolderRule.class);
-  private final SourceLinesHashRepository sourceLinesHash = mock(SourceLinesHashRepository.class);
-  private final FileSimilarity fileSimilarity = new FileSimilarityImpl(new SourceSimilarityImpl());
-  private final CapturingScoreMatrixDumper scoreMatrixDumper = new CapturingScoreMatrixDumper();
-  private final RecordingMutableAddedFileRepository addedFileRepository = new RecordingMutableAddedFileRepository();
-
-  private final FileMoveDetectionStep underTest = new FileMoveDetectionStep(analysisMetadataHolder, treeRootHolder, dbClient,
-    fileSimilarity, movedFilesRepository, sourceLinesHash, scoreMatrixDumper, addedFileRepository);
-
-  @Before
-  public void setUp() throws Exception {
-    project = dbTester.components().insertPrivateProject();
-    treeRootHolder.setRoot(builder(Component.Type.PROJECT, ROOT_REF).setUuid(project.uuid()).build());
-  }
-
-  @Test
-  public void getDescription_returns_description() {
-    assertThat(underTest.getDescription()).isEqualTo("Detect file moves");
-  }
-
-  @Test
-  public void execute_detects_no_move_if_in_pull_request_scope() {
-    prepareAnalysis(PULL_REQUEST, ANALYSIS);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
-    verifyStatistics(context, null, null, null, null);
-  }
-
-  @Test
-  public void execute_detects_no_move_on_first_analysis() {
-    prepareAnalysis(BRANCH, null);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
-    verifyStatistics(context, 0, null, null, null);
-  }
-
-  @Test
-  public void execute_detects_no_move_if_baseSnapshot_has_no_file_and_report_has_no_file() {
-    prepareBranchAnalysis(ANALYSIS);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
-    assertThat(addedFileRepository.getComponents()).isEmpty();
-    verifyStatistics(context, 0, null, null, null);
-  }
-
-  @Test
-  public void execute_detects_no_move_if_baseSnapshot_has_no_file() {
-    prepareBranchAnalysis(ANALYSIS);
-    Component file1 = fileComponent(FILE_1_REF, null);
-    Component file2 = fileComponent(FILE_2_REF, null);
-    setFilesInReport(file1, file2);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
-    assertThat(addedFileRepository.getComponents()).containsOnly(file1, file2);
-    verifyStatistics(context, 2, 0, 2, null);
-  }
-
-  @Test
-  public void execute_detects_no_move_if_there_is_no_file_in_report() {
-    prepareBranchAnalysis(ANALYSIS);
-    insertFiles( /* no components */);
-    setFilesInReport();
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
-    assertThat(addedFileRepository.getComponents()).isEmpty();
-    verifyStatistics(context, 0, null, null, null);
-  }
-
-  @Test
-  public void execute_detects_no_move_if_file_key_exists_in_both_DB_and_report() {
-    prepareBranchAnalysis(ANALYSIS);
-    Component file1 = fileComponent(FILE_1_REF, null);
-    Component file2 = fileComponent(FILE_2_REF, null);
-    insertFiles(file1.getUuid(), file2.getUuid());
-    insertContentOfFileInDb(file1.getUuid(), CONTENT1);
-    insertContentOfFileInDb(file2.getUuid(), CONTENT2);
-    setFilesInReport(file2, file1);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
-    assertThat(addedFileRepository.getComponents()).isEmpty();
-    verifyStatistics(context, 2, 2, 0, null);
-  }
-
-  @Test
-  public void execute_detects_move_if_content_of_file_is_same_in_DB_and_report() {
-    prepareBranchAnalysis(ANALYSIS);
-    Component file1 = fileComponent(FILE_1_REF, null);
-    Component file2 = fileComponent(FILE_2_REF, CONTENT1);
-    ComponentDto[] dtos = insertFiles(file1.getUuid());
-    insertContentOfFileInDb(file1.getUuid(), CONTENT1);
-    setFilesInReport(file2);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).containsExactly(file2);
-    MovedFilesRepository.OriginalFile originalFile = movedFilesRepository.getOriginalFile(file2).get();
-    assertThat(originalFile.key()).isEqualTo(dtos[0].getKey());
-    assertThat(originalFile.uuid()).isEqualTo(dtos[0].uuid());
-    assertThat(addedFileRepository.getComponents()).isEmpty();
-    verifyStatistics(context, 1, 1, 1, 1);
-  }
-
-  @Test
-  public void execute_detects_no_move_if_content_of_file_is_not_similar_enough() {
-    prepareBranchAnalysis(ANALYSIS);
-    Component file1 = fileComponent(FILE_1_REF, null);
-    Component file2 = fileComponent(FILE_2_REF, LESS_CONTENT1);
-    insertFiles(file1.getKey());
-    insertContentOfFileInDb(file1.getKey(), CONTENT1);
-    setFilesInReport(file2);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
-    assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore())
-      .isPositive()
-      .isLessThan(MIN_REQUIRED_SCORE);
-    assertThat(addedFileRepository.getComponents()).contains(file2);
-    verifyStatistics(context, 1, 1, 1, 0);
-  }
-
-  @Test
-  public void execute_detects_no_move_if_content_of_file_is_empty_in_DB() {
-    prepareBranchAnalysis(ANALYSIS);
-    Component file1 = fileComponent(FILE_1_REF, null);
-    Component file2 = fileComponent(FILE_2_REF, CONTENT1);
-    insertFiles(file1.getKey());
-    insertContentOfFileInDb(file1.getKey(), CONTENT_EMPTY);
-    setFilesInReport(file2);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
-    assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isZero();
-    assertThat(addedFileRepository.getComponents()).contains(file2);
-    verifyStatistics(context, 1, 1, 1, 0);
-  }
-
-  @Test
-  public void execute_detects_no_move_if_content_of_file_has_no_path_in_DB() {
-    prepareBranchAnalysis(ANALYSIS);
-    Component file1 = fileComponent(FILE_1_REF, null);
-    Component file2 = fileComponent(FILE_2_REF, CONTENT1);
-    insertFiles(key -> newComponentDto(key).setPath(null), file1.getKey());
-    insertContentOfFileInDb(file1.getKey(), CONTENT1);
-    setFilesInReport(file2);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
-    assertThat(scoreMatrixDumper.scoreMatrix).isNull();
-    assertThat(addedFileRepository.getComponents()).containsOnly(file2);
-    verifyStatistics(context, 1, 0, 1, null);
-  }
-
-  @Test
-  public void execute_detects_no_move_if_content_of_file_is_empty_in_report() {
-    prepareBranchAnalysis(ANALYSIS);
-    Component file1 = fileComponent(FILE_1_REF, null);
-    Component file2 = fileComponent(FILE_2_REF, CONTENT_EMPTY);
-    insertFiles(file1.getKey());
-    insertContentOfFileInDb(file1.getKey(), CONTENT1);
-    setFilesInReport(file2);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
-    assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isZero();
-    assertThat(addedFileRepository.getComponents()).contains(file2);
-    verifyStatistics(context, 1, 1, 1, 0);
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("max score in matrix is less than min required score (85). Do nothing.");
-  }
-
-  @Test
-  public void execute_detects_no_move_if_two_added_files_have_same_content_as_the_one_in_db() {
-    prepareBranchAnalysis(ANALYSIS);
-    Component file1 = fileComponent(FILE_1_REF, null);
-    Component file2 = fileComponent(FILE_2_REF, CONTENT1);
-    Component file3 = fileComponent(FILE_3_REF, CONTENT1);
-    insertFiles(file1.getKey());
-    insertContentOfFileInDb(file1.getKey(), CONTENT1);
-    setFilesInReport(file2, file3);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
-    assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isEqualTo(100);
-    assertThat(addedFileRepository.getComponents()).containsOnly(file2, file3);
-    verifyStatistics(context, 2, 1, 2, 0);
-  }
-
-  @Test
-  public void execute_detects_no_move_if_two_deleted_files_have_same_content_as_the_one_added() {
-    prepareBranchAnalysis(ANALYSIS);
-    Component file1 = fileComponent(FILE_1_REF, null);
-    Component file2 = fileComponent(FILE_2_REF, null);
-    Component file3 = fileComponent(FILE_3_REF, CONTENT1);
-    insertFiles(file1.getUuid(), file2.getUuid());
-    insertContentOfFileInDb(file1.getUuid(), CONTENT1);
-    insertContentOfFileInDb(file2.getUuid(), CONTENT1);
-    setFilesInReport(file3);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
-    assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isEqualTo(100);
-    assertThat(addedFileRepository.getComponents()).containsOnly(file3);
-    verifyStatistics(context, 1, 2, 1, 0);
-  }
-
-  @Test
-  public void execute_detects_no_move_if_two_files_are_empty_in_DB() {
-    prepareBranchAnalysis(ANALYSIS);
-    Component file1 = fileComponent(FILE_1_REF, null);
-    Component file2 = fileComponent(FILE_2_REF, null);
-    insertFiles(file1.getUuid(), file2.getUuid());
-    insertContentOfFileInDb(file1.getUuid(), null);
-    insertContentOfFileInDb(file2.getUuid(), null);
-    setFilesInReport(file1, file2);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
-    assertThat(scoreMatrixDumper.scoreMatrix).isNull();
-    assertThat(addedFileRepository.getComponents()).isEmpty();
-    verifyStatistics(context, 2, 2, 0, null);
-  }
-
-  @Test
-  public void execute_detects_several_moves() {
-    // testing:
-    // - file1 renamed to file3
-    // - file2 deleted
-    // - file4 untouched
-    // - file5 renamed to file6 with a small change
-    prepareBranchAnalysis(ANALYSIS);
-    Component file1 = fileComponent(FILE_1_REF, null);
-    Component file2 = fileComponent(FILE_2_REF, null);
-    Component file3 = fileComponent(FILE_3_REF, CONTENT1);
-    Component file4 = fileComponent(5, new String[] {"a", "b"});
-    Component file5 = fileComponent(6, null);
-    Component file6 = fileComponent(7, LESS_CONTENT2);
-    ComponentDto[] dtos = insertFiles(file1.getUuid(), file2.getUuid(), file4.getUuid(), file5.getUuid());
-    insertContentOfFileInDb(file1.getUuid(), CONTENT1);
-    insertContentOfFileInDb(file2.getUuid(), LESS_CONTENT1);
-    insertContentOfFileInDb(file4.getUuid(), new String[] {"e", "f", "g", "h", "i"});
-    insertContentOfFileInDb(file5.getUuid(), CONTENT2);
-    setFilesInReport(file3, file4, file6);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).containsOnly(file3, file6);
-    MovedFilesRepository.OriginalFile originalFile2 = movedFilesRepository.getOriginalFile(file3).get();
-    assertThat(originalFile2.key()).isEqualTo(dtos[0].getKey());
-    assertThat(originalFile2.uuid()).isEqualTo(dtos[0].uuid());
-    MovedFilesRepository.OriginalFile originalFile5 = movedFilesRepository.getOriginalFile(file6).get();
-    assertThat(originalFile5.key()).isEqualTo(dtos[3].getKey());
-    assertThat(originalFile5.uuid()).isEqualTo(dtos[3].uuid());
-    assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isGreaterThan(MIN_REQUIRED_SCORE);
-    assertThat(addedFileRepository.getComponents()).isEmpty();
-    verifyStatistics(context, 3, 4, 2, 2);
-  }
-
-  @Test
-  public void execute_does_not_compute_any_distance_if_all_files_sizes_are_all_too_different() {
-    prepareBranchAnalysis(ANALYSIS);
-    Component file1 = fileComponent(FILE_1_REF, null);
-    Component file2 = fileComponent(FILE_2_REF, null);
-    Component file3 = fileComponent(FILE_3_REF, arrayOf(118));
-    Component file4 = fileComponent(5, arrayOf(25));
-    insertFiles(file1.getKey(), file2.getKey());
-    insertContentOfFileInDb(file1.getKey(), arrayOf(100));
-    insertContentOfFileInDb(file2.getKey(), arrayOf(30));
-    setFilesInReport(file3, file4);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
-    assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isZero();
-    verifyStatistics(context, 2, 2, 2, 0);
-  }
-
-  /**
-   * Creates an array of {@code numberOfElements} int values as String, starting with zero.
-   */
-  private static String[] arrayOf(int numberOfElements) {
-    return IntStream.range(0, numberOfElements).mapToObj(String::valueOf).toArray(String[]::new);
-  }
-
-  /**
-   * JH: A bug was encountered in the algorithm and I didn't manage to forge a simpler test case.
-   */
-  @Test
-  public void real_life_use_case() throws Exception {
-    prepareBranchAnalysis(ANALYSIS);
-    for (File f : FileUtils.listFiles(new File("src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1"), null, false)) {
-      insertFiles("uuid_" + f.getName().hashCode());
-      insertContentOfFileInDb("uuid_" + f.getName().hashCode(), readLines(f));
-    }
-
-    Map<String, Component> comps = new HashMap<>();
-    int i = 1;
-    for (File f : FileUtils.listFiles(new File("src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2"), null, false)) {
-      String[] lines = readLines(f);
-      Component c = builder(Component.Type.FILE, i++)
-        .setUuid("uuid_" + f.getName().hashCode())
-        .setKey(f.getName())
-        .setName(f.getName())
-        .setFileAttributes(new FileAttributes(false, null, lines.length))
-        .build();
-
-      comps.put(f.getName(), c);
-      setFileLineHashesInReport(c, lines);
-    }
-
-    setFilesInReport(comps.values().toArray(new Component[0]));
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    Component makeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex = comps.get("MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex.java");
-    Component migrationRb1238 = comps.get("1238_make_component_uuid_and_analysis_uuid_not_null_on_duplications_index.rb");
-    Component addComponentUuidAndAnalysisUuidColumnToDuplicationsIndex = comps.get("AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex.java");
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).containsOnly(
-      makeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex,
-      migrationRb1238,
-      addComponentUuidAndAnalysisUuidColumnToDuplicationsIndex);
-
-    assertThat(movedFilesRepository.getOriginalFile(makeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex).get().uuid())
-      .isEqualTo("uuid_" + "MakeComponentUuidNotNullOnDuplicationsIndex.java".hashCode());
-    assertThat(movedFilesRepository.getOriginalFile(migrationRb1238).get().uuid())
-      .isEqualTo("uuid_" + "1242_make_analysis_uuid_not_null_on_duplications_index.rb".hashCode());
-    assertThat(movedFilesRepository.getOriginalFile(addComponentUuidAndAnalysisUuidColumnToDuplicationsIndex).get().uuid())
-      .isEqualTo("uuid_" + "AddComponentUuidColumnToDuplicationsIndex.java".hashCode());
-    verifyStatistics(context, comps.values().size(), 12, 6, 3);
-  }
-
-  private String[] readLines(File filename) throws IOException {
-    return FileUtils
-      .readLines(filename, StandardCharsets.UTF_8)
-      .toArray(new String[0]);
-  }
-
-  @CheckForNull
-  private FileSourceDto insertContentOfFileInDb(String uuid, @Nullable String[] content) {
-    return dbTester.getDbClient().componentDao().selectByUuid(dbTester.getSession(), uuid)
-      .map(file -> {
-        SourceLineHashesComputer linesHashesComputer = new SourceLineHashesComputer();
-        if (content != null) {
-          stream(content).forEach(linesHashesComputer::addLine);
-        }
-        FileSourceDto fileSourceDto = new FileSourceDto()
-          .setUuid(Uuids.createFast())
-          .setFileUuid(file.uuid())
-          .setProjectUuid(file.branchUuid())
-          .setLineHashes(linesHashesComputer.getLineHashes());
-        dbTester.getDbClient().fileSourceDao().insert(dbTester.getSession(), fileSourceDto);
-        dbTester.commit();
-        return fileSourceDto;
-      }).orElse(null);
-  }
-
-  private void setFilesInReport(Component... files) {
-    treeRootHolder.setRoot(builder(Component.Type.PROJECT, ROOT_REF)
-      .setUuid(project.uuid())
-      .addChildren(files)
-      .build());
-  }
-
-  private ComponentDto[] insertFiles(String... uuids) {
-    return insertFiles(this::newComponentDto, uuids);
-  }
-
-  private ComponentDto[] insertFiles(Function<String, ComponentDto> newComponentDto, String... uuids) {
-    return stream(uuids)
-      .map(newComponentDto)
-      .map(dto -> dbTester.components().insertComponent(dto))
-      .toArray(ComponentDto[]::new);
-  }
-
-  private ComponentDto newComponentDto(String uuid) {
-    return ComponentTesting.newFileDto(project)
-      .setKey("key_" + uuid)
-      .setUuid(uuid)
-      .setPath("path_" + uuid);
-  }
-
-  private Component fileComponent(int ref, @Nullable String[] content) {
-    ReportComponent component = builder(Component.Type.FILE, ref)
-      .setName("report_path" + ref)
-      .setFileAttributes(new FileAttributes(false, null, content == null ? 1 : content.length))
-      .build();
-    if (content != null) {
-      setFileLineHashesInReport(component, content);
-    }
-    return component;
-  }
-
-  private void setFileLineHashesInReport(Component file, String[] content) {
-    SourceLineHashesComputer computer = new SourceLineHashesComputer();
-    for (String line : content) {
-      computer.addLine(line);
-    }
-    when(sourceLinesHash.getLineHashesMatchingDBVersion(file)).thenReturn(computer.getLineHashes());
-  }
-
-  private static class CapturingScoreMatrixDumper implements ScoreMatrixDumper {
-    private ScoreMatrix scoreMatrix;
-
-    @Override
-    public void dumpAsCsv(ScoreMatrix scoreMatrix) {
-      this.scoreMatrix = scoreMatrix;
-    }
-  }
-
-  private void prepareBranchAnalysis(Analysis analysis) {
-    prepareAnalysis(BRANCH, analysis);
-  }
-
-  private void prepareAnalysis(BranchType branch, Analysis analysis) {
-    mockBranchType(branch);
-    analysisMetadataHolder.setBaseAnalysis(analysis);
-  }
-
-  private void mockBranchType(BranchType branchType) {
-    when(analysisMetadataHolder.isPullRequest()).thenReturn(branchType == PULL_REQUEST);
-  }
-
-  public static void verifyStatistics(TestComputationStepContext context,
-    @Nullable Integer expectedReportFiles, @Nullable Integer expectedDbFiles,
-    @Nullable Integer expectedAddedFiles, @Nullable Integer expectedMovedFiles) {
-    context.getStatistics().assertValue("reportFiles", expectedReportFiles);
-    context.getStatistics().assertValue("dbFiles", expectedDbFiles);
-    context.getStatistics().assertValue("addedFiles", expectedAddedFiles);
-    context.getStatistics().assertValue("movedFiles", expectedMovedFiles);
-  }
-
-  public static class RecordingMutableAddedFileRepository implements MutableAddedFileRepository {
-    private final List<Component> components = new ArrayList<>();
-
-    @Override
-    public void register(Component file) {
-      components.add(file);
-    }
-
-    @Override
-    public boolean isAdded(Component component) {
-      throw new UnsupportedOperationException("isAdded should not be called");
-    }
-
-    public List<Component> getComponents() {
-      return components;
-    }
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/filemove/PullRequestFileMoveDetectionStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/filemove/PullRequestFileMoveDetectionStepTest.java
deleted file mode 100644 (file)
index 5c734d0..0000000
+++ /dev/null
@@ -1,385 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.ce.task.projectanalysis.filemove;
-
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.Immutable;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.api.utils.log.LogTester;
-import org.sonar.ce.task.projectanalysis.analysis.Analysis;
-import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
-import org.sonar.ce.task.projectanalysis.analysis.Branch;
-import org.sonar.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.component.FileAttributes;
-import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
-import org.sonar.ce.task.projectanalysis.filemove.MovedFilesRepository.OriginalFile;
-import org.sonar.ce.task.step.TestComputationStepContext;
-import org.sonar.core.util.Uuids;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.BranchType;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.source.FileSourceDto;
-import org.sonar.server.project.Project;
-
-import static java.util.function.Function.identity;
-import static java.util.stream.Collectors.toMap;
-import static java.util.stream.Collectors.toSet;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.ce.task.projectanalysis.component.Component.Type.FILE;
-import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT;
-import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
-import static org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.RecordingMutableAddedFileRepository;
-import static org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.verifyStatistics;
-import static org.sonar.db.component.BranchType.BRANCH;
-import static org.sonar.db.component.BranchType.PULL_REQUEST;
-
-public class PullRequestFileMoveDetectionStepTest {
-  private static final String ROOT_REF = "0";
-  private static final String FILE_1_REF = "1";
-  private static final String FILE_2_REF = "2";
-  private static final String FILE_3_REF = "3";
-  private static final String FILE_4_REF = "4";
-  private static final String FILE_5_REF = "5";
-  private static final String FILE_6_REF = "6";
-  private static final String FILE_7_REF = "7";
-  private static final String TARGET_BRANCH = "target_branch";
-  private static final String BRANCH_UUID = "branch_uuid";
-  private static final String SNAPSHOT_UUID = "uuid_1";
-
-  private static final Analysis ANALYSIS = new Analysis.Builder()
-    .setUuid(SNAPSHOT_UUID)
-    .setCreatedAt(86521)
-    .build();
-
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-  @Rule
-  public MutableMovedFilesRepositoryRule movedFilesRepository = new MutableMovedFilesRepositoryRule();
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-  @Rule
-  public LogTester logTester = new LogTester();
-
-  private ComponentDto branch;
-  private ComponentDto project;
-
-  private final DbClient dbClient = dbTester.getDbClient();
-  private final AnalysisMetadataHolderRule analysisMetadataHolder = mock(AnalysisMetadataHolderRule.class);
-  private final RecordingMutableAddedFileRepository addedFileRepository = new RecordingMutableAddedFileRepository();
-  private final PullRequestFileMoveDetectionStep underTest = new PullRequestFileMoveDetectionStep(analysisMetadataHolder, treeRootHolder, dbClient, movedFilesRepository, addedFileRepository);
-
-  @Before
-  public void setUp() throws Exception {
-    project = dbTester.components().insertPrivateProject();
-    branch = dbTester.components().insertProjectBranch(project, branchDto -> branchDto.setUuid(BRANCH_UUID).setKey(TARGET_BRANCH));
-    treeRootHolder.setRoot(builder(Component.Type.PROJECT, Integer.parseInt(ROOT_REF)).setUuid(project.uuid()).build());
-  }
-
-  @Test
-  public void getDescription_returns_description() {
-    assertThat(underTest.getDescription()).isEqualTo("Detect file moves in Pull Request scope");
-  }
-
-  @Test
-  public void execute_does_not_detect_any_files_if_not_in_pull_request_scope() {
-    prepareAnalysis(BRANCH, ANALYSIS);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
-    verifyStatistics(context, null, null, null, null);
-  }
-
-  @Test
-  public void execute_detects_no_move_if_report_has_no_file() {
-    preparePullRequestAnalysis(ANALYSIS);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
-    assertThat(addedFileRepository.getComponents()).isEmpty();
-    verifyStatistics(context, 0, null, null, null);
-  }
-
-  @Test
-  public void execute_detects_no_move_if_target_branch_has_no_files() {
-    preparePullRequestAnalysis(ANALYSIS);
-    Set<FileReference> fileReferences = Set.of(FileReference.of(FILE_1_REF), FileReference.of(FILE_2_REF));
-    Map<String, Component> reportFilesByUuid = initializeAnalysisReportComponents(fileReferences);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
-    assertThat(addedFileRepository.getComponents()).containsOnlyOnceElementsOf(reportFilesByUuid.values());
-    verifyStatistics(context, 2, 0, 2, null);
-  }
-
-  @Test
-  public void execute_detects_no_move_if_there_are_no_files_in_report() {
-    preparePullRequestAnalysis(ANALYSIS);
-    initializeAnalysisReportComponents(Set.of());
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
-    assertThat(addedFileRepository.getComponents()).isEmpty();
-    verifyStatistics(context, 0, null, null, null);
-  }
-
-  @Test
-  public void execute_detects_no_move_if_file_key_exists_in_both_database_and_report() {
-    preparePullRequestAnalysis(ANALYSIS);
-
-    Set<FileReference> fileReferences = Set.of(FileReference.of(FILE_1_REF), FileReference.of(FILE_2_REF));
-    initializeAnalysisReportComponents(fileReferences);
-    initializeTargetBranchDatabaseComponents(fileReferences);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty();
-    assertThat(addedFileRepository.getComponents()).isEmpty();
-    verifyStatistics(context, 2, 2, 0, 0);
-  }
-
-  @Test
-  public void execute_detects_renamed_file() {
-    // - FILE_1_REF on target branch is renamed to FILE_2_REF on Pull Request
-    preparePullRequestAnalysis(ANALYSIS);
-
-    Set<FileReference> reportFileReferences = Set.of(FileReference.of(FILE_2_REF, FILE_1_REF));
-    Set<FileReference> databaseFileReferences = Set.of(FileReference.of(FILE_1_REF));
-
-    Map<String, Component> reportFilesByUuid = initializeAnalysisReportComponents(reportFileReferences);
-    Map<String, Component> databaseFilesByUuid = initializeTargetBranchDatabaseComponents(databaseFileReferences);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(addedFileRepository.getComponents()).isEmpty();
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).hasSize(1);
-    assertThatFileRenameHasBeenDetected(reportFilesByUuid, databaseFilesByUuid, FILE_2_REF, FILE_1_REF);
-    verifyStatistics(context, 1, 1, 0, 1);
-  }
-
-  @Test
-  public void execute_detects_several_renamed_file() {
-    // - FILE_1_REF has been renamed to FILE_3_REF on Pull Request
-    // - FILE_2_REF has been deleted on Pull Request
-    // - FILE_4_REF has been left untouched
-    // - FILE_5_REF has been renamed to FILE_6_REF on Pull Request
-    // - FILE_7_REF has been added on Pull Request
-    preparePullRequestAnalysis(ANALYSIS);
-
-    Set<FileReference> reportFileReferences = Set.of(
-      FileReference.of(FILE_3_REF, FILE_1_REF),
-      FileReference.of(FILE_4_REF),
-      FileReference.of(FILE_6_REF, FILE_5_REF),
-      FileReference.of(FILE_7_REF));
-
-    Set<FileReference> databaseFileReferences = Set.of(
-      FileReference.of(FILE_1_REF),
-      FileReference.of(FILE_2_REF),
-      FileReference.of(FILE_4_REF),
-      FileReference.of(FILE_5_REF));
-
-    Map<String, Component> reportFilesByUuid = initializeAnalysisReportComponents(reportFileReferences);
-    Map<String, Component> databaseFilesByUuid = initializeTargetBranchDatabaseComponents(databaseFileReferences);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(addedFileRepository.getComponents()).hasSize(1);
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).hasSize(2);
-    assertThatFileAdditionHasBeenDetected(reportFilesByUuid, FILE_7_REF);
-    assertThatFileRenameHasBeenDetected(reportFilesByUuid, databaseFilesByUuid, FILE_3_REF, FILE_1_REF);
-    assertThatFileRenameHasBeenDetected(reportFilesByUuid, databaseFilesByUuid, FILE_6_REF, FILE_5_REF);
-    verifyStatistics(context, 4, 4, 1, 2);
-  }
-
-  private void assertThatFileAdditionHasBeenDetected(Map<String, Component> reportFilesByUuid, String fileInReportReference) {
-    Component fileInReport = reportFilesByUuid.get(fileInReportReference);
-
-    assertThat(addedFileRepository.getComponents()).contains(fileInReport);
-    assertThat(movedFilesRepository.getOriginalPullRequestFile(fileInReport)).isEmpty();
-  }
-
-
-  private void assertThatFileRenameHasBeenDetected(Map<String, Component> reportFilesByUuid, Map<String, Component> databaseFilesByUuid, String fileInReportReference, String originalFileInDatabaseReference) {
-    Component fileInReport = reportFilesByUuid.get(fileInReportReference);
-    Component originalFileInDatabase = databaseFilesByUuid.get(originalFileInDatabaseReference);
-
-    assertThat(movedFilesRepository.getComponentsWithOriginal()).contains(fileInReport);
-    assertThat(movedFilesRepository.getOriginalPullRequestFile(fileInReport)).isPresent();
-
-    OriginalFile detectedOriginalFile = movedFilesRepository.getOriginalPullRequestFile(fileInReport).get();
-    assertThat(detectedOriginalFile.key()).isEqualTo(originalFileInDatabase.getKey());
-    assertThat(detectedOriginalFile.uuid()).isEqualTo(originalFileInDatabase.getUuid());
-  }
-
-  private Map<String, Component> initializeTargetBranchDatabaseComponents(Set<FileReference> references) {
-    Set<Component> fileComponents = createFileComponents(references);
-    insertFileComponentsInDatabase(fileComponents);
-    return toFileComponentsByUuidMap(fileComponents);
-  }
-
-  private Map<String, Component> initializeAnalysisReportComponents(Set<FileReference> refs) {
-    Set<Component> fileComponents = createFileComponents(refs);
-    insertFileComponentsInReport(fileComponents);
-    return toFileComponentsByUuidMap(fileComponents);
-  }
-
-  private Map<String, Component> toFileComponentsByUuidMap(Set<Component> fileComponents) {
-    return fileComponents
-      .stream()
-      .collect(toMap(Component::getUuid, identity()));
-  }
-
-  private static Set<Component> createFileComponents(Set<FileReference> references) {
-    return references
-      .stream()
-      .map(PullRequestFileMoveDetectionStepTest::createReportFileComponent)
-      .collect(toSet());
-  }
-
-  private static Component createReportFileComponent(FileReference fileReference) {
-    return builder(FILE, Integer.parseInt(fileReference.getReference()))
-      .setUuid(fileReference.getReference())
-      .setName("report_path" + fileReference.getReference())
-      .setFileAttributes(new FileAttributes(false, null, 1, false, composeComponentPath(fileReference.getPastReference())))
-      .build();
-  }
-
-  private void insertFileComponentsInReport(Set<Component> files) {
-    treeRootHolder
-      .setRoot(builder(PROJECT, Integer.parseInt(ROOT_REF))
-      .setUuid(project.uuid())
-      .addChildren(files.toArray(Component[]::new))
-      .build());
-  }
-
-  private Set<ComponentDto> insertFileComponentsInDatabase(Set<Component> files) {
-    return files
-      .stream()
-      .map(Component::getUuid)
-      .map(this::composeComponentDto)
-      .peek(this::insertComponentDto)
-      .peek(this::insertContentOfFileInDatabase)
-      .collect(toSet());
-  }
-
-  private void insertComponentDto(ComponentDto component) {
-    dbTester.components().insertComponent(component);
-  }
-
-  private ComponentDto composeComponentDto(String uuid) {
-    return ComponentTesting
-      .newFileDto(project)
-      .setBranchUuid(branch.uuid())
-      .setKey("key_" + uuid)
-      .setUuid(uuid)
-      .setPath(composeComponentPath(uuid));
-  }
-
-  @CheckForNull
-  private static String composeComponentPath(@Nullable String reference) {
-    return Optional.ofNullable(reference)
-      .map(r -> String.join("_", "path", r))
-      .orElse(null);
-  }
-
-  private FileSourceDto insertContentOfFileInDatabase(ComponentDto file) {
-    FileSourceDto fileSourceDto = composeFileSourceDto(file);
-    persistFileSourceDto(fileSourceDto);
-    return fileSourceDto;
-  }
-
-  private static FileSourceDto composeFileSourceDto(ComponentDto file) {
-    return new FileSourceDto()
-      .setUuid(Uuids.createFast())
-      .setFileUuid(file.uuid())
-      .setProjectUuid(file.branchUuid());
-  }
-
-  private void persistFileSourceDto(FileSourceDto fileSourceDto) {
-    dbTester.getDbClient().fileSourceDao().insert(dbTester.getSession(), fileSourceDto);
-    dbTester.commit();
-  }
-
-  private void preparePullRequestAnalysis(Analysis analysis) {
-    prepareAnalysis(PULL_REQUEST, analysis);
-  }
-
-  private void prepareAnalysis(BranchType branch, Analysis analysis) {
-    mockBranchType(branch);
-    analysisMetadataHolder.setBaseAnalysis(analysis);
-  }
-
-  private void mockBranchType(BranchType branchType) {
-    Branch branch = mock(Branch.class);
-    when(analysisMetadataHolder.getBranch()).thenReturn(branch);
-    when(analysisMetadataHolder.getBranch().getTargetBranchName()).thenReturn(TARGET_BRANCH);
-    when(analysisMetadataHolder.isPullRequest()).thenReturn(branchType == PULL_REQUEST);
-    when(analysisMetadataHolder.getProject()).thenReturn(Project.from(project));
-  }
-
-  @Immutable
-  private static class FileReference {
-    private final String reference;
-    private final String pastReference;
-
-    private FileReference(String reference, @Nullable String pastReference) {
-      this.reference = reference;
-      this.pastReference = pastReference;
-    }
-
-    public String getReference() {
-      return reference;
-    }
-
-    @CheckForNull
-    public String getPastReference() {
-      return pastReference;
-    }
-
-    public static FileReference of(String reference, String pastReference) {
-      return new FileReference(reference, pastReference);
-    }
-
-    public static FileReference of(String reference) {
-      return new FileReference(reference, null);
-    }
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/metric/MetricRepositoryImplTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/metric/MetricRepositoryImplTest.java
deleted file mode 100644 (file)
index 91ee223..0000000
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.ce.task.projectanalysis.metric;
-
-import java.util.List;
-import java.util.Random;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.db.metric.MetricDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-
-public class MetricRepositoryImplTest {
-  private static final String SOME_KEY = "some_key";
-  private static final String SOME_UUID = "uuid";
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private DbClient dbClient = dbTester.getDbClient();
-  private MetricRepositoryImpl underTest = new MetricRepositoryImpl(dbClient);
-
-  @Test
-  public void getByKey_throws_NPE_if_arg_is_null() {
-    assertThatThrownBy(() -> underTest.getByKey(null))
-      .isInstanceOf(NullPointerException.class);
-  }
-
-  @Test
-  public void getByKey_throws_ISE_if_start_has_not_been_called() {
-    assertThatThrownBy(() -> underTest.getByKey(SOME_KEY))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Metric cache has not been initialized");
-  }
-
-  @Test
-  public void getByKey_throws_ISE_of_Metric_does_not_exist() {
-    assertThatThrownBy(() -> {
-      underTest.start();
-      underTest.getByKey(SOME_KEY);
-    })
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage(String.format("Metric with key '%s' does not exist", SOME_KEY));
-  }
-
-  @Test
-  public void getByKey_throws_ISE_of_Metric_is_disabled() {
-    dbTester.measures().insertMetric(t -> t.setKey("complexity").setEnabled(false));
-
-    underTest.start();
-
-    assertThatThrownBy(() -> underTest.getByKey("complexity"))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage(String.format("Metric with key '%s' does not exist", "complexity"));
-  }
-
-  @Test
-  public void getByKey_find_enabled_Metrics() {
-    MetricDto ncloc = dbTester.measures().insertMetric(t -> t.setKey("ncloc").setEnabled(true));
-    MetricDto coverage = dbTester.measures().insertMetric(t -> t.setKey("coverage").setEnabled(true));
-
-    underTest.start();
-
-    assertThat(underTest.getByKey("ncloc").getUuid()).isEqualTo(ncloc.getUuid());
-    assertThat(underTest.getByKey("coverage").getUuid()).isEqualTo(coverage.getUuid());
-  }
-
-  @Test
-  public void getById_throws_ISE_if_start_has_not_been_called() {
-    assertThatThrownBy(() -> underTest.getByUuid(SOME_UUID))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Metric cache has not been initialized");
-  }
-
-  @Test
-  public void getById_throws_ISE_of_Metric_does_not_exist() {
-    underTest.start();
-
-    assertThatThrownBy(() -> underTest.getByUuid(SOME_UUID))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage(String.format("Metric with uuid '%s' does not exist", SOME_UUID));
-  }
-
-  @Test
-  public void getById_throws_ISE_of_Metric_is_disabled() {
-    dbTester.measures().insertMetric(t -> t.setKey("complexity").setEnabled(false));
-
-    underTest.start();
-
-    assertThatThrownBy(() -> underTest.getByUuid(SOME_UUID))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage(String.format("Metric with uuid '%s' does not exist", SOME_UUID));
-  }
-
-  @Test
-  public void getById_find_enabled_Metrics() {
-    MetricDto ncloc = dbTester.measures().insertMetric(t -> t.setKey("ncloc").setEnabled(true));
-    MetricDto coverage = dbTester.measures().insertMetric(t -> t.setKey("coverage").setEnabled(true));
-
-    underTest.start();
-
-    assertThat(underTest.getByUuid(ncloc.getUuid()).getKey()).isEqualTo("ncloc");
-    assertThat(underTest.getByUuid(coverage.getUuid()).getKey()).isEqualTo("coverage");
-  }
-
-  @Test
-  public void getOptionalById_throws_ISE_if_start_has_not_been_called() {
-    assertThatThrownBy(() -> underTest.getOptionalByUuid(SOME_UUID))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Metric cache has not been initialized");
-  }
-
-  @Test
-  public void getOptionalById_returns_empty_of_Metric_does_not_exist() {
-    underTest.start();
-
-    assertThat(underTest.getOptionalByUuid(SOME_UUID)).isEmpty();
-  }
-
-  @Test
-  public void getOptionalById_returns_empty_of_Metric_is_disabled() {
-    dbTester.measures().insertMetric(t -> t.setKey("complexity").setEnabled(false));
-
-    underTest.start();
-
-    assertThat(underTest.getOptionalByUuid(SOME_UUID)).isEmpty();
-  }
-
-  @Test
-  public void getOptionalById_find_enabled_Metrics() {
-    MetricDto ncloc = dbTester.measures().insertMetric(t -> t.setKey("ncloc").setEnabled(true));
-    MetricDto coverage = dbTester.measures().insertMetric(t -> t.setKey("coverage").setEnabled(true));
-
-    underTest.start();
-
-    assertThat(underTest.getOptionalByUuid(ncloc.getUuid()).get().getKey()).isEqualTo("ncloc");
-    assertThat(underTest.getOptionalByUuid(coverage.getUuid()).get().getKey()).isEqualTo("coverage");
-  }
-
-  @Test
-  public void get_all_metrics() {
-    List<MetricDto> enabledMetrics = IntStream.range(0, 1 + new Random().nextInt(12))
-      .mapToObj(i -> dbTester.measures().insertMetric(t -> t.setKey("key_enabled_" + i).setEnabled(true)))
-      .toList();
-    IntStream.range(0, 1 + new Random().nextInt(12))
-      .forEach(i -> dbTester.measures().insertMetric(t -> t.setKey("key_disabled_" + i).setEnabled(false)));
-
-    underTest.start();
-    assertThat(underTest.getAll())
-      .extracting(Metric::getKey)
-      .containsOnly(enabledMetrics.stream().map(MetricDto::getKey).toArray(String[]::new));
-  }
-
-  @Test
-  public void getMetricsByType_givenRatingType_returnRatingMetrics() {
-    List<MetricDto> enabledMetrics = IntStream.range(0, 1 + new Random().nextInt(12))
-      .mapToObj(i -> dbTester.measures().insertMetric(t -> t.setKey("key_enabled_" + i).setEnabled(true).setValueType("RATING")))
-      .toList();
-
-    underTest.start();
-    assertThat(underTest.getMetricsByType(Metric.MetricType.RATING))
-      .extracting(Metric::getKey)
-      .containsOnly(enabledMetrics.stream().map(MetricDto::getKey).toArray(String[]::new));
-  }
-
-  @Test
-  public void getMetricsByType_givenRatingTypeAndWantedMilisecType_returnEmptyList() {
-    IntStream.range(0, 1 + new Random().nextInt(12))
-      .mapToObj(i -> dbTester.measures().insertMetric(t -> t.setKey("key_enabled_" + i).setEnabled(true).setValueType("RATING")))
-      .collect(Collectors.toList());
-
-    underTest.start();
-    assertThat(underTest.getMetricsByType(Metric.MetricType.MILLISEC)).isEmpty();
-  }
-
-  @Test
-  public void getMetricsByType_givenOnlyMilisecTypeAndWantedRatingMetrics_returnEmptyList() {
-    IntStream.range(0, 1 + new Random().nextInt(12))
-      .mapToObj(i -> dbTester.measures().insertMetric(t -> t.setKey("key_enabled_" + i).setEnabled(true).setValueType("MILISEC")));
-
-    underTest.start();
-    assertThat(underTest.getMetricsByType(Metric.MetricType.RATING)).isEmpty();
-  }
-
-  @Test
-  public void getMetricsByType_givenMetricsAreNull_throwException() {
-    assertThatThrownBy(() -> underTest.getMetricsByType(Metric.MetricType.RATING))
-      .isInstanceOf(IllegalStateException.class);
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/scm/ScmInfoDbLoaderTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/scm/ScmInfoDbLoaderTest.java
deleted file mode 100644 (file)
index 93c4b40..0000000
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.ce.task.projectanalysis.scm;
-
-import com.google.common.collect.ImmutableList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Optional;
-import javax.annotation.Nullable;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.api.utils.log.LogTester;
-import org.sonar.ce.task.projectanalysis.analysis.Analysis;
-import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
-import org.sonar.ce.task.projectanalysis.analysis.Branch;
-import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
-import org.sonar.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.component.ReferenceBranchComponentUuids;
-import org.sonar.ce.task.projectanalysis.filemove.MutableMovedFilesRepositoryRule;
-import org.sonar.ce.task.projectanalysis.period.NewCodeReferenceBranchComponentUuids;
-import org.sonar.ce.task.projectanalysis.period.Period;
-import org.sonar.ce.task.projectanalysis.period.PeriodHolderRule;
-import org.sonar.core.hash.SourceHashComputer;
-import org.sonar.core.util.Uuids;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.BranchType;
-import org.sonar.db.newcodeperiod.NewCodePeriodType;
-import org.sonar.db.protobuf.DbFileSources;
-import org.sonar.db.source.FileSourceDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.api.utils.log.LoggerLevel.TRACE;
-import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
-
-public class ScmInfoDbLoaderTest {
-  static final int FILE_REF = 1;
-  static final Component FILE = builder(Component.Type.FILE, FILE_REF).setKey("FILE_KEY").setUuid("FILE_UUID").build();
-  static final long DATE_1 = 123456789L;
-
-  static Analysis baseProjectAnalysis = new Analysis.Builder()
-    .setUuid("uuid_1")
-    .setCreatedAt(123456789L)
-    .build();
-
-  @Rule
-  public LogTester logTester = new LogTester();
-  @Rule
-  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-  @Rule
-  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
-  @Rule
-  public MutableMovedFilesRepositoryRule movedFiles = new MutableMovedFilesRepositoryRule();
-  @Rule
-  public PeriodHolderRule periodHolder = new PeriodHolderRule();
-
-  private final Branch branch = mock(Branch.class);
-  private final ReferenceBranchComponentUuids referenceBranchComponentUuids = mock(ReferenceBranchComponentUuids.class);
-  private final NewCodeReferenceBranchComponentUuids newCodeReferenceBranchComponentUuids = mock(NewCodeReferenceBranchComponentUuids.class);
-
-  private final ScmInfoDbLoader underTest = new ScmInfoDbLoader(analysisMetadataHolder, movedFiles, dbTester.getDbClient(), referenceBranchComponentUuids,
-      newCodeReferenceBranchComponentUuids, periodHolder);
-
-  @Before
-  public void before() {
-    periodHolder.setPeriod(new Period(NewCodePeriodType.PREVIOUS_VERSION.name(), null, null));
-  }
-
-  @Test
-  public void returns_ScmInfo_from_DB() {
-    analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis);
-    analysisMetadataHolder.setBranch(null);
-
-    String hash = computeSourceHash(1);
-    addFileSourceInDb("henry", DATE_1, "rev-1", hash);
-
-    DbScmInfo scmInfo = underTest.getScmInfo(FILE).get();
-    assertThat(scmInfo.getAllChangesets()).hasSize(1);
-    assertThat(scmInfo.fileHash()).isEqualTo(hash);
-
-    assertThat(logTester.logs(TRACE)).containsOnly("Reading SCM info from DB for file 'FILE_UUID'");
-  }
-
-  @Test
-  public void read_from_reference_branch_if_no_base() {
-    analysisMetadataHolder.setBaseAnalysis(null);
-    analysisMetadataHolder.setBranch(branch);
-
-    String referenceFileUuid = "referenceFileUuid";
-    String hash = computeSourceHash(1);
-
-    when(referenceBranchComponentUuids.getComponentUuid(FILE.getKey())).thenReturn(referenceFileUuid);
-    addFileSourceInDb("henry", DATE_1, "rev-1", hash, referenceFileUuid);
-
-    DbScmInfo scmInfo = underTest.getScmInfo(FILE).get();
-    assertThat(scmInfo.getAllChangesets()).hasSize(1);
-    assertThat(scmInfo.fileHash()).isEqualTo(hash);
-    assertThat(logTester.logs(TRACE)).containsOnly("Reading SCM info from DB for file 'referenceFileUuid'");
-  }
-
-  @Test
-  public void read_from_target_if_pullrequest() {
-    Branch branch = mock(Branch.class);
-    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
-    analysisMetadataHolder.setBaseAnalysis(null);
-    analysisMetadataHolder.setBranch(branch);
-
-    String targetBranchFileUuid = "targetBranchFileUuid";
-    String hash = computeSourceHash(1);
-
-    when(referenceBranchComponentUuids.getComponentUuid(FILE.getKey())).thenReturn(targetBranchFileUuid);
-    addFileSourceInDb("henry", DATE_1, "rev-1", hash, targetBranchFileUuid);
-
-    DbScmInfo scmInfo = underTest.getScmInfo(FILE).get();
-    assertThat(scmInfo.getAllChangesets()).hasSize(1);
-    assertThat(scmInfo.fileHash()).isEqualTo(hash);
-    assertThat(logTester.logs(TRACE)).containsOnly("Reading SCM info from DB for file 'targetBranchFileUuid'");
-  }
-
-  @Test
-  public void read_from_target_if_reference_branch() {
-    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), null, null));
-
-    Branch branch = mock(Branch.class);
-    when(branch.getType()).thenReturn(BranchType.BRANCH);
-    analysisMetadataHolder.setBaseAnalysis(null);
-    analysisMetadataHolder.setBranch(branch);
-
-    String targetBranchFileUuid = "targetBranchFileUuid";
-    String hash = computeSourceHash(1);
-
-    when(newCodeReferenceBranchComponentUuids.getComponentUuid(FILE.getKey())).thenReturn(targetBranchFileUuid);
-    addFileSourceInDb("henry", DATE_1, "rev-1", hash, targetBranchFileUuid);
-
-    DbScmInfo scmInfo = underTest.getScmInfo(FILE).get();
-    assertThat(scmInfo.getAllChangesets()).hasSize(1);
-    assertThat(scmInfo.fileHash()).isEqualTo(hash);
-    assertThat(logTester.logs(TRACE)).containsOnly("Reading SCM info from DB for file 'targetBranchFileUuid'");
-  }
-
-  @Test
-  public void read_from_db_if_not_exist_in_reference_branch() {
-    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), null, null));
-
-    Branch branch = mock(Branch.class);
-    when(branch.getType()).thenReturn(BranchType.BRANCH);
-    analysisMetadataHolder.setBaseAnalysis(null);
-    analysisMetadataHolder.setBranch(branch);
-
-    String hash = computeSourceHash(1);
-
-    addFileSourceInDb("henry", DATE_1, "rev-1", hash, FILE.getUuid());
-
-    DbScmInfo scmInfo = underTest.getScmInfo(FILE).get();
-    assertThat(scmInfo.getAllChangesets()).hasSize(1);
-    assertThat(scmInfo.fileHash()).isEqualTo(hash);
-    assertThat(logTester.logs(TRACE)).containsOnly("Reading SCM info from DB for file 'FILE_UUID'");
-  }
-
-  @Test
-  public void return_empty_if_no_dto_available() {
-    analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis);
-    analysisMetadataHolder.setBranch(null);
-
-    Optional<DbScmInfo> scmInfo = underTest.getScmInfo(FILE);
-
-    assertThat(logTester.logs(TRACE)).containsOnly("Reading SCM info from DB for file 'FILE_UUID'");
-    assertThat(scmInfo).isEmpty();
-  }
-
-  @Test
-  public void do_not_read_from_db_on_first_analysis_if_there_is_no_reference_branch() {
-    Branch branch = mock(Branch.class);
-    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
-    analysisMetadataHolder.setBaseAnalysis(null);
-    analysisMetadataHolder.setBranch(branch);
-
-    assertThat(underTest.getScmInfo(FILE)).isEmpty();
-    assertThat(logTester.logs(TRACE)).isEmpty();
-  }
-
-  private static List<String> generateLines(int lineCount) {
-    ImmutableList.Builder<String> builder = ImmutableList.builder();
-    for (int i = 0; i < lineCount; i++) {
-      builder.add("line " + i);
-    }
-    return builder.build();
-  }
-
-  private static String computeSourceHash(int lineCount) {
-    SourceHashComputer sourceHashComputer = new SourceHashComputer();
-    Iterator<String> lines = generateLines(lineCount).iterator();
-    while (lines.hasNext()) {
-      sourceHashComputer.addLine(lines.next(), lines.hasNext());
-    }
-    return sourceHashComputer.getHash();
-  }
-
-  private void addFileSourceInDb(@Nullable String author, @Nullable Long date, @Nullable String revision, String srcHash) {
-    addFileSourceInDb(author, date, revision, srcHash, FILE.getUuid());
-  }
-
-  private void addFileSourceInDb(@Nullable String author, @Nullable Long date, @Nullable String revision, String srcHash, String fileUuid) {
-    DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder();
-    DbFileSources.Line.Builder builder = fileDataBuilder.addLinesBuilder()
-      .setLine(1);
-    if (author != null) {
-      builder.setScmAuthor(author);
-    }
-    if (date != null) {
-      builder.setScmDate(date);
-    }
-    if (revision != null) {
-      builder.setScmRevision(revision);
-    }
-    dbTester.getDbClient().fileSourceDao().insert(dbTester.getSession(), new FileSourceDto()
-      .setUuid(Uuids.createFast())
-      .setLineHashes(Collections.singletonList("lineHash"))
-      .setFileUuid(fileUuid)
-      .setProjectUuid("PROJECT_UUID")
-      .setSourceData(fileDataBuilder.build())
-      .setSrcHash(srcHash));
-    dbTester.commit();
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStepTest.java
deleted file mode 100644 (file)
index 6d7970d..0000000
+++ /dev/null
@@ -1,586 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.ce.task.projectanalysis.step;
-
-import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-import javax.annotation.Nullable;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.projectanalysis.analysis.Branch;
-import org.sonar.ce.task.projectanalysis.analysis.MutableAnalysisMetadataHolderRule;
-import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
-import org.sonar.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.component.DefaultBranchImpl;
-import org.sonar.ce.task.projectanalysis.component.MutableTreeRootHolderRule;
-import org.sonar.ce.task.step.TestComputationStepContext;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.BranchType;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.SnapshotDto;
-import org.sonar.scanner.protocol.output.ScannerReport;
-import org.sonar.scanner.protocol.output.ScannerReport.Component.ComponentType;
-import org.sonar.scanner.protocol.output.ScannerReport.Component.FileStatus;
-import org.sonar.server.project.Project;
-
-import static java.util.Optional.ofNullable;
-import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME;
-import static org.sonar.db.component.ComponentTesting.newDirectory;
-import static org.sonar.db.component.ComponentTesting.newFileDto;
-import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
-import static org.sonar.db.component.SnapshotTesting.newAnalysis;
-import static org.sonar.scanner.protocol.output.ScannerReport.Component.ComponentType.FILE;
-import static org.sonar.scanner.protocol.output.ScannerReport.Component.ComponentType.PROJECT;
-
-@RunWith(DataProviderRunner.class)
-public class BuildComponentTreeStepTest {
-  private static final String NO_SCANNER_PROJECT_VERSION = null;
-  private static final String NO_SCANNER_BUILD_STRING = null;
-
-  private static final int ROOT_REF = 1;
-  private static final int FILE_1_REF = 4;
-  private static final int FILE_2_REF = 5;
-  private static final int FILE_3_REF = 7;
-  private static final int UNCHANGED_FILE_REF = 10;
-
-  private static final String REPORT_PROJECT_KEY = "REPORT_PROJECT_KEY";
-  private static final String REPORT_DIR_PATH_1 = "src/main/java/dir1";
-  private static final String REPORT_FILE_PATH_1 = "src/main/java/dir1/File1.java";
-  private static final String REPORT_FILE_NAME_1 = "File1.java";
-  private static final String REPORT_DIR_PATH_2 = "src/main/java/dir2";
-  private static final String REPORT_FILE_PATH_2 = "src/main/java/dir2/File2.java";
-  private static final String REPORT_FILE_PATH_3 = "src/main/java/dir2/File3.java";
-  private static final String REPORT_UNCHANGED_FILE_PATH = "src/main/File3.java";
-
-  private static final long ANALYSIS_DATE = 123456789L;
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-  @Rule
-  public BatchReportReaderRule reportReader = new BatchReportReaderRule().setMetadata(createReportMetadata(NO_SCANNER_PROJECT_VERSION, NO_SCANNER_BUILD_STRING));
-  @Rule
-  public MutableTreeRootHolderRule treeRootHolder = new MutableTreeRootHolderRule();
-  @Rule
-  public MutableAnalysisMetadataHolderRule analysisMetadataHolder = new MutableAnalysisMetadataHolderRule();
-
-  private DbClient dbClient = dbTester.getDbClient();
-  private BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder);
-
-  @Test
-  public void fails_if_root_component_does_not_exist_in_reportReader() {
-    setAnalysisMetadataHolder();
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(NullPointerException.class);
-  }
-
-  @Test
-  public void verify_tree_is_correctly_built() {
-    setAnalysisMetadataHolder();
-    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF, FILE_2_REF, FILE_3_REF));
-    reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1));
-    reportReader.putComponent(componentWithPath(FILE_2_REF, FILE, REPORT_FILE_PATH_2));
-    reportReader.putComponent(componentWithPath(FILE_3_REF, FILE, REPORT_FILE_PATH_3));
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    Component root = treeRootHolder.getRoot();
-    assertThat(root).isNotNull();
-    verifyComponent(root, Component.Type.PROJECT, ROOT_REF, 1);
-
-    Component dir = root.getChildren().iterator().next();
-    verifyComponent(dir, Component.Type.DIRECTORY, null, 2);
-
-    Component dir1 = dir.getChildren().get(0);
-    verifyComponent(dir1, Component.Type.DIRECTORY, null, 1);
-    verifyComponent(dir1.getChildren().get(0), Component.Type.FILE, FILE_1_REF, 0);
-
-    Component dir2 = dir.getChildren().get(1);
-    verifyComponent(dir2, Component.Type.DIRECTORY, null, 2);
-    verifyComponent(dir2.getChildren().get(0), Component.Type.FILE, FILE_2_REF, 0);
-    verifyComponent(dir2.getChildren().get(1), Component.Type.FILE, FILE_3_REF, 0);
-
-    context.getStatistics().assertValue("components", 7);
-  }
-
-  @Test
-  public void verify_tree_is_correctly_built_in_prs() {
-    setAnalysisMetadataHolder(true);
-    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF, FILE_2_REF, FILE_3_REF, UNCHANGED_FILE_REF));
-    reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1));
-    reportReader.putComponent(componentWithPath(FILE_2_REF, FILE, REPORT_FILE_PATH_2));
-    reportReader.putComponent(componentWithPath(FILE_3_REF, FILE, REPORT_FILE_PATH_3));
-    reportReader.putComponent(unchangedComponentWithPath(UNCHANGED_FILE_REF, FILE, REPORT_UNCHANGED_FILE_PATH));
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    // modified root
-    Component mRoot = treeRootHolder.getRoot();
-    verifyComponent(mRoot, Component.Type.PROJECT, ROOT_REF, 1);
-
-    Component mDir = mRoot.getChildren().get(0);
-    assertThat(mDir.getName()).isEqualTo("src/main/java");
-    verifyComponent(mDir, Component.Type.DIRECTORY, null, 2);
-
-    Component mDir1 = mDir.getChildren().get(0);
-    assertThat(mDir1.getName()).isEqualTo("src/main/java/dir1");
-    verifyComponent(mDir1, Component.Type.DIRECTORY, null, 1);
-    verifyComponent(mDir1.getChildren().get(0), Component.Type.FILE, FILE_1_REF, 0);
-
-    Component mDir2 = mDir.getChildren().get(1);
-    assertThat(mDir2.getName()).isEqualTo("src/main/java/dir2");
-    verifyComponent(mDir2, Component.Type.DIRECTORY, null, 2);
-    verifyComponent(mDir2.getChildren().get(0), Component.Type.FILE, FILE_2_REF, 0);
-    verifyComponent(mDir2.getChildren().get(1), Component.Type.FILE, FILE_3_REF, 0);
-
-    // root
-    Component root = treeRootHolder.getReportTreeRoot();
-    verifyComponent(root, Component.Type.PROJECT, ROOT_REF, 1);
-
-    Component dir = root.getChildren().get(0);
-    assertThat(dir.getName()).isEqualTo("src/main");
-    verifyComponent(dir, Component.Type.DIRECTORY, null, 2);
-
-    Component dir1 = dir.getChildren().get(0);
-    assertThat(dir1.getName()).isEqualTo("src/main/java");
-    verifyComponent(dir1, Component.Type.DIRECTORY, null, 2);
-    verifyComponent(dir1.getChildren().get(0), Component.Type.DIRECTORY, null, 1);
-    verifyComponent(dir1.getChildren().get(1), Component.Type.DIRECTORY, null, 2);
-
-    Component dir2 = dir1.getChildren().get(0);
-    assertThat(dir2.getName()).isEqualTo("src/main/java/dir1");
-    verifyComponent(dir2, Component.Type.DIRECTORY, null, 1);
-    verifyComponent(dir2.getChildren().get(0), Component.Type.FILE, FILE_1_REF, 0);
-
-    Component dir3 = dir1.getChildren().get(1);
-    assertThat(dir3.getName()).isEqualTo("src/main/java/dir2");
-    verifyComponent(dir3, Component.Type.DIRECTORY, null, 2);
-    verifyComponent(dir3.getChildren().get(0), Component.Type.FILE, FILE_2_REF, 0);
-    verifyComponent(dir3.getChildren().get(1), Component.Type.FILE, FILE_3_REF, 0);
-
-    context.getStatistics().assertValue("components", 7);
-  }
-
-  /**
-   * SONAR-13262
-   */
-  @Test
-  public void verify_tree_is_correctly_built_in_prs_with_repeated_names() {
-    setAnalysisMetadataHolder(true);
-    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF));
-    reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_PROJECT_KEY + "/file.js"));
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    // modified root
-    Component mRoot = treeRootHolder.getRoot();
-    verifyComponent(mRoot, Component.Type.PROJECT, ROOT_REF, 1);
-
-    Component dir = mRoot.getChildren().get(0);
-    assertThat(dir.getName()).isEqualTo(REPORT_PROJECT_KEY);
-    assertThat(dir.getShortName()).isEqualTo(REPORT_PROJECT_KEY);
-
-    verifyComponent(dir, Component.Type.DIRECTORY, null, 1);
-  }
-
-  @Test
-  public void compute_keys_and_uuids() {
-    setAnalysisMetadataHolder();
-    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF));
-    reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1));
-
-    underTest.execute(new TestComputationStepContext());
-
-    verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, analysisMetadataHolder.getProject().getName());
-    verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, REPORT_DIR_PATH_1);
-    verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, REPORT_FILE_NAME_1);
-  }
-
-  @Test
-  public void return_existing_uuids() {
-    setAnalysisMetadataHolder();
-    ComponentDto project = insertComponent(newPrivateProjectDto("ABCD").setKey(REPORT_PROJECT_KEY));
-    ComponentDto directory = newDirectory(project, "CDEF", REPORT_DIR_PATH_1);
-    insertComponent(directory.setKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1));
-    insertComponent(newFileDto(project, directory, "DEFG")
-      .setKey(REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1)
-      .setPath(REPORT_FILE_PATH_1));
-
-    // new structure, without modules
-    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF));
-    reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1));
-
-    underTest.execute(new TestComputationStepContext());
-
-    verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, analysisMetadataHolder.getProject().getName(), "ABCD");
-    verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, REPORT_DIR_PATH_1, "CDEF");
-    verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, REPORT_FILE_NAME_1, "DEFG");
-  }
-
-  @Test
-  public void generate_keys_when_using_new_branch() {
-    Branch branch = mock(Branch.class);
-    when(branch.getName()).thenReturn("origin/feature");
-    when(branch.isMain()).thenReturn(false);
-    when(branch.generateKey(any(), any())).thenReturn("generated");
-    analysisMetadataHolder.setRootComponentRef(ROOT_REF)
-      .setAnalysisDate(ANALYSIS_DATE)
-      .setProject(Project.from(newPrivateProjectDto().setKey(REPORT_PROJECT_KEY)))
-      .setBranch(branch);
-    BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder);
-    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF));
-    reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1));
-
-    underTest.execute(new TestComputationStepContext());
-
-    verifyComponentByRef(ROOT_REF, "generated", analysisMetadataHolder.getProject().getName(), null);
-    verifyComponentByRef(FILE_1_REF, "generated", REPORT_FILE_NAME_1, null);
-  }
-
-  @Test
-  public void generate_keys_when_using_existing_branch() {
-    ComponentDto projectDto = dbTester.components().insertPublicProject();
-    String branchName = randomAlphanumeric(248);
-    ComponentDto componentDto = dbTester.components().insertProjectBranch(projectDto, b -> b.setKey(branchName));
-    Branch branch = mock(Branch.class);
-    when(branch.getName()).thenReturn(branchName);
-    when(branch.isMain()).thenReturn(false);
-    when(branch.generateKey(any(), any())).thenReturn(componentDto.getKey());
-    analysisMetadataHolder.setRootComponentRef(ROOT_REF)
-      .setAnalysisDate(ANALYSIS_DATE)
-      .setProject(Project.from(projectDto))
-      .setBranch(branch);
-    BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder);
-    reportReader.putComponent(component(ROOT_REF, PROJECT, componentDto.getKey()));
-
-    underTest.execute(new TestComputationStepContext());
-
-    verifyComponentByRef(ROOT_REF, componentDto.getKey(), analysisMetadataHolder.getProject().getName(), componentDto.uuid());
-  }
-
-  @Test
-  public void generate_keys_when_using_main_branch() {
-    setAnalysisMetadataHolder();
-    BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder);
-    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF));
-    reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1));
-
-    underTest.execute(new TestComputationStepContext());
-
-    verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, analysisMetadataHolder.getProject().getName(), null);
-    verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, REPORT_DIR_PATH_1);
-    verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, REPORT_FILE_NAME_1, null);
-  }
-
-  @Test
-  public void compute_keys_and_uuids_on_project_having_module_and_directory() {
-    setAnalysisMetadataHolder();
-    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF, FILE_2_REF));
-    reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1));
-    reportReader.putComponent(componentWithPath(FILE_2_REF, FILE, REPORT_FILE_PATH_2));
-
-    underTest.execute(new TestComputationStepContext());
-
-    verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, analysisMetadataHolder.getProject().getName());
-    verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, "dir1");
-    verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, REPORT_FILE_NAME_1);
-    verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_2, "dir2");
-    verifyComponentByRef(FILE_2_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_2, "File2.java");
-  }
-
-  @Test
-  public void compute_keys_and_uuids_on_multi_modules() {
-    setAnalysisMetadataHolder();
-    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF));
-    reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1));
-
-    underTest.execute(new TestComputationStepContext());
-
-    verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, analysisMetadataHolder.getProject().getName());
-    verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, REPORT_DIR_PATH_1);
-    verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, REPORT_FILE_NAME_1);
-  }
-
-  @Test
-  public void set_no_base_project_snapshot_when_no_snapshot() {
-    setAnalysisMetadataHolder();
-    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY));
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(analysisMetadataHolder.isFirstAnalysis()).isTrue();
-  }
-
-  @Test
-  public void set_no_base_project_snapshot_when_no_last_snapshot() {
-    setAnalysisMetadataHolder();
-    ComponentDto project = insertComponent(newPrivateProjectDto("ABCD").setKey(REPORT_PROJECT_KEY));
-    insertSnapshot(newAnalysis(project).setLast(false));
-
-    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY));
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(analysisMetadataHolder.isFirstAnalysis()).isTrue();
-  }
-
-  @Test
-  public void set_base_project_snapshot_when_last_snapshot_exist() {
-    setAnalysisMetadataHolder();
-    ComponentDto project = insertComponent(newPrivateProjectDto("ABCD").setKey(REPORT_PROJECT_KEY));
-    insertSnapshot(newAnalysis(project).setLast(true));
-
-    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY));
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(analysisMetadataHolder.isFirstAnalysis()).isFalse();
-  }
-
-  @Test
-  public void set_projectVersion_to_not_provided_when_not_set_on_first_analysis() {
-    setAnalysisMetadataHolder();
-    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY));
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(treeRootHolder.getReportTreeRoot().getProjectAttributes().getProjectVersion()).isEqualTo("not provided");
-  }
-
-  @Test
-  @UseDataProvider("oneParameterNullNonNullCombinations")
-  public void set_projectVersion_to_previous_analysis_when_not_set(@Nullable String previousAnalysisProjectVersion) {
-    setAnalysisMetadataHolder();
-    ComponentDto project = insertComponent(newPrivateProjectDto("ABCD").setKey(REPORT_PROJECT_KEY));
-    insertSnapshot(newAnalysis(project).setProjectVersion(previousAnalysisProjectVersion).setLast(true));
-    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY));
-
-    underTest.execute(new TestComputationStepContext());
-
-    String projectVersion = treeRootHolder.getReportTreeRoot().getProjectAttributes().getProjectVersion();
-    if (previousAnalysisProjectVersion == null) {
-      assertThat(projectVersion).isEqualTo("not provided");
-    } else {
-      assertThat(projectVersion).isEqualTo(previousAnalysisProjectVersion);
-    }
-  }
-
-  @Test
-  public void set_projectVersion_when_it_is_set_on_first_analysis() {
-    String scannerProjectVersion = randomAlphabetic(12);
-    setAnalysisMetadataHolder();
-    reportReader.setMetadata(createReportMetadata(scannerProjectVersion, NO_SCANNER_BUILD_STRING));
-    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY));
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(treeRootHolder.getReportTreeRoot().getProjectAttributes().getProjectVersion())
-      .isEqualTo(scannerProjectVersion);
-  }
-
-  @Test
-  @UseDataProvider("oneParameterNullNonNullCombinations")
-  public void set_projectVersion_when_it_is_set_on_later_analysis(@Nullable String previousAnalysisProjectVersion) {
-    String scannerProjectVersion = randomAlphabetic(12);
-    setAnalysisMetadataHolder();
-    reportReader.setMetadata(createReportMetadata(scannerProjectVersion, NO_SCANNER_BUILD_STRING));
-    ComponentDto project = insertComponent(newPrivateProjectDto("ABCD").setKey(REPORT_PROJECT_KEY));
-    insertSnapshot(newAnalysis(project).setProjectVersion(previousAnalysisProjectVersion).setLast(true));
-    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY));
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(treeRootHolder.getReportTreeRoot().getProjectAttributes().getProjectVersion())
-      .isEqualTo(scannerProjectVersion);
-  }
-
-  @Test
-  @UseDataProvider("oneParameterNullNonNullCombinations")
-  public void set_buildString(@Nullable String buildString) {
-    String projectVersion = randomAlphabetic(7);
-    setAnalysisMetadataHolder();
-    reportReader.setMetadata(createReportMetadata(projectVersion, buildString));
-    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY));
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(treeRootHolder.getReportTreeRoot().getProjectAttributes().getBuildString()).isEqualTo(Optional.ofNullable(buildString));
-  }
-
-  @DataProvider
-  public static Object[][] oneParameterNullNonNullCombinations() {
-    return new Object[][] {
-      {null},
-      {randomAlphabetic(7)}
-    };
-  }
-
-  private void verifyComponent(Component component, Component.Type type, @Nullable Integer componentRef, int size) {
-    assertThat(component.getType()).isEqualTo(type);
-    assertThat(component.getReportAttributes().getRef()).isEqualTo(componentRef);
-    assertThat(component.getChildren()).hasSize(size);
-  }
-
-  private void verifyComponentByRef(int ref, String key, String shortName) {
-    verifyComponentByRef(ref, key, shortName, null);
-  }
-
-  private void verifyComponentByKey(String key, String shortName) {
-    verifyComponentByKey(key, shortName, null);
-  }
-
-  private void verifyComponentByKey(String key, String shortName, @Nullable String uuid) {
-    Map<String, Component> componentsByKey = indexAllComponentsInTreeByKey(treeRootHolder.getRoot());
-    Component component = componentsByKey.get(key);
-    assertThat(component.getKey()).isEqualTo(key);
-    assertThat(component.getReportAttributes().getRef()).isNull();
-    assertThat(component.getShortName()).isEqualTo(shortName);
-    if (uuid != null) {
-      assertThat(component.getUuid()).isEqualTo(uuid);
-    } else {
-      assertThat(component.getUuid()).isNotNull();
-    }
-  }
-
-  private void verifyComponentByRef(int ref, String key, String shortName, @Nullable String uuid) {
-    Map<Integer, Component> componentsByRef = indexAllComponentsInTreeByRef(treeRootHolder.getRoot());
-    Component component = componentsByRef.get(ref);
-    assertThat(component.getKey()).isEqualTo(key);
-    assertThat(component.getShortName()).isEqualTo(shortName);
-    if (uuid != null) {
-      assertThat(component.getUuid()).isEqualTo(uuid);
-    } else {
-      assertThat(component.getUuid()).isNotNull();
-    }
-  }
-
-  private static ScannerReport.Component component(int componentRef, ComponentType componentType, String key, int... children) {
-    return component(componentRef, componentType, key, FileStatus.CHANGED, null, children);
-  }
-
-  private static ScannerReport.Component unchangedComponentWithPath(int componentRef, ComponentType componentType, String path, int... children) {
-    return component(componentRef, componentType, REPORT_PROJECT_KEY + ":" + path, FileStatus.SAME, path, children);
-  }
-
-  private static ScannerReport.Component componentWithPath(int componentRef, ComponentType componentType, String path, int... children) {
-    return component(componentRef, componentType, REPORT_PROJECT_KEY + ":" + path, FileStatus.CHANGED, path, children);
-  }
-
-  private static ScannerReport.Component component(int componentRef, ComponentType componentType, String key, FileStatus status, @Nullable String path, int... children) {
-    ScannerReport.Component.Builder builder = ScannerReport.Component.newBuilder()
-      .setType(componentType)
-      .setRef(componentRef)
-      .setName(key)
-      .setStatus(status)
-      .setLines(1)
-      .setKey(key);
-    if (path != null) {
-      builder.setProjectRelativePath(path);
-    }
-    for (int child : children) {
-      builder.addChildRef(child);
-    }
-    return builder.build();
-  }
-
-  private static Map<Integer, Component> indexAllComponentsInTreeByRef(Component root) {
-    Map<Integer, Component> componentsByRef = new HashMap<>();
-    feedComponentByRef(root, componentsByRef);
-    return componentsByRef;
-  }
-
-  private static Map<String, Component> indexAllComponentsInTreeByKey(Component root) {
-    Map<String, Component> componentsByKey = new HashMap<>();
-    feedComponentByKey(root, componentsByKey);
-    return componentsByKey;
-  }
-
-  private static void feedComponentByKey(Component component, Map<String, Component> map) {
-    map.put(component.getKey(), component);
-    for (Component child : component.getChildren()) {
-      feedComponentByKey(child, map);
-    }
-  }
-
-  private static void feedComponentByRef(Component component, Map<Integer, Component> map) {
-    if (component.getReportAttributes().getRef() != null) {
-      map.put(component.getReportAttributes().getRef(), component);
-    }
-    for (Component child : component.getChildren()) {
-      feedComponentByRef(child, map);
-    }
-  }
-
-  private ComponentDto insertComponent(ComponentDto component) {
-    return dbTester.components().insertComponent(component);
-  }
-
-  private SnapshotDto insertSnapshot(SnapshotDto snapshot) {
-    dbClient.snapshotDao().insert(dbTester.getSession(), snapshot);
-    dbTester.getSession().commit();
-    return snapshot;
-  }
-
-  private void setAnalysisMetadataHolder() {
-    setAnalysisMetadataHolder(false);
-  }
-
-  private void setAnalysisMetadataHolder(boolean isPr) {
-    Branch branch = isPr ? new PrBranch(DEFAULT_MAIN_BRANCH_NAME) : new DefaultBranchImpl(DEFAULT_MAIN_BRANCH_NAME);
-    analysisMetadataHolder.setRootComponentRef(ROOT_REF)
-      .setAnalysisDate(ANALYSIS_DATE)
-      .setBranch(branch)
-      .setProject(Project.from(newPrivateProjectDto().setKey(REPORT_PROJECT_KEY).setName(REPORT_PROJECT_KEY)));
-  }
-
-  public static ScannerReport.Metadata createReportMetadata(@Nullable String projectVersion, @Nullable String buildString) {
-    ScannerReport.Metadata.Builder builder = ScannerReport.Metadata.newBuilder()
-      .setProjectKey(REPORT_PROJECT_KEY)
-      .setRootComponentRef(ROOT_REF);
-    ofNullable(projectVersion).ifPresent(builder::setProjectVersion);
-    ofNullable(buildString).ifPresent(builder::setBuildString);
-    return builder.build();
-  }
-
-  private static class PrBranch extends DefaultBranchImpl {
-    public PrBranch(String branch) {
-      super(branch);
-    }
-
-    @Override
-    public BranchType getType() {
-      return BranchType.PULL_REQUEST;
-    }
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadCrossProjectDuplicationsRepositoryStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadCrossProjectDuplicationsRepositoryStepTest.java
deleted file mode 100644 (file)
index 339e122..0000000
+++ /dev/null
@@ -1,346 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.ce.task.projectanalysis.step;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.projectanalysis.analysis.Analysis;
-import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
-import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
-import org.sonar.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.component.FileAttributes;
-import org.sonar.ce.task.projectanalysis.component.ReportComponent;
-import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
-import org.sonar.ce.task.projectanalysis.duplication.CrossProjectDuplicationStatusHolder;
-import org.sonar.ce.task.projectanalysis.duplication.IntegrateCrossProjectDuplications;
-import org.sonar.ce.task.step.ComputationStep;
-import org.sonar.ce.task.step.TestComputationStepContext;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.component.SnapshotDto;
-import org.sonar.db.component.SnapshotTesting;
-import org.sonar.db.duplication.DuplicationUnitDto;
-import org.sonar.duplications.block.Block;
-import org.sonar.duplications.block.ByteArray;
-import org.sonar.scanner.protocol.output.ScannerReport;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoInteractions;
-import static org.mockito.Mockito.when;
-import static org.sonar.ce.task.projectanalysis.component.Component.Type.FILE;
-import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT;
-
-public class LoadCrossProjectDuplicationsRepositoryStepTest {
-
-
-  private static final String XOO_LANGUAGE = "xoo";
-  private static final int PROJECT_REF = 1;
-  private static final int FILE_REF = 2;
-  private static final String CURRENT_FILE_KEY = "FILE_KEY";
-
-  private static final Component CURRENT_FILE = ReportComponent.builder(FILE, FILE_REF)
-    .setKey(CURRENT_FILE_KEY)
-    .setFileAttributes(new FileAttributes(false, XOO_LANGUAGE, 1))
-    .build();
-
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule().setRoot(
-    ReportComponent.builder(PROJECT, PROJECT_REF)
-      .addChildren(CURRENT_FILE).build());
-
-  @Rule
-  public BatchReportReaderRule batchReportReader = new BatchReportReaderRule();
-
-  @Rule
-  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
-
-  private CrossProjectDuplicationStatusHolder crossProjectDuplicationStatusHolder = mock(CrossProjectDuplicationStatusHolder.class);
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private DbClient dbClient = dbTester.getDbClient();
-  private DbSession dbSession = dbTester.getSession();
-  private IntegrateCrossProjectDuplications integrateCrossProjectDuplications = mock(IntegrateCrossProjectDuplications.class);
-  private Analysis baseProjectAnalysis;
-
-  private ComputationStep underTest = new LoadCrossProjectDuplicationsRepositoryStep(treeRootHolder, batchReportReader, analysisMetadataHolder, crossProjectDuplicationStatusHolder,
-    integrateCrossProjectDuplications, dbClient);
-
-  @Before
-  public void setUp() {
-    ComponentDto project = ComponentTesting.newPrivateProjectDto();
-    dbTester.components().insertComponent(project);
-    SnapshotDto projectSnapshot = SnapshotTesting.newAnalysis(project);
-    dbClient.snapshotDao().insert(dbSession, projectSnapshot);
-    dbSession.commit();
-
-    baseProjectAnalysis = new Analysis.Builder()
-      .setUuid(projectSnapshot.getUuid())
-      .setCreatedAt(projectSnapshot.getCreatedAt())
-      .build();
-  }
-
-  @Test
-  public void call_compute_cpd_on_one_duplication() {
-    when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true);
-    analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis);
-
-    ComponentDto otherProject = createProject("OTHER_PROJECT_KEY");
-    SnapshotDto otherProjectSnapshot = createProjectSnapshot(otherProject);
-
-    ComponentDto otherFile = createFile("OTHER_FILE_KEY", otherProject);
-
-    String hash = "a8998353e96320ec";
-    DuplicationUnitDto duplicate = new DuplicationUnitDto()
-      .setHash(hash)
-      .setStartLine(40)
-      .setEndLine(55)
-      .setIndexInFile(0)
-      .setAnalysisUuid(otherProjectSnapshot.getUuid())
-      .setComponentUuid(otherFile.uuid());
-    dbClient.duplicationDao().insert(dbSession, duplicate);
-    dbSession.commit();
-
-    ScannerReport.CpdTextBlock originBlock = ScannerReport.CpdTextBlock.newBuilder()
-      .setHash(hash)
-      .setStartLine(30)
-      .setEndLine(45)
-      .setStartTokenIndex(0)
-      .setEndTokenIndex(10)
-      .build();
-    batchReportReader.putDuplicationBlocks(FILE_REF, singletonList(originBlock));
-
-    underTest.execute(new TestComputationStepContext());
-
-    verify(integrateCrossProjectDuplications).computeCpd(CURRENT_FILE,
-      singletonList(
-        new Block.Builder()
-          .setResourceId(CURRENT_FILE_KEY)
-          .setBlockHash(new ByteArray(hash))
-          .setIndexInFile(0)
-          .setLines(originBlock.getStartLine(), originBlock.getEndLine())
-          .setUnit(originBlock.getStartTokenIndex(), originBlock.getEndTokenIndex())
-          .build()),
-      singletonList(
-        new Block.Builder()
-          .setResourceId(otherFile.getKey())
-          .setBlockHash(new ByteArray(hash))
-          .setIndexInFile(duplicate.getIndexInFile())
-          .setLines(duplicate.getStartLine(), duplicate.getEndLine())
-          .build()));
-  }
-
-  @Test
-  public void call_compute_cpd_on_many_duplication() {
-    when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true);
-    analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis);
-
-    ComponentDto otherProject = createProject("OTHER_PROJECT_KEY");
-    SnapshotDto otherProjectSnapshot = createProjectSnapshot(otherProject);
-
-    ComponentDto otherFile = createFile("OTHER_FILE_KEY", otherProject);
-
-    ScannerReport.CpdTextBlock originBlock1 = ScannerReport.CpdTextBlock.newBuilder()
-      .setHash("a8998353e96320ec")
-      .setStartLine(30)
-      .setEndLine(45)
-      .setStartTokenIndex(0)
-      .setEndTokenIndex(10)
-      .build();
-    ScannerReport.CpdTextBlock originBlock2 = ScannerReport.CpdTextBlock.newBuilder()
-      .setHash("b1234353e96320ff")
-      .setStartLine(10)
-      .setEndLine(25)
-      .setStartTokenIndex(5)
-      .setEndTokenIndex(15)
-      .build();
-    batchReportReader.putDuplicationBlocks(FILE_REF, asList(originBlock1, originBlock2));
-
-    DuplicationUnitDto duplicate1 = new DuplicationUnitDto()
-      .setHash(originBlock1.getHash())
-      .setStartLine(40)
-      .setEndLine(55)
-      .setIndexInFile(0)
-      .setAnalysisUuid(otherProjectSnapshot.getUuid())
-      .setComponentUuid(otherFile.uuid());
-
-    DuplicationUnitDto duplicate2 = new DuplicationUnitDto()
-      .setHash(originBlock2.getHash())
-      .setStartLine(20)
-      .setEndLine(35)
-      .setIndexInFile(1)
-      .setAnalysisUuid(otherProjectSnapshot.getUuid())
-      .setComponentUuid(otherFile.uuid());
-    dbClient.duplicationDao().insert(dbSession, duplicate1);
-    dbClient.duplicationDao().insert(dbSession, duplicate2);
-    dbSession.commit();
-
-    underTest.execute(new TestComputationStepContext());
-
-    ArgumentCaptor<List<Block>> originBlocks = ArgumentCaptor.forClass(List.class);
-    ArgumentCaptor<List<Block>> duplicationBlocks = ArgumentCaptor.forClass(List.class);
-
-    verify(integrateCrossProjectDuplications).computeCpd(eq(CURRENT_FILE), originBlocks.capture(), duplicationBlocks.capture());
-
-    Map<Integer, Block> originBlocksByIndex = blocksByIndexInFile(originBlocks.getValue());
-    assertThat(originBlocksByIndex).containsExactly(
-      Map.entry(0, new Block.Builder()
-        .setResourceId(CURRENT_FILE_KEY)
-        .setBlockHash(new ByteArray(originBlock1.getHash()))
-        .setIndexInFile(0)
-        .setLines(originBlock1.getStartLine(), originBlock1.getEndLine())
-        .setUnit(originBlock1.getStartTokenIndex(), originBlock1.getEndTokenIndex())
-        .build()),
-      Map.entry(1, new Block.Builder()
-        .setResourceId(CURRENT_FILE_KEY)
-        .setBlockHash(new ByteArray(originBlock2.getHash()))
-        .setIndexInFile(1)
-        .setLines(originBlock2.getStartLine(), originBlock2.getEndLine())
-        .setUnit(originBlock2.getStartTokenIndex(), originBlock2.getEndTokenIndex())
-        .build()));
-
-    Map<Integer, Block> duplicationBlocksByIndex = blocksByIndexInFile(duplicationBlocks.getValue());
-    assertThat(duplicationBlocksByIndex).containsExactly(
-      Map.entry(0, new Block.Builder()
-        .setResourceId(otherFile.getKey())
-        .setBlockHash(new ByteArray(originBlock1.getHash()))
-        .setIndexInFile(duplicate1.getIndexInFile())
-        .setLines(duplicate1.getStartLine(), duplicate1.getEndLine())
-        .build()),
-      Map.entry(1, new Block.Builder()
-        .setResourceId(otherFile.getKey())
-        .setBlockHash(new ByteArray(originBlock2.getHash()))
-        .setIndexInFile(duplicate2.getIndexInFile())
-        .setLines(duplicate2.getStartLine(), duplicate2.getEndLine())
-        .build()));
-  }
-
-  @Test
-  public void nothing_to_do_when_cross_project_duplication_is_disabled() {
-    when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(false);
-    analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis);
-
-    ComponentDto otherProject = createProject("OTHER_PROJECT_KEY");
-    SnapshotDto otherProjectSnapshot = createProjectSnapshot(otherProject);
-
-    ComponentDto otherFIle = createFile("OTHER_FILE_KEY", otherProject);
-
-    String hash = "a8998353e96320ec";
-    DuplicationUnitDto duplicate = new DuplicationUnitDto()
-      .setHash(hash)
-      .setStartLine(40)
-      .setEndLine(55)
-      .setIndexInFile(0)
-      .setAnalysisUuid(otherProjectSnapshot.getUuid())
-      .setComponentUuid(otherFIle.uuid());
-    dbClient.duplicationDao().insert(dbSession, duplicate);
-    dbSession.commit();
-
-    ScannerReport.CpdTextBlock originBlock = ScannerReport.CpdTextBlock.newBuilder()
-      .setHash(hash)
-      .setStartLine(30)
-      .setEndLine(45)
-      .setStartTokenIndex(0)
-      .setEndTokenIndex(10)
-      .build();
-    batchReportReader.putDuplicationBlocks(FILE_REF, singletonList(originBlock));
-
-    underTest.execute(new TestComputationStepContext());
-
-    verifyNoInteractions(integrateCrossProjectDuplications);
-  }
-
-  @Test
-  public void nothing_to_do_when_no_cpd_text_blocks_found() {
-    when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true);
-    analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis);
-
-    batchReportReader.putDuplicationBlocks(FILE_REF, Collections.emptyList());
-
-    underTest.execute(new TestComputationStepContext());
-
-    verifyNoInteractions(integrateCrossProjectDuplications);
-  }
-
-  @Test
-  public void nothing_to_do_when_cpd_text_blocks_exists_but_no_duplicated_found() {
-    when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true);
-    analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis);
-
-    ScannerReport.CpdTextBlock originBlock = ScannerReport.CpdTextBlock.newBuilder()
-      .setHash("a8998353e96320ec")
-      .setStartLine(30)
-      .setEndLine(45)
-      .setStartTokenIndex(0)
-      .setEndTokenIndex(10)
-      .build();
-    batchReportReader.putDuplicationBlocks(FILE_REF, singletonList(originBlock));
-
-    underTest.execute(new TestComputationStepContext());
-
-    verifyNoInteractions(integrateCrossProjectDuplications);
-  }
-
-  private ComponentDto createProject(String projectKey) {
-    ComponentDto project = ComponentTesting.newPrivateProjectDto().setKey(projectKey);
-    return dbTester.components().insertComponent(project);
-  }
-
-  private SnapshotDto createProjectSnapshot(ComponentDto project) {
-    SnapshotDto projectSnapshot = SnapshotTesting.newAnalysis(project);
-    dbClient.snapshotDao().insert(dbSession, projectSnapshot);
-    dbSession.commit();
-    return projectSnapshot;
-  }
-
-  private ComponentDto createFile(String fileKey, ComponentDto project) {
-    ComponentDto file = ComponentTesting.newFileDto(project, null)
-      .setKey(fileKey)
-      .setLanguage(XOO_LANGUAGE);
-    dbClient.componentDao().insert(dbSession, file);
-    dbSession.commit();
-    return file;
-  }
-
-  private static Map<Integer, Block> blocksByIndexInFile(List<Block> blocks) {
-    Map<Integer, Block> blocksByIndexInFile = new HashMap<>();
-    for (Block block : blocks) {
-      blocksByIndexInFile.put(block.getIndexInFile(), block);
-    }
-    return blocksByIndexInFile;
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisPropertiesStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisPropertiesStepTest.java
deleted file mode 100644 (file)
index 6344846..0000000
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.ce.task.projectanalysis.step;
-
-import com.google.common.collect.ImmutableList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Optional;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
-import org.sonar.ce.task.projectanalysis.batch.BatchReportReader;
-import org.sonar.ce.task.step.TestComputationStepContext;
-import org.sonar.core.util.CloseableIterator;
-import org.sonar.core.util.UuidFactoryFast;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.AnalysisPropertyDto;
-import org.sonar.scanner.protocol.output.ScannerReport;
-
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.tuple;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class PersistAnalysisPropertiesStepTest {
-  private static final String SNAPSHOT_UUID = randomAlphanumeric(40);
-  private static final String SMALL_VALUE1 = randomAlphanumeric(50);
-  private static final String SMALL_VALUE2 = randomAlphanumeric(50);
-  private static final String SMALL_VALUE3 = randomAlphanumeric(50);
-  private static final String BIG_VALUE = randomAlphanumeric(5000);
-  private static final String VALUE_PREFIX_FOR_PR_PROPERTIES = "pr_";
-  private static final List<ScannerReport.ContextProperty> PROPERTIES = Arrays.asList(
-    newContextProperty("key1", "value1"),
-    newContextProperty("key2", "value1"),
-    newContextProperty("sonar.analysis", SMALL_VALUE1),
-    newContextProperty("sonar.analysis.branch", SMALL_VALUE2),
-    newContextProperty("sonar.analysis.empty_string", ""),
-    newContextProperty("sonar.analysis.big_value", BIG_VALUE),
-    newContextProperty("sonar.analysis.", SMALL_VALUE3),
-    newContextProperty("sonar.pullrequest", VALUE_PREFIX_FOR_PR_PROPERTIES + SMALL_VALUE1),
-    newContextProperty("sonar.pullrequest.branch", VALUE_PREFIX_FOR_PR_PROPERTIES + SMALL_VALUE2),
-    newContextProperty("sonar.pullrequest.empty_string", ""),
-    newContextProperty("sonar.pullrequest.big_value", VALUE_PREFIX_FOR_PR_PROPERTIES + BIG_VALUE),
-    newContextProperty("sonar.pullrequest.", VALUE_PREFIX_FOR_PR_PROPERTIES + SMALL_VALUE3));
-  private static final String SCM_REV_ID = "sha1";
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private BatchReportReader batchReportReader = mock(BatchReportReader.class);
-  private AnalysisMetadataHolder analysisMetadataHolder = mock(AnalysisMetadataHolder.class);
-  private PersistAnalysisPropertiesStep underTest = new PersistAnalysisPropertiesStep(dbTester.getDbClient(), analysisMetadataHolder, batchReportReader,
-    UuidFactoryFast.getInstance());
-
-  @Test
-  public void persist_should_stores_sonarDotAnalysisDot_and_sonarDotPullRequestDot_properties() {
-    when(batchReportReader.readContextProperties()).thenReturn(CloseableIterator.from(PROPERTIES.iterator()));
-    when(analysisMetadataHolder.getUuid()).thenReturn(SNAPSHOT_UUID);
-    when(analysisMetadataHolder.getScmRevision()).thenReturn(Optional.of(SCM_REV_ID));
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbTester.countRowsOfTable("analysis_properties")).isEqualTo(8);
-    List<AnalysisPropertyDto> propertyDtos = dbTester.getDbClient()
-      .analysisPropertiesDao().selectByAnalysisUuid(dbTester.getSession(), SNAPSHOT_UUID);
-
-    assertThat(propertyDtos)
-      .extracting(AnalysisPropertyDto::getAnalysisUuid, AnalysisPropertyDto::getKey, AnalysisPropertyDto::getValue)
-      .containsExactlyInAnyOrder(
-        tuple(SNAPSHOT_UUID, "sonar.analysis.branch", SMALL_VALUE2),
-        tuple(SNAPSHOT_UUID, "sonar.analysis.empty_string", ""),
-        tuple(SNAPSHOT_UUID, "sonar.analysis.big_value", BIG_VALUE),
-        tuple(SNAPSHOT_UUID, "sonar.analysis.", SMALL_VALUE3),
-        tuple(SNAPSHOT_UUID, "sonar.pullrequest.branch", VALUE_PREFIX_FOR_PR_PROPERTIES + SMALL_VALUE2),
-        tuple(SNAPSHOT_UUID, "sonar.pullrequest.empty_string", ""),
-        tuple(SNAPSHOT_UUID, "sonar.pullrequest.big_value", VALUE_PREFIX_FOR_PR_PROPERTIES + BIG_VALUE),
-        tuple(SNAPSHOT_UUID, "sonar.pullrequest.", VALUE_PREFIX_FOR_PR_PROPERTIES + SMALL_VALUE3));
-  }
-
-  @Test
-  public void persist_filtering_of_properties_is_case_sensitive() {
-    when(analysisMetadataHolder.getScmRevision()).thenReturn(Optional.of(SCM_REV_ID));
-    when(batchReportReader.readContextProperties()).thenReturn(CloseableIterator.from(ImmutableList.of(
-      newContextProperty("sonar.ANALYSIS.foo", "foo"),
-      newContextProperty("sonar.anaLysis.bar", "bar"),
-      newContextProperty("sonar.anaLYSIS.doo", "doh"),
-      newContextProperty("sonar.PULLREQUEST.foo", "foo"),
-      newContextProperty("sonar.pullRequest.bar", "bar"),
-      newContextProperty("sonar.pullREQUEST.doo", "doh")).iterator()));
-    when(analysisMetadataHolder.getUuid()).thenReturn(SNAPSHOT_UUID);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbTester.countRowsOfTable("analysis_properties")).isZero();
-  }
-
-  @Test
-  public void persist_should_store_nothing_if_there_are_no_context_properties() {
-    when(analysisMetadataHolder.getScmRevision()).thenReturn(Optional.of(SCM_REV_ID));
-    when(batchReportReader.readContextProperties()).thenReturn(CloseableIterator.emptyCloseableIterator());
-    when(analysisMetadataHolder.getUuid()).thenReturn(SNAPSHOT_UUID);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbTester.countRowsOfTable("analysis_properties")).isZero();
-  }
-
-  @Test
-  public void verify_description_value() {
-    assertThat(underTest.getDescription()).isEqualTo("Persist analysis properties");
-  }
-
-  private static ScannerReport.ContextProperty newContextProperty(String key, String value) {
-    return ScannerReport.ContextProperty.newBuilder()
-      .setKey(key)
-      .setValue(value)
-      .build();
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistCrossProjectDuplicationIndexStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistCrossProjectDuplicationIndexStepTest.java
deleted file mode 100644 (file)
index ac428f4..0000000
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.ce.task.projectanalysis.step;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.projectanalysis.analysis.Analysis;
-import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
-import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
-import org.sonar.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.component.ReportComponent;
-import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
-import org.sonar.ce.task.projectanalysis.duplication.CrossProjectDuplicationStatusHolder;
-import org.sonar.ce.task.step.ComputationStep;
-import org.sonar.ce.task.step.TestComputationStepContext;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.scanner.protocol.output.ScannerReport;
-
-import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class PersistCrossProjectDuplicationIndexStepTest {
-
-  private static final int FILE_1_REF = 2;
-  private static final int FILE_2_REF = 3;
-  private static final String FILE_2_UUID = "file2";
-
-  private static final Component FILE_1 = ReportComponent.builder(Component.Type.FILE, FILE_1_REF).build();
-  private static final Component FILE_2 = ReportComponent.builder(Component.Type.FILE, FILE_2_REF)
-    .setStatus(Component.Status.SAME).setUuid(FILE_2_UUID).build();
-
-  private static final Component PROJECT = ReportComponent.builder(Component.Type.PROJECT, 1)
-    .addChildren(FILE_1)
-    .addChildren(FILE_2)
-    .build();
-
-  private static final ScannerReport.CpdTextBlock CPD_TEXT_BLOCK = ScannerReport.CpdTextBlock.newBuilder()
-    .setHash("a8998353e96320ec")
-    .setStartLine(30)
-    .setEndLine(45)
-    .build();
-  private static final String ANALYSIS_UUID = "analysis uuid";
-  private static final String BASE_ANALYSIS_UUID = "base analysis uuid";
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-  @Rule
-  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule().setRoot(PROJECT);
-  @Rule
-  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
-
-  private CrossProjectDuplicationStatusHolder crossProjectDuplicationStatusHolder = mock(CrossProjectDuplicationStatusHolder.class);
-  private Analysis baseAnalysis = mock(Analysis.class);
-  private DbClient dbClient = dbTester.getDbClient();
-
-  private ComputationStep underTest;
-
-  @Before
-  public void setUp() {
-    when(baseAnalysis.getUuid()).thenReturn(BASE_ANALYSIS_UUID);
-    analysisMetadataHolder.setUuid(ANALYSIS_UUID);
-    analysisMetadataHolder.setBaseAnalysis(baseAnalysis);
-    underTest = new PersistCrossProjectDuplicationIndexStep(crossProjectDuplicationStatusHolder, dbClient, treeRootHolder, analysisMetadataHolder, reportReader);
-  }
-
-  @Test
-  public void persist_cpd_text_block() {
-    when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true);
-    reportReader.putDuplicationBlocks(FILE_1_REF, singletonList(CPD_TEXT_BLOCK));
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    Map<String, Object> dto = dbTester.selectFirst("select HASH, START_LINE, END_LINE, INDEX_IN_FILE, COMPONENT_UUID, ANALYSIS_UUID from duplications_index");
-    assertThat(dto)
-      .containsEntry("HASH", CPD_TEXT_BLOCK.getHash())
-      .containsEntry("START_LINE", 30L)
-      .containsEntry("END_LINE", 45L)
-      .containsEntry("INDEX_IN_FILE", 0L)
-      .containsEntry("COMPONENT_UUID", FILE_1.getUuid())
-      .containsEntry("ANALYSIS_UUID", ANALYSIS_UUID);
-    context.getStatistics().assertValue("inserts", 1);
-  }
-
-  @Test
-  public void persist_many_cpd_text_blocks() {
-    when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true);
-    reportReader.putDuplicationBlocks(FILE_1_REF, Arrays.asList(
-      CPD_TEXT_BLOCK,
-      ScannerReport.CpdTextBlock.newBuilder()
-        .setHash("b1234353e96320ff")
-        .setStartLine(20)
-        .setEndLine(15)
-        .build()));
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    List<Map<String, Object>> dtos = dbTester.select("select HASH, START_LINE, END_LINE, INDEX_IN_FILE, COMPONENT_UUID, ANALYSIS_UUID from duplications_index");
-    assertThat(dtos).extracting("HASH").containsOnly(CPD_TEXT_BLOCK.getHash(), "b1234353e96320ff");
-    assertThat(dtos).extracting("START_LINE").containsOnly(30L, 20L);
-    assertThat(dtos).extracting("END_LINE").containsOnly(45L, 15L);
-    assertThat(dtos).extracting("INDEX_IN_FILE").containsOnly(0L, 1L);
-    assertThat(dtos).extracting("COMPONENT_UUID").containsOnly(FILE_1.getUuid());
-    assertThat(dtos).extracting("ANALYSIS_UUID").containsOnly(ANALYSIS_UUID);
-    context.getStatistics().assertValue("inserts", 2);
-  }
-
-  @Test
-  public void nothing_to_persist_when_no_cpd_text_blocks_in_report() {
-    when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true);
-    reportReader.putDuplicationBlocks(FILE_1_REF, Collections.emptyList());
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(dbTester.countRowsOfTable("duplications_index")).isZero();
-    context.getStatistics().assertValue("inserts", 0);
-  }
-
-  @Test
-  public void nothing_to_do_when_cross_project_duplication_is_disabled() {
-    when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(false);
-    reportReader.putDuplicationBlocks(FILE_1_REF, singletonList(CPD_TEXT_BLOCK));
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(dbTester.countRowsOfTable("duplications_index")).isZero();
-    context.getStatistics().assertValue("inserts", null);
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistEventsStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistEventsStepTest.java
deleted file mode 100644 (file)
index 4629dcd..0000000
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.ce.task.projectanalysis.step;
-
-import com.google.common.collect.ImmutableList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
-import org.sonar.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.component.ReportComponent;
-import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
-import org.sonar.ce.task.projectanalysis.event.Event;
-import org.sonar.ce.task.projectanalysis.event.EventRepository;
-import org.sonar.ce.task.step.ComputationStep;
-import org.sonar.ce.task.step.TestComputationStepContext;
-import org.sonar.core.util.UuidFactory;
-import org.sonar.core.util.UuidFactoryImpl;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.event.EventDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.ce.task.projectanalysis.component.Component.Type.DIRECTORY;
-import static org.sonar.ce.task.projectanalysis.component.Component.Type.FILE;
-import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT;
-import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
-import static org.sonar.db.event.EventDto.CATEGORY_ALERT;
-import static org.sonar.db.event.EventDto.CATEGORY_PROFILE;
-import static org.sonar.db.event.EventDto.CATEGORY_VERSION;
-
-public class PersistEventsStepTest extends BaseStepTest {
-
-  private static final long NOW = 1225630680000L;
-  private static final ReportComponent ROOT = builder(PROJECT, 1)
-    .setUuid("ABCD")
-    .setProjectVersion("version_1")
-    .addChildren(
-      builder(DIRECTORY, 2)
-        .setUuid("BCDE")
-        .addChildren(
-          builder(DIRECTORY, 3)
-            .setUuid("Q")
-            .addChildren(
-              builder(FILE, 4)
-                .setUuid("Z")
-                .build())
-            .build())
-        .build())
-    .build();
-  private static final String ANALYSIS_UUID = "uuid_1";
-
-  System2 system2 = mock(System2.class);
-
-  @Rule
-  public DbTester dbTester = DbTester.create(system2);
-  @Rule
-  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-
-  private Date someDate = new Date(150000000L);
-
-  private EventRepository eventRepository = mock(EventRepository.class);
-  private UuidFactory uuidFactory = UuidFactoryImpl.INSTANCE;
-
-  private PersistEventsStep underTest;
-
-  @Before
-  public void setup() {
-    analysisMetadataHolder.setAnalysisDate(someDate.getTime()).setUuid(ANALYSIS_UUID);
-    underTest = new PersistEventsStep(dbTester.getDbClient(), system2, treeRootHolder, analysisMetadataHolder, eventRepository, uuidFactory);
-    when(eventRepository.getEvents(any(Component.class))).thenReturn(Collections.emptyList());
-  }
-
-  @Override
-  protected ComputationStep step() {
-    return underTest;
-  }
-
-  @Test
-  public void create_version_event() {
-    when(system2.now()).thenReturn(NOW);
-    Component project = builder(PROJECT, 1)
-      .setUuid("ABCD")
-      .setProjectVersion("1.0")
-      .addChildren(
-        builder(DIRECTORY, 2)
-          .setUuid("BCDE")
-          .addChildren(
-            builder(DIRECTORY, 3)
-              .setUuid("Q")
-              .addChildren(
-                builder(FILE, 4)
-                  .setUuid("Z")
-                  .build())
-              .build())
-          .build())
-      .build();
-    treeRootHolder.setRoot(project);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbTester.countRowsOfTable(dbTester.getSession(), "events")).isOne();
-    List<EventDto> eventDtos = dbTester.getDbClient().eventDao().selectByComponentUuid(dbTester.getSession(), ROOT.getUuid());
-    assertThat(eventDtos).hasSize(1);
-    EventDto eventDto = eventDtos.iterator().next();
-    assertThat(eventDto.getComponentUuid()).isEqualTo(ROOT.getUuid());
-    assertThat(eventDto.getName()).isEqualTo("1.0");
-    assertThat(eventDto.getDescription()).isNull();
-    assertThat(eventDto.getCategory()).isEqualTo(CATEGORY_VERSION);
-    assertThat(eventDto.getData()).isNull();
-    assertThat(eventDto.getDate()).isEqualTo(analysisMetadataHolder.getAnalysisDate());
-    assertThat(eventDto.getCreatedAt()).isEqualTo(NOW);
-  }
-
-  @Test
-  public void persist_alert_events_on_root() {
-    when(system2.now()).thenReturn(NOW);
-    treeRootHolder.setRoot(ROOT);
-    Event alert = Event.createAlert("Failed", null, "Open issues > 0");
-    when(eventRepository.getEvents(ROOT)).thenReturn(ImmutableList.of(alert));
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbTester.countRowsOfTable(dbTester.getSession(), "events")).isEqualTo(2);
-    List<EventDto> eventDtos = dbTester.getDbClient().eventDao().selectByComponentUuid(dbTester.getSession(), ROOT.getUuid());
-    assertThat(eventDtos)
-      .extracting(EventDto::getCategory)
-      .containsOnly(CATEGORY_ALERT, CATEGORY_VERSION);
-    EventDto eventDto = eventDtos.stream().filter(t -> CATEGORY_ALERT.equals(t.getCategory())).findAny().get();
-    assertThat(eventDto.getComponentUuid()).isEqualTo(ROOT.getUuid());
-    assertThat(eventDto.getName()).isEqualTo(alert.getName());
-    assertThat(eventDto.getDescription()).isEqualTo(alert.getDescription());
-    assertThat(eventDto.getCategory()).isEqualTo(CATEGORY_ALERT);
-    assertThat(eventDto.getData()).isNull();
-    assertThat(eventDto.getDate()).isEqualTo(analysisMetadataHolder.getAnalysisDate());
-    assertThat(eventDto.getCreatedAt()).isEqualTo(NOW);
-  }
-
-  @Test
-  public void persist_profile_events_on_root() {
-    when(system2.now()).thenReturn(NOW);
-    treeRootHolder.setRoot(ROOT);
-    Event profile = Event.createProfile("foo", null, "bar");
-    when(eventRepository.getEvents(ROOT)).thenReturn(ImmutableList.of(profile));
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbTester.countRowsOfTable(dbTester.getSession(), "events")).isEqualTo(2);
-    List<EventDto> eventDtos = dbTester.getDbClient().eventDao().selectByComponentUuid(dbTester.getSession(), ROOT.getUuid());
-    assertThat(eventDtos)
-      .extracting(EventDto::getCategory)
-      .containsOnly(CATEGORY_PROFILE, CATEGORY_VERSION);
-    EventDto eventDto = eventDtos.stream().filter(t -> CATEGORY_PROFILE.equals(t.getCategory())).findAny().get();
-    assertThat(eventDto.getComponentUuid()).isEqualTo(ROOT.getUuid());
-    assertThat(eventDto.getName()).isEqualTo(profile.getName());
-    assertThat(eventDto.getDescription()).isEqualTo(profile.getDescription());
-    assertThat(eventDto.getCategory()).isEqualTo(EventDto.CATEGORY_PROFILE);
-    assertThat(eventDto.getData()).isNull();
-    assertThat(eventDto.getDate()).isEqualTo(analysisMetadataHolder.getAnalysisDate());
-    assertThat(eventDto.getCreatedAt()).isEqualTo(NOW);
-  }
-
-  @Test
-  public void keep_one_event_by_version() {
-    ComponentDto projectDto = dbTester.components().insertPublicProject();
-    EventDto[] existingEvents = new EventDto[] {
-      dbTester.events().insertEvent(newVersionEventDto(projectDto, 120_000_000L, "1.3-SNAPSHOT")),
-      dbTester.events().insertEvent(newVersionEventDto(projectDto, 130_000_000L, "1.4")),
-      dbTester.events().insertEvent(newVersionEventDto(projectDto, 140_000_000L, "1.5-SNAPSHOT"))
-    };
-
-    Component project = builder(PROJECT, 1)
-      .setUuid(projectDto.uuid())
-      .setProjectVersion("1.5-SNAPSHOT")
-      .addChildren(
-        builder(DIRECTORY, 2)
-          .setUuid("BCDE")
-          .addChildren(
-            builder(DIRECTORY, 3)
-              .setUuid("Q")
-              .addChildren(
-                builder(FILE, 4)
-                  .setUuid("Z")
-                  .build())
-              .build())
-          .build())
-      .build();
-    treeRootHolder.setRoot(project);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbTester.countRowsOfTable(dbTester.getSession(), "events")).isEqualTo(3);
-    List<EventDto> eventDtos = dbTester.getDbClient().eventDao().selectByComponentUuid(dbTester.getSession(), projectDto.uuid());
-    assertThat(eventDtos).hasSize(3);
-    assertThat(eventDtos)
-      .extracting(EventDto::getName)
-      .containsOnly("1.3-SNAPSHOT", "1.4", "1.5-SNAPSHOT");
-    assertThat(eventDtos)
-      .extracting(EventDto::getUuid)
-      .contains(existingEvents[0].getUuid(), existingEvents[1].getUuid())
-      .doesNotContain(existingEvents[2].getUuid());
-  }
-
-  private EventDto newVersionEventDto(ComponentDto project, long date, String name) {
-    return new EventDto().setUuid(uuidFactory.create()).setComponentUuid(project.uuid())
-      .setAnalysisUuid("analysis_uuid")
-      .setCategory(CATEGORY_VERSION)
-      .setName(name).setDate(date).setCreatedAt(date);
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/taskprocessor/IgnoreOrphanBranchStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/taskprocessor/IgnoreOrphanBranchStepTest.java
deleted file mode 100644 (file)
index a63c27c..0000000
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.ce.task.projectanalysis.taskprocessor;
-
-import java.util.Optional;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.CeTask;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.BranchDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.sonar.db.component.BranchType.BRANCH;
-
-public class IgnoreOrphanBranchStepTest {
-
-  private String BRANCH_UUID = "branch_uuid";
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private CeTask.Component component = new CeTask.Component(BRANCH_UUID, "component key", "component name");
-  private CeTask ceTask = new CeTask.Builder()
-    .setType("type")
-    .setUuid("uuid")
-    .setComponent(component)
-    .setMainComponent(component)
-    .build();
-
-  private DbClient dbClient = dbTester.getDbClient();
-  private IgnoreOrphanBranchStep underTest = new IgnoreOrphanBranchStep(ceTask, dbClient);
-
-  @Test
-  public void execute() {
-    BranchDto branchDto = new BranchDto()
-      .setBranchType(BRANCH)
-      .setKey("branchName")
-      .setUuid(BRANCH_UUID)
-      .setProjectUuid("project_uuid")
-      .setNeedIssueSync(true);
-    dbClient.branchDao().insert(dbTester.getSession(), branchDto);
-    dbTester.commit();
-
-    underTest.execute(() -> null);
-
-    Optional<BranchDto> branch = dbClient.branchDao().selectByUuid(dbTester.getSession(), BRANCH_UUID);
-    assertThat(branch.get().isNeedIssueSync()).isFalse();
-    assertThat(branch.get().isExcludeFromPurge()).isFalse();
-  }
-
-  @Test
-  public void execute_on_already_indexed_branch() {
-    BranchDto branchDto = new BranchDto()
-      .setBranchType(BRANCH)
-      .setKey("branchName")
-      .setUuid(BRANCH_UUID)
-      .setProjectUuid("project_uuid")
-      .setNeedIssueSync(false);
-    dbClient.branchDao().insert(dbTester.getSession(), branchDto);
-    dbTester.commit();
-
-    underTest.execute(() -> null);
-
-    Optional<BranchDto> branch = dbClient.branchDao().selectByUuid(dbTester.getSession(), BRANCH_UUID);
-    assertThat(branch.get().isNeedIssueSync()).isFalse();
-    assertThat(branch.get().isExcludeFromPurge()).isFalse();
-  }
-
-  @Test
-  public void fail_if_missing_main_component_in_task() {
-    CeTask ceTask = new CeTask.Builder()
-      .setType("type")
-      .setUuid("uuid")
-      .setComponent(null)
-      .setMainComponent(null)
-      .build();
-    IgnoreOrphanBranchStep underTest = new IgnoreOrphanBranchStep(ceTask, dbClient);
-
-    assertThatThrownBy(() -> underTest.execute(() -> null))
-      .isInstanceOf(UnsupportedOperationException.class)
-      .hasMessage("main component not found in task");
-  }
-
-  @Test
-  public void verify_step_description() {
-    assertThat(underTest.getDescription()).isEqualTo("Ignore orphan component");
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/component/ExportComponentsStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/component/ExportComponentsStepTest.java
deleted file mode 100644 (file)
index 52bb309..0000000
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.ce.task.projectexport.component;
-
-import com.google.common.collect.ImmutableSet;
-import com.sonarsource.governance.projectdump.protobuf.ProjectDump;
-import java.util.Date;
-import java.util.List;
-import org.junit.After;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.api.resources.Scopes;
-import org.sonar.api.utils.System2;
-import org.sonar.api.utils.log.LogTester;
-import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.ce.task.projectexport.steps.DumpElement;
-import org.sonar.ce.task.projectexport.steps.FakeDumpWriter;
-import org.sonar.ce.task.projectexport.steps.ProjectHolder;
-import org.sonar.ce.task.step.TestComputationStepContext;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.assertj.core.api.Assertions.tuple;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.db.component.ComponentDto.UUID_PATH_OF_ROOT;
-import static org.sonar.db.component.ComponentDto.UUID_PATH_SEPARATOR;
-
-public class ExportComponentsStepTest {
-
-  private static final String PROJECT_UUID = "PROJECT_UUID";
-  private static final ComponentDto PROJECT = new ComponentDto()
-    // no id yet
-    .setScope(Scopes.PROJECT)
-    .setQualifier(Qualifiers.PROJECT)
-    .setKey("the_project")
-    .setName("The Project")
-    .setDescription("The project description")
-    .setEnabled(true)
-    .setUuid(PROJECT_UUID)
-    .setUuidPath(UUID_PATH_OF_ROOT)
-    .setCreatedAt(new Date(1596749115856L))
-    .setBranchUuid(PROJECT_UUID);
-
-  private static final String FILE_UUID = "FILE_UUID";
-  private static final String FILE_UUID_PATH = PROJECT_UUID + FILE_UUID + UUID_PATH_SEPARATOR;
-  private static final ComponentDto FILE = new ComponentDto()
-    // no id yet
-    .setScope(Scopes.FILE)
-    .setQualifier(Qualifiers.FILE)
-    .setKey("the_file")
-    .setName("The File")
-    .setUuid(FILE_UUID)
-    .setUuidPath(FILE_UUID_PATH)
-    .setEnabled(true)
-    .setCreatedAt(new Date(1596749148406L))
-    .setBranchUuid(PROJECT_UUID);
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-  @Rule
-  public LogTester logTester = new LogTester();
-
-  private final FakeDumpWriter dumpWriter = new FakeDumpWriter();
-  private final ProjectHolder projectHolder = mock(ProjectHolder.class);
-  private final MutableComponentRepository componentRepository = new ComponentRepositoryImpl();
-  private final ExportComponentsStep underTest = new ExportComponentsStep(dbTester.getDbClient(), projectHolder, componentRepository, dumpWriter);
-
-  @After
-  public void tearDown() {
-    dbTester.getSession().close();
-  }
-
-  @Test
-  public void export_components_including_project() {
-    dbTester.components().insertPublicProject(PROJECT);
-    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), FILE);
-    dbTester.commit();
-    when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(PROJECT));
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("2 components exported");
-    List<ProjectDump.Component> components = dumpWriter.getWrittenMessagesOf(DumpElement.COMPONENTS);
-    assertThat(components).extracting(ProjectDump.Component::getQualifier, ProjectDump.Component::getUuid, ProjectDump.Component::getUuidPath)
-      .containsExactlyInAnyOrder(
-        tuple(Qualifiers.FILE, FILE_UUID, FILE_UUID_PATH),
-        tuple(Qualifiers.PROJECT, PROJECT_UUID, UUID_PATH_OF_ROOT));
-  }
-
-  @Test
-  public void execute_register_all_components_uuids_as_their_id_in_ComponentRepository() {
-    dbTester.components().insertPublicProject(PROJECT);
-    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), FILE);
-    dbTester.commit();
-    when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(PROJECT));
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(ImmutableSet.of(
-      componentRepository.getRef(PROJECT.uuid()),
-      componentRepository.getRef(FILE.uuid()))).containsExactlyInAnyOrder(1L, 2L);
-  }
-
-  @Test
-  public void throws_ISE_if_error() {
-    dbTester.components().insertPublicProject(PROJECT);
-    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), FILE);
-    dbTester.commit();
-    when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(PROJECT));
-    dumpWriter.failIfMoreThan(1, DumpElement.COMPONENTS);
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Component Export failed after processing 1 components successfully");
-  }
-
-  @Test
-  public void getDescription_is_defined() {
-    assertThat(underTest.getDescription()).isEqualTo("Export components");
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/ExportMeasuresStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/ExportMeasuresStepTest.java
deleted file mode 100644 (file)
index 5ad6441..0000000
+++ /dev/null
@@ -1,259 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.ce.task.projectexport.steps;
-
-import com.sonarsource.governance.projectdump.protobuf.ProjectDump;
-import java.util.List;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.api.utils.log.LogTester;
-import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.ce.task.projectexport.component.ComponentRepositoryImpl;
-import org.sonar.ce.task.step.TestComputationStepContext;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.BranchDto;
-import org.sonar.db.component.BranchType;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.SnapshotDto;
-import org.sonar.db.measure.MeasureDto;
-import org.sonar.db.metric.MetricDto;
-
-import static com.google.common.collect.Lists.newArrayList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.db.component.ComponentDto.UUID_PATH_OF_ROOT;
-import static org.sonar.db.component.ComponentDto.UUID_PATH_SEPARATOR;
-import static org.sonar.db.component.SnapshotDto.STATUS_PROCESSED;
-import static org.sonar.db.component.SnapshotDto.STATUS_UNPROCESSED;
-
-public class ExportMeasuresStepTest {
-
-  private static final ComponentDto PROJECT = new ComponentDto()
-    .setKey("project_key")
-    .setUuid("project_uuid")
-    .setBranchUuid("project_uuid")
-    .setUuidPath(UUID_PATH_OF_ROOT)
-    .setEnabled(true);
-  private static final ComponentDto FILE = new ComponentDto()
-    .setKey("file_key")
-    .setUuid("file_uuid")
-    .setBranchUuid("project_uuid")
-    .setUuidPath(UUID_PATH_OF_ROOT + PROJECT.uuid() + UUID_PATH_SEPARATOR)
-    .setEnabled(true);
-  private static final ComponentDto ANOTHER_PROJECT = new ComponentDto()
-    .setKey("another_project_key")
-    .setUuid("another_project_uuid")
-    .setBranchUuid("another_project_uuid")
-    .setUuidPath(UUID_PATH_OF_ROOT)
-    .setEnabled(true);
-
-  private static final MetricDto NCLOC = new MetricDto()
-    .setUuid("3")
-    .setKey("ncloc")
-    .setShortName("Lines of code")
-    .setEnabled(true);
-
-  private static final MetricDto DISABLED_METRIC = new MetricDto()
-    .setUuid("4")
-    .setKey("coverage")
-    .setShortName("Coverage")
-    .setEnabled(false);
-
-  private static final MetricDto NEW_NCLOC = new MetricDto()
-    .setUuid("5")
-    .setKey("new_ncloc")
-    .setShortName("New Lines of code")
-    .setEnabled(true);
-
-  private static final List<BranchDto> BRANCHES = newArrayList(
-    new BranchDto()
-      .setBranchType(BranchType.BRANCH)
-      .setKey("master")
-      .setUuid(PROJECT.uuid())
-      .setProjectUuid(PROJECT.uuid()));
-
-
-  @Rule
-  public LogTester logTester = new LogTester();
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private ComponentRepositoryImpl componentRepository = new ComponentRepositoryImpl();
-  private MutableMetricRepository metricRepository = new MutableMetricRepositoryImpl();
-  private ProjectHolder projectHolder = mock(ProjectHolder.class);
-  private FakeDumpWriter dumpWriter = new FakeDumpWriter();
-  private ExportMeasuresStep underTest = new ExportMeasuresStep(dbTester.getDbClient(), projectHolder, componentRepository, metricRepository, dumpWriter);
-
-  @Before
-  public void setUp() {
-    String projectUuid = dbTester.components().insertPublicProject(PROJECT).uuid();
-    componentRepository.register(1, projectUuid, false);
-    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), FILE, ANOTHER_PROJECT);
-    dbTester.getDbClient().metricDao().insert(dbTester.getSession(), NCLOC, DISABLED_METRIC, NEW_NCLOC);
-    dbTester.commit();
-    when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(PROJECT));
-    when(projectHolder.branches()).thenReturn(BRANCHES);
-  }
-
-  @Test
-  public void export_zero_measures() {
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES)).isEmpty();
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("0 measures exported");
-    assertThat(metricRepository.getRefByUuid()).isEmpty();
-  }
-
-  @Test
-  public void export_measures() {
-    SnapshotDto firstAnalysis = insertSnapshot("U_1", PROJECT, STATUS_PROCESSED);
-    insertMeasure(firstAnalysis, PROJECT, new MeasureDto().setValue(100.0).setMetricUuid(NCLOC.getUuid()));
-    SnapshotDto secondAnalysis = insertSnapshot("U_2", PROJECT, STATUS_PROCESSED);
-    insertMeasure(secondAnalysis, PROJECT, new MeasureDto().setValue(110.0).setMetricUuid(NCLOC.getUuid()));
-    SnapshotDto anotherProjectAnalysis = insertSnapshot("U_3", ANOTHER_PROJECT, STATUS_PROCESSED);
-    insertMeasure(anotherProjectAnalysis, ANOTHER_PROJECT, new MeasureDto().setValue(500.0).setMetricUuid(NCLOC.getUuid()));
-    dbTester.commit();
-
-    underTest.execute(new TestComputationStepContext());
-
-    List<ProjectDump.Measure> exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES);
-    assertThat(exportedMeasures).hasSize(2);
-    assertThat(exportedMeasures).extracting(ProjectDump.Measure::getAnalysisUuid).containsOnly(firstAnalysis.getUuid(), secondAnalysis.getUuid());
-    assertThat(exportedMeasures).extracting(ProjectDump.Measure::getMetricRef).containsOnly(0);
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("2 measures exported");
-    assertThat(metricRepository.getRefByUuid()).containsOnlyKeys(NCLOC.getUuid());
-  }
-
-  @Test
-  public void do_not_export_measures_on_unprocessed_snapshots() {
-    SnapshotDto firstAnalysis = insertSnapshot("U_1", PROJECT, STATUS_UNPROCESSED);
-    insertMeasure(firstAnalysis, PROJECT, new MeasureDto().setValue(100.0).setMetricUuid(NCLOC.getUuid()));
-    dbTester.commit();
-
-    underTest.execute(new TestComputationStepContext());
-
-    List<ProjectDump.Measure> exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES);
-    assertThat(exportedMeasures).isEmpty();
-  }
-
-  @Test
-  public void do_not_export_measures_on_disabled_metrics() {
-    SnapshotDto firstAnalysis = insertSnapshot("U_1", PROJECT, STATUS_PROCESSED);
-    insertMeasure(firstAnalysis, PROJECT, new MeasureDto().setValue(100.0).setMetricUuid(DISABLED_METRIC.getUuid()));
-    dbTester.commit();
-
-    underTest.execute(new TestComputationStepContext());
-
-    List<ProjectDump.Measure> exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES);
-    assertThat(exportedMeasures).isEmpty();
-  }
-
-  @Test
-  public void test_exported_fields() {
-    SnapshotDto analysis = insertSnapshot("U_1", PROJECT, STATUS_PROCESSED);
-    MeasureDto dto = new MeasureDto()
-      .setMetricUuid(NCLOC.getUuid())
-      .setValue(100.0)
-      .setData("data")
-      .setAlertStatus("OK")
-      .setAlertText("alert text");
-    insertMeasure(analysis, PROJECT, dto);
-    dbTester.commit();
-
-    underTest.execute(new TestComputationStepContext());
-
-    List<ProjectDump.Measure> exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES);
-    ProjectDump.Measure measure = exportedMeasures.get(0);
-    assertThat(measure.getAlertStatus()).isEqualTo(dto.getAlertStatus());
-    assertThat(measure.getAlertText()).isEqualTo(dto.getAlertText());
-    assertThat(measure.getDoubleValue().getValue()).isEqualTo(dto.getValue());
-    assertThat(measure.getTextValue()).isEqualTo(dto.getData());
-    assertThat(measure.getMetricRef()).isZero();
-    assertThat(measure.getAnalysisUuid()).isEqualTo(analysis.getUuid());
-    assertThat(measure.getVariation1().getValue()).isZero();
-  }
-
-  @Test
-  public void test_exported_fields_new_metric() {
-    SnapshotDto analysis = insertSnapshot("U_1", PROJECT, STATUS_PROCESSED);
-    MeasureDto dto = new MeasureDto()
-      .setMetricUuid(NEW_NCLOC.getUuid())
-      .setValue(100.0)
-      .setData("data")
-      .setAlertStatus("OK")
-      .setAlertText("alert text");
-    insertMeasure(analysis, PROJECT, dto);
-    dbTester.commit();
-
-    underTest.execute(new TestComputationStepContext());
-
-    List<ProjectDump.Measure> exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES);
-    ProjectDump.Measure measure = exportedMeasures.get(0);
-    assertThat(measure.getAlertStatus()).isEqualTo(dto.getAlertStatus());
-    assertThat(measure.getAlertText()).isEqualTo(dto.getAlertText());
-    assertThat(measure.getDoubleValue().getValue()).isZero();
-    assertThat(measure.getTextValue()).isEqualTo(dto.getData());
-    assertThat(measure.getMetricRef()).isZero();
-    assertThat(measure.getAnalysisUuid()).isEqualTo(analysis.getUuid());
-    assertThat(measure.getVariation1().getValue()).isEqualTo(dto.getValue());
-  }
-
-  @Test
-  public void test_null_exported_fields() {
-    SnapshotDto analysis = insertSnapshot("U_1", PROJECT, STATUS_PROCESSED);
-    insertMeasure(analysis, PROJECT, new MeasureDto().setMetricUuid(NCLOC.getUuid()));
-    dbTester.commit();
-
-    underTest.execute(new TestComputationStepContext());
-
-    ProjectDump.Measure measure = dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES).get(0);
-    assertThat(measure.getAlertStatus()).isEmpty();
-    assertThat(measure.getAlertText()).isEmpty();
-    assertThat(measure.hasDoubleValue()).isFalse();
-    assertThat(measure.getTextValue()).isEmpty();
-    assertThat(measure.hasVariation1()).isFalse();
-  }
-
-  @Test
-  public void test_getDescription() {
-    assertThat(underTest.getDescription()).isEqualTo("Export measures");
-  }
-
-  private SnapshotDto insertSnapshot(String snapshotUuid, ComponentDto project, String status) {
-    SnapshotDto snapshot = new SnapshotDto()
-      .setUuid(snapshotUuid)
-      .setComponentUuid(project.uuid())
-      .setStatus(status)
-      .setLast(true);
-    dbTester.getDbClient().snapshotDao().insert(dbTester.getSession(), snapshot);
-    return snapshot;
-  }
-
-  private void insertMeasure(SnapshotDto analysisDto, ComponentDto componentDto, MeasureDto measureDto) {
-    measureDto
-      .setAnalysisUuid(analysisDto.getUuid())
-      .setComponentUuid(componentDto.uuid());
-    dbTester.getDbClient().measureDao().insert(dbTester.getSession(), measureDto);
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1235_add_component_uuid_to_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1235_add_component_uuid_to_duplications_index.rb
deleted file mode 100644 (file)
index 34dfbce..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2016 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.
-#
-
-#
-# SonarQube 6.0
-#
-class AddComponentUuidToDuplicationsIndex < ActiveRecord::Migration
-
-  def self.up
-    execute_java_migration('org.sonar.db.version.v60.AddComponentUuidColumnToDuplicationsIndex')
-  end
-end
diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1236_populate_component_uuid_of_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1236_populate_component_uuid_of_duplications_index.rb
deleted file mode 100644 (file)
index 6f9d4dd..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2016 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.
-#
-
-#
-# SonarQube 6.0
-#
-class PopulateComponentUuidOfDuplicationsIndex < ActiveRecord::Migration
-
-  def self.up
-    execute_java_migration('org.sonar.db.version.v60.PopulateComponentUuidOfDuplicationsIndex')
-  end
-end
diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1237_delete_orphan_duplications_index_rows_without_component.rb b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1237_delete_orphan_duplications_index_rows_without_component.rb
deleted file mode 100644 (file)
index f4f67a9..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2016 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.
-#
-
-#
-# SonarQube 6.0
-#
-class DeleteOrphanDuplicationsIndexRowsWithoutComponent < ActiveRecord::Migration
-
-  def self.up
-    execute_java_migration('org.sonar.db.version.v60.DeleteOrphanDuplicationsIndexRowsWithoutComponent')
-  end
-end
diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1238_make_component_uuid_not_null_on_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1238_make_component_uuid_not_null_on_duplications_index.rb
deleted file mode 100644 (file)
index eece62d..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2016 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.
-#
-
-#
-# SonarQube 6.0
-#
-class MakeComponentUuidNotNullOnDuplicationsIndex < ActiveRecord::Migration
-
-  def self.up
-    execute_java_migration('org.sonar.db.version.v60.MakeComponentUuidNotNullOnDuplicationsIndex')
-  end
-end
diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1239_add_analysis_uuid_to_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1239_add_analysis_uuid_to_duplications_index.rb
deleted file mode 100644 (file)
index bb5011b..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2016 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.
-#
-
-#
-# SonarQube 6.0
-#
-class AddAnalysisUuidToDuplicationsIndex < ActiveRecord::Migration
-
-  def self.up
-    execute_java_migration('org.sonar.db.version.v60.AddAnalysisUuidColumnToDuplicationsIndex')
-  end
-end
diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1240_populate_analysis_uuid_of_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1240_populate_analysis_uuid_of_duplications_index.rb
deleted file mode 100644 (file)
index 6451732..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2016 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.
-#
-
-#
-# SonarQube 6.0
-#
-class PopulateAnalysisUuidOfDuplicationsIndex < ActiveRecord::Migration
-
-  def self.up
-    execute_java_migration('org.sonar.db.version.v60.PopulateAnalysisUuidOfDuplicationsIndex')
-  end
-end
diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1241_delete_orphan_duplications_index_rows_without_analysis.rb b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1241_delete_orphan_duplications_index_rows_without_analysis.rb
deleted file mode 100644 (file)
index 5afedbe..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2016 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.
-#
-
-#
-# SonarQube 6.0
-#
-class DeleteOrphanDuplicationsIndexRowsWithoutAnalysis < ActiveRecord::Migration
-
-  def self.up
-    execute_java_migration('org.sonar.db.version.v60.DeleteOrphanDuplicationsIndexRowsWithoutAnalysis')
-  end
-end
diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1242_make_analysis_uuid_not_null_on_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1242_make_analysis_uuid_not_null_on_duplications_index.rb
deleted file mode 100644 (file)
index 961cd6e..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2016 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.
-#
-
-#
-# SonarQube 6.0
-#
-class MakeAnalysisUuidNotNullOnDuplicationsIndex < ActiveRecord::Migration
-
-  def self.up
-    execute_java_migration('org.sonar.db.version.v60.MakeAnalysisUuidNotNullOnDuplicationsIndex')
-
-    add_index :duplications_index, [:analysis_uuid, :component_uuid], :name => 'duplication_analysis_component'
-  end
-end
diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/AddAnalysisUuidColumnToDuplicationsIndex.java b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/AddAnalysisUuidColumnToDuplicationsIndex.java
deleted file mode 100644 (file)
index f641c80..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.v1;
-
-import java.sql.SQLException;
-import org.sonar.db.Database;
-import org.sonar.db.version.AddColumnsBuilder;
-
-import static org.sonar.db.version.VarcharColumnDef.UUID_VARCHAR_SIZE;
-import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder;
-
-public class AddAnalysisUuidColumnToDuplicationsIndex extends DdlChange {
-
-  private static final String TABLE_DUPLICATIONS_INDEX = "duplications_index";
-
-  public AddAnalysisUuidColumnToDuplicationsIndex(Database db) {
-    super(db);
-  }
-
-  @Override
-  public void execute(Context context) throws SQLException {
-    context.execute(new AddColumnsBuilder(getDatabase().getDialect(), TABLE_DUPLICATIONS_INDEX)
-      .addColumn(newVarcharColumnDefBuilder().setColumnName("analysis_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(true).build())
-      .build());
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/AddComponentUuidColumnToDuplicationsIndex.java b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/AddComponentUuidColumnToDuplicationsIndex.java
deleted file mode 100644 (file)
index b6f6763..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.v1;
-
-import java.sql.SQLException;
-import org.sonar.db.Database;
-import org.sonar.db.version.AddColumnsBuilder;
-
-import static org.sonar.db.version.VarcharColumnDef.UUID_VARCHAR_SIZE;
-import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder;
-
-public class AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex extends DdlChange {
-
-  private static final String TABLE_PUBLICATIONS_INDEX = "duplications_index";
-
-  public AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex(Database db) {
-    super(db);
-  }
-
-  @Override
-  public void execute(Context context) throws SQLException {
-    context.execute(new AddColumnsBuilder(getDatabase().getDialect(), TABLE_PUBLICATIONS_INDEX)
-      .addColumn(newVarcharColumnDefBuilder().setColumnName("component_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(true).build())
-      .addColumn(newVarcharColumnDefBuilder().setColumnName("analysis_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(true).build())
-      .build());
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/DeleteOrphanDuplicationsIndexRowsWithoutComponent.java b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/DeleteOrphanDuplicationsIndexRowsWithoutComponent.java
deleted file mode 100644 (file)
index eb7ed0b..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.v1;
-
-import java.sql.SQLException;
-import org.sonar.db.Database;
-import org.sonar.server.platform.db.migration.step.MassUpdate;
-
-public class DeleteOrphanDuplicationsIndexRowsWithoutComponent extends BaseDataChange {
-
-  public DeleteOrphanDuplicationsIndexRowsWithoutComponent(Database db) {
-    super(db);
-  }
-
-  @Override
-  public void execute(Context context) throws SQLException {
-    MassUpdate massUpdate = context.prepareMassUpdate();
-    massUpdate.select("SELECT id from duplications_index where component_uuid is null");
-    massUpdate.update("DELETE from duplications_index WHERE id=?");
-    massUpdate.rowPluralName("resources_index entries");
-    massUpdate.execute((row, update) -> {
-      update.setLong(1, row.getLong(1));
-      return true;
-    });
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/MakeComponentUuidNotNullOnDuplicationsIndex.java b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/MakeComponentUuidNotNullOnDuplicationsIndex.java
deleted file mode 100644 (file)
index e4d1f35..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.v1;
-
-import java.sql.SQLException;
-import org.sonar.db.Database;
-import org.sonar.db.version.AlterColumnsBuilder;
-
-import static org.sonar.db.version.VarcharColumnDef.UUID_VARCHAR_SIZE;
-import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder;
-
-public class MakeComponentUuidNotNullOnDuplicationsIndex extends DdlChange {
-
-  private static final String TABLE_DUPLICATIONS_INDEX = "duplications_index";
-
-  public MakeComponentUuidNotNullOnDuplicationsIndex(Database db) {
-    super(db);
-  }
-
-  @Override
-  public void execute(Context context) throws SQLException {
-    context.execute(new AlterColumnsBuilder(getDatabase().getDialect(), TABLE_DUPLICATIONS_INDEX)
-      .updateColumn(newVarcharColumnDefBuilder().setColumnName("component_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(false).build())
-      .build());
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/1235_add_component_uuid_and_analysis_uuid_to_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/1235_add_component_uuid_and_analysis_uuid_to_duplications_index.rb
deleted file mode 100644 (file)
index c7ecc23..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2016 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.
-#
-
-#
-# SonarQube 6.0
-#
-class AddComponentUuidAndAnalysisUuidToDuplicationsIndex < ActiveRecord::Migration
-
-  def self.up
-    execute_java_migration('org.sonar.db.version.v60.AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex')
-  end
-end
diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/1236_populate_component_uuid_and_analysis_uuid_of_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/1236_populate_component_uuid_and_analysis_uuid_of_duplications_index.rb
deleted file mode 100644 (file)
index 024aa7f..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2016 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.
-#
-
-#
-# SonarQube 6.0
-#
-class PopulateComponentUuidAndAnalysisUuidOfDuplicationsIndex < ActiveRecord::Migration
-
-  def self.up
-    execute_java_migration('org.sonar.db.version.v60.PopulateComponentUuidAndAnalysisUuidOfDuplicationsIndex')
-  end
-end
diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/1237_delete_orphan_duplications_index_rows_without_component_or_analysis.rb b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/1237_delete_orphan_duplications_index_rows_without_component_or_analysis.rb
deleted file mode 100644 (file)
index caa887b..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2016 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.
-#
-
-#
-# SonarQube 6.0
-#
-class DeleteOrphanDuplicationsIndexRowsWithoutComponentOrAnalysis < ActiveRecord::Migration
-
-  def self.up
-    execute_java_migration('org.sonar.db.version.v60.DeleteOrphanDuplicationsIndexRowsWithoutComponentOrAnalysis')
-  end
-end
diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/1238_make_component_uuid_and_analysis_uuid_not_null_on_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/1238_make_component_uuid_and_analysis_uuid_not_null_on_duplications_index.rb
deleted file mode 100644 (file)
index 72e090f..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-#
-# SonarQube, open source software quality management tool.
-# Copyright (C) 2008-2016 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.
-#
-
-#
-# SonarQube 6.0
-#
-class MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex < ActiveRecord::Migration
-
-  def self.up
-    execute_java_migration('org.sonar.db.version.v60.MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex')
-
-    add_index :duplications_index, [:analysis_uuid, :component_uuid], :name => 'duplication_analysis_component'
-  end
-end
diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex.java b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex.java
deleted file mode 100644 (file)
index d3df009..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.v2;
-
-import java.sql.SQLException;
-import org.sonar.db.Database;
-import org.sonar.db.version.AddColumnsBuilder;
-
-import static org.sonar.db.version.VarcharColumnDef.UUID_VARCHAR_SIZE;
-import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder;
-
-public class AddComponentUuidColumnToDuplicationsIndex extends DdlChange {
-
-  private static final String TABLE_PUBLICATIONS_INDEX = "duplications_index";
-
-  public AddComponentUuidColumnToDuplicationsIndex(Database db) {
-    super(db);
-  }
-
-  @Override
-  public void execute(Context context) throws SQLException {
-    context.execute(new AddColumnsBuilder(getDatabase().getDialect(), TABLE_PUBLICATIONS_INDEX)
-      .addColumn(newVarcharColumnDefBuilder().setColumnName("component_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(true).build())
-      .build());
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex.java b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex.java
deleted file mode 100644 (file)
index 01f5610..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.v2;
-
-import java.sql.SQLException;
-import org.sonar.db.Database;
-import org.sonar.db.version.AlterColumnsBuilder;
-
-import static org.sonar.db.version.VarcharColumnDef.UUID_VARCHAR_SIZE;
-import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder;
-
-public class MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex extends DdlChange {
-
-  private static final String TABLE_DUPLICATIONS_INDEX = "duplications_index";
-
-  public MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex(Database db) {
-    super(db);
-  }
-
-  @Override
-  public void execute(Context context) throws SQLException {
-    context.execute(new AlterColumnsBuilder(getDatabase().getDialect(), TABLE_DUPLICATIONS_INDEX)
-      .updateColumn(newVarcharColumnDefBuilder().setColumnName("component_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(false).build())
-      .updateColumn(newVarcharColumnDefBuilder().setColumnName("analysis_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(false).build())
-      .build());
-  }
-
-}
diff --git a/server/sonar-webserver-api/src/it/java/org/sonar/server/plugins/DetectPluginChangeIT.java b/server/sonar-webserver-api/src/it/java/org/sonar/server/plugins/DetectPluginChangeIT.java
new file mode 100644 (file)
index 0000000..412565a
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.plugins;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.Plugin;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.plugin.PluginType;
+import org.sonar.db.DbTester;
+import org.sonar.db.plugin.PluginDto;
+import org.sonar.server.plugins.PluginFilesAndMd5.FileAndMd5;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class DetectPluginChangeIT {
+  @Rule
+  public DbTester dbTester = DbTester.create();
+
+  private final ServerPluginRepository pluginRepository = new ServerPluginRepository();
+  private final DetectPluginChange detectPluginChange = new DetectPluginChange(pluginRepository, dbTester.getDbClient());
+
+  @Test
+  public void detect_changed_plugin() {
+    addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED);
+    addPluginToFs("plugin1", "hash2", PluginType.BUNDLED);
+
+    detectPluginChange.start();
+    assertThat(detectPluginChange.anyPluginChanged()).isTrue();
+  }
+
+  @Test
+  public void detect_changed_type() {
+    addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED);
+    addPluginToFs("plugin1", "hash1", PluginType.EXTERNAL);
+
+    detectPluginChange.start();
+    assertThat(detectPluginChange.anyPluginChanged()).isTrue();
+  }
+
+  @Test
+  public void detect_new_plugin() {
+    addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED);
+    addPluginToFs("plugin2", "hash1", PluginType.BUNDLED);
+
+    detectPluginChange.start();
+    assertThat(detectPluginChange.anyPluginChanged()).isTrue();
+  }
+
+  @Test
+  public void detect_removed_plugin() {
+    addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED, true);
+    addPluginToFs("plugin1", "hash1", PluginType.BUNDLED);
+
+    detectPluginChange.start();
+    assertThat(detectPluginChange.anyPluginChanged()).isTrue();
+  }
+
+  @Test
+  public void detect_missing_plugin() {
+    addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED);
+
+    detectPluginChange.start();
+    assertThat(detectPluginChange.anyPluginChanged()).isTrue();
+  }
+
+  @Test
+  public void detect_no_changes() {
+    addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED);
+    addPluginToFs("plugin1", "hash1", PluginType.BUNDLED);
+    addPluginToDb("plugin2", "hash2", PluginDto.Type.EXTERNAL);
+    addPluginToFs("plugin2", "hash2", PluginType.EXTERNAL);
+
+    detectPluginChange.start();
+    assertThat(detectPluginChange.anyPluginChanged()).isFalse();
+  }
+
+  @Test
+  public void fail_if_start_twice() {
+    detectPluginChange.start();
+    assertThrows(IllegalStateException.class, detectPluginChange::start);
+  }
+
+  @Test
+  public void fail_if_not_started() {
+    assertThrows(NullPointerException.class, detectPluginChange::anyPluginChanged);
+  }
+
+  private void addPluginToDb(String key, String hash, PluginDto.Type type) {
+    addPluginToDb(key, hash, type,  false);
+  }
+
+  private void addPluginToDb(String key, String hash, PluginDto.Type type, boolean removed) {
+    dbTester.pluginDbTester().insertPlugin(p -> p.setKee(key).setFileHash(hash).setType(type).setRemoved(removed));
+  }
+
+  private void addPluginToFs(String key, String hash, PluginType type) {
+    PluginInfo pluginInfo = new PluginInfo(key);
+    Plugin plugin = mock(Plugin.class);
+    FileAndMd5 fileAndMd5 = mock(FileAndMd5.class);
+    when(fileAndMd5.getMd5()).thenReturn(hash);
+    ServerPlugin serverPlugin = new ServerPlugin(pluginInfo, type, plugin, fileAndMd5, null);
+    pluginRepository.addPlugin(serverPlugin);
+  }
+
+}
diff --git a/server/sonar-webserver-api/src/it/java/org/sonar/server/rule/CachingRuleFinderIT.java b/server/sonar-webserver-api/src/it/java/org/sonar/server/rule/CachingRuleFinderIT.java
new file mode 100644 (file)
index 0000000..5d0134b
--- /dev/null
@@ -0,0 +1,437 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.rule;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Consumer;
+import javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.Rule;
+import org.sonar.api.rules.RuleQuery;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.rule.RuleDao;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleParamDto;
+import org.sonar.db.rule.RuleTesting;
+
+import static java.util.stream.Collectors.toList;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+public class CachingRuleFinderIT {
+  @org.junit.Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private final DbClient dbClient = dbTester.getDbClient();
+  private final AlwaysIncreasingSystem2 system2 = new AlwaysIncreasingSystem2();
+  private RuleDto[] ruleDtos;
+  private RuleParamDto[] ruleParams;
+  private CachingRuleFinder underTest;
+  private RuleDescriptionFormatter ruleDescriptionFormatter = new RuleDescriptionFormatter();
+
+  @Before()
+  public void setUp() {
+    Consumer<RuleDto> setUpdatedAt = rule -> rule.setUpdatedAt(system2.now());
+    this.ruleDtos = new RuleDto[] {
+      dbTester.rules().insert(setUpdatedAt),
+      dbTester.rules().insert(setUpdatedAt),
+      dbTester.rules().insert(setUpdatedAt),
+      dbTester.rules().insert(setUpdatedAt),
+      dbTester.rules().insert(setUpdatedAt),
+      dbTester.rules().insert(setUpdatedAt)
+    };
+    this.ruleParams = Arrays.stream(ruleDtos)
+      .map(rule -> dbTester.rules().insertRuleParam(rule))
+      .toArray(RuleParamDto[]::new);
+
+    underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter);
+
+    // delete all data from DB to ensure tests rely on cache exclusively
+    dbTester.executeUpdateSql("delete from rules");
+    dbTester.executeUpdateSql("delete from rules_parameters");
+    assertThat(dbTester.countRowsOfTable("rules")).isZero();
+    assertThat(dbTester.countRowsOfTable("rules_parameters")).isZero();
+  }
+
+  @Test
+  public void constructor_reads_rules_from_DB() {
+    DbClient dbClient = mock(DbClient.class);
+    DbSession dbSession = mock(DbSession.class);
+    RuleDao ruleDao = mock(RuleDao.class);
+    when(dbClient.openSession(anyBoolean())).thenReturn(dbSession);
+    when(dbClient.ruleDao()).thenReturn(ruleDao);
+
+    new CachingRuleFinder(dbClient, ruleDescriptionFormatter);
+
+    verify(dbClient).openSession(anyBoolean());
+    verify(ruleDao).selectAll(dbSession);
+    verify(ruleDao).selectAllRuleParams(dbSession);
+    verifyNoMoreInteractions(ruleDao);
+  }
+
+  @Test
+  public void constructor_reads_parameters_from_DB() {
+    DbClient dbClient = mock(DbClient.class);
+    DbSession dbSession = mock(DbSession.class);
+    RuleDao ruleDao = mock(RuleDao.class);
+    when(dbClient.openSession(anyBoolean())).thenReturn(dbSession);
+    when(dbClient.ruleDao()).thenReturn(ruleDao);
+    List<RuleKey> ruleKeys = Arrays.asList(RuleKey.of("A", "B"), RuleKey.of("C", "D"), RuleKey.of("E", "F"));
+    when(ruleDao.selectAll(dbSession)).thenReturn(ruleKeys.stream().map(RuleTesting::newRule).collect(toList()));
+
+    new CachingRuleFinder(dbClient, ruleDescriptionFormatter);
+
+    verify(ruleDao).selectAllRuleParams(dbSession);
+  }
+
+  @Test
+  public void findByKey_returns_all_loaded_rules() {
+    for (int i = 0; i < ruleDtos.length; i++) {
+      RuleDto ruleDto = ruleDtos[i];
+      RuleParamDto ruleParam = ruleParams[i];
+
+      org.sonar.api.rules.Rule rule = underTest.findByKey(ruleDto.getKey());
+      verifyRule(rule, ruleDto, ruleParam);
+      assertThat(underTest.findByKey(ruleDto.getRepositoryKey(), ruleDto.getRuleKey()))
+        .isSameAs(rule);
+    }
+  }
+
+  @Test
+  public void findByKey_returns_null_when_RuleKey_is_null() {
+    assertThat(underTest.findByKey(null)).isNull();
+  }
+
+  @Test
+  public void findByKey_returns_null_when_repository_key_is_null() {
+    assertThat(underTest.findByKey(null, randomAlphabetic(2))).isNull();
+  }
+
+  @Test
+  public void findByKey_returns_null_when_key_is_null() {
+    assertThat(underTest.findByKey(randomAlphabetic(2), null)).isNull();
+  }
+
+  @Test
+  public void findByKey_returns_null_when_both_repository_key_and_key_are_null() {
+    assertThat(underTest.findByKey(null, null)).isNull();
+  }
+
+  @Test
+  public void find_returns_null_when_RuleQuery_is_empty() {
+    assertThat(underTest.find(null)).isNull();
+  }
+
+  @Test
+  public void find_returns_most_recent_rule_when_RuleQuery_has_no_non_null_field() {
+    Rule rule = underTest.find(RuleQuery.create());
+
+    assertThat(toRuleKey(rule)).isEqualTo(ruleDtos[5].getKey());
+  }
+
+  @Test
+  public void find_searches_by_exact_match_of_repository_key_and_returns_most_recent_rule() {
+    String repoKey = "ABCD";
+    RuleDto[] sameRepoKey = {
+      dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setUpdatedAt(system2.now())),
+      dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setUpdatedAt(system2.now()))
+    };
+    RuleDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now()));
+
+    CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter);
+
+    assertThat(toRuleKey(underTest.find(RuleQuery.create().withRepositoryKey(repoKey))))
+      .isEqualTo(sameRepoKey[1].getKey());
+    assertThat(toRuleKey(underTest.find(RuleQuery.create().withRepositoryKey(otherRule.getRepositoryKey()))))
+      .isEqualTo(otherRule.getKey());
+    assertThat(underTest.find(RuleQuery.create().withRepositoryKey(repoKey.toLowerCase())))
+      .isNull();
+    assertThat(underTest.find(RuleQuery.create().withRepositoryKey(randomAlphabetic(3))))
+      .isNull();
+  }
+
+  @Test
+  public void find_searches_by_exact_match_of_ruleKey_and_returns_most_recent_rule() {
+    String ruleKey = "ABCD";
+    RuleDto[] sameRuleKey = {
+      dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setUpdatedAt(system2.now())),
+      dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setUpdatedAt(system2.now()))
+    };
+    RuleDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now()));
+
+    CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter);
+
+    assertThat(toRuleKey(underTest.find(RuleQuery.create().withKey(ruleKey))))
+      .isEqualTo(sameRuleKey[1].getKey());
+    assertThat(toRuleKey(underTest.find(RuleQuery.create().withKey(otherRule.getRuleKey()))))
+      .isEqualTo(otherRule.getKey());
+    assertThat(underTest.find(RuleQuery.create().withKey(ruleKey.toLowerCase())))
+      .isNull();
+    assertThat(underTest.find(RuleQuery.create().withKey(randomAlphabetic(3))))
+      .isNull();
+  }
+
+  @Test
+  public void find_searches_by_exact_match_of_configKey_and_returns_most_recent_rule() {
+    String configKey = "ABCD";
+    RuleDto[] sameConfigKey = {
+      dbTester.rules().insert(rule -> rule.setConfigKey(configKey).setUpdatedAt(system2.now())),
+      dbTester.rules().insert(rule -> rule.setConfigKey(configKey).setUpdatedAt(system2.now()))
+    };
+    RuleDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now()));
+
+    CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter);
+
+    assertThat(toRuleKey(underTest.find(RuleQuery.create().withConfigKey(configKey))))
+      .isEqualTo(sameConfigKey[1].getKey());
+    assertThat(toRuleKey(underTest.find(RuleQuery.create().withConfigKey(otherRule.getConfigKey()))))
+      .isEqualTo(otherRule.getKey());
+    assertThat(underTest.find(RuleQuery.create().withConfigKey(configKey.toLowerCase())))
+      .isNull();
+    assertThat(underTest.find(RuleQuery.create().withConfigKey(randomAlphabetic(3))))
+      .isNull();
+  }
+
+  @Test
+  public void find_searches_by_exact_match_and_match_on_all_criterias_and_returns_most_recent_match() {
+    String repoKey = "ABCD";
+    String ruleKey = "EFGH";
+    String configKey = "IJKL";
+    RuleDto[] rules = {
+      dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setRuleKey(ruleKey).setConfigKey(configKey).setUpdatedAt(system2.now())),
+      dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setConfigKey(configKey).setUpdatedAt(system2.now())),
+      dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setConfigKey(configKey).setUpdatedAt(system2.now())),
+      dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now()))
+    };
+    RuleQuery allQuery = RuleQuery.create().withRepositoryKey(repoKey).withKey(ruleKey).withConfigKey(configKey);
+    RuleQuery ruleAndConfigKeyQuery = RuleQuery.create().withKey(ruleKey).withConfigKey(configKey);
+    RuleQuery repoAndConfigKeyQuery = RuleQuery.create().withRepositoryKey(repoKey).withConfigKey(configKey);
+    RuleQuery repoAndKeyQuery = RuleQuery.create().withRepositoryKey(repoKey).withKey(ruleKey);
+    RuleQuery configKeyQuery = RuleQuery.create().withConfigKey(configKey);
+    RuleQuery ruleKeyQuery = RuleQuery.create().withKey(ruleKey);
+    RuleQuery repoKeyQuery = RuleQuery.create().withRepositoryKey(repoKey);
+
+    CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter);
+
+    assertThat(toRuleKey(underTest.find(allQuery))).isEqualTo(rules[0].getKey());
+    assertThat(toRuleKey(underTest.find(ruleAndConfigKeyQuery))).isEqualTo(rules[1].getKey());
+    assertThat(toRuleKey(underTest.find(repoAndConfigKeyQuery))).isEqualTo(rules[2].getKey());
+    assertThat(toRuleKey(underTest.find(repoAndKeyQuery))).isEqualTo(rules[0].getKey());
+    assertThat(toRuleKey(underTest.find(repoKeyQuery))).isEqualTo(rules[2].getKey());
+    assertThat(toRuleKey(underTest.find(ruleKeyQuery))).isEqualTo(rules[1].getKey());
+    assertThat(toRuleKey(underTest.find(configKeyQuery))).isEqualTo(rules[2].getKey());
+  }
+
+  @Test
+  public void findAll_returns_empty_when_RuleQuery_is_empty() {
+    assertThat(underTest.findAll(null)).isEmpty();
+  }
+
+  @Test
+  public void findAll_returns_all_rules_when_RuleQuery_has_no_non_null_field() {
+    assertThat(underTest.findAll(RuleQuery.create()))
+      .extracting(CachingRuleFinderIT::toRuleKey)
+      .containsOnly(Arrays.stream(ruleDtos).map(RuleDto::getKey).toArray(RuleKey[]::new));
+  }
+
+  @Test
+  public void findAll_returns_all_rules_with_exact_same_repository_key_and_order_them_most_recent_first() {
+    String repoKey = "ABCD";
+    long currentTimeMillis = System.currentTimeMillis();
+    RuleDto[] sameRepoKey = {
+      dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setUpdatedAt(currentTimeMillis + system2.now())),
+      dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setUpdatedAt(currentTimeMillis + system2.now()))
+    };
+    RuleDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(currentTimeMillis + system2.now()));
+
+    CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter);
+
+    assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey(repoKey)))
+      .extracting(CachingRuleFinderIT::toRuleKey)
+      .containsExactly(sameRepoKey[1].getKey(), sameRepoKey[0].getKey());
+    assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey(otherRule.getRepositoryKey())))
+      .extracting(CachingRuleFinderIT::toRuleKey)
+      .containsExactly(otherRule.getKey());
+    assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey(repoKey.toLowerCase())))
+      .isEmpty();
+    assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey(randomAlphabetic(3))))
+      .isEmpty();
+  }
+
+  @Test
+  public void findAll_returns_all_rules_with_exact_same_rulekey_and_order_them_most_recent_first() {
+    String ruleKey = "ABCD";
+    RuleDto[] sameRuleKey = {
+      dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setUpdatedAt(system2.now())),
+      dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setUpdatedAt(system2.now()))
+    };
+    RuleDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now()));
+
+    CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter);
+
+    assertThat(underTest.findAll(RuleQuery.create().withKey(ruleKey)))
+      .extracting(CachingRuleFinderIT::toRuleKey)
+      .containsExactly(sameRuleKey[1].getKey(), sameRuleKey[0].getKey());
+    assertThat(underTest.findAll(RuleQuery.create().withKey(otherRule.getRuleKey())))
+      .extracting(CachingRuleFinderIT::toRuleKey)
+      .containsExactly(otherRule.getKey());
+    assertThat(underTest.findAll(RuleQuery.create().withKey(ruleKey.toLowerCase())))
+      .isEmpty();
+    assertThat(underTest.findAll(RuleQuery.create().withKey(randomAlphabetic(3))))
+      .isEmpty();
+  }
+
+  @Test
+  public void findAll_returns_all_rules_with_exact_same_configkey_and_order_them_most_recent_first() {
+    String configKey = "ABCD";
+    RuleDto[] sameConfigKey = {
+      dbTester.rules().insert(rule -> rule.setConfigKey(configKey).setUpdatedAt(system2.now())),
+      dbTester.rules().insert(rule -> rule.setConfigKey(configKey).setUpdatedAt(system2.now()))
+    };
+    RuleDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now()));
+
+    CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter);
+
+    assertThat(underTest.findAll(RuleQuery.create().withConfigKey(configKey)))
+      .extracting(CachingRuleFinderIT::toRuleKey)
+      .containsExactly(sameConfigKey[1].getKey(), sameConfigKey[0].getKey());
+    assertThat(underTest.findAll(RuleQuery.create().withConfigKey(otherRule.getConfigKey())))
+      .extracting(CachingRuleFinderIT::toRuleKey)
+      .containsExactly(otherRule.getKey());
+    assertThat(underTest.findAll(RuleQuery.create().withConfigKey(configKey.toLowerCase())))
+      .isEmpty();
+    assertThat(underTest.findAll(RuleQuery.create().withConfigKey(randomAlphabetic(3))))
+      .isEmpty();
+  }
+
+  @Test
+  public void findAll_returns_all_rules_which_match_exactly_all_criteria_and_order_then_by_most_recent_first() {
+    String repoKey = "ABCD";
+    String ruleKey = "EFGH";
+    String configKey = "IJKL";
+    RuleDto[] rules = {
+      dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setRuleKey(ruleKey).setConfigKey(configKey).setUpdatedAt(system2.now())),
+      dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setConfigKey(configKey).setUpdatedAt(system2.now())),
+      dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setConfigKey(configKey).setUpdatedAt(system2.now())),
+      dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now()))
+    };
+    RuleQuery allQuery = RuleQuery.create().withRepositoryKey(repoKey).withKey(ruleKey).withConfigKey(configKey);
+    RuleQuery ruleAndConfigKeyQuery = RuleQuery.create().withKey(ruleKey).withConfigKey(configKey);
+    RuleQuery repoAndConfigKeyQuery = RuleQuery.create().withRepositoryKey(repoKey).withConfigKey(configKey);
+    RuleQuery repoAndKeyQuery = RuleQuery.create().withRepositoryKey(repoKey).withKey(ruleKey);
+    RuleQuery configKeyQuery = RuleQuery.create().withConfigKey(configKey);
+    RuleQuery ruleKeyQuery = RuleQuery.create().withKey(ruleKey);
+    RuleQuery repoKeyQuery = RuleQuery.create().withRepositoryKey(repoKey);
+
+    CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter);
+
+    assertThat(underTest.findAll(allQuery))
+      .extracting(CachingRuleFinderIT::toRuleKey)
+      .containsExactly(rules[0].getKey());
+    assertThat(underTest.findAll(ruleAndConfigKeyQuery))
+      .extracting(CachingRuleFinderIT::toRuleKey)
+      .containsExactly(rules[1].getKey(), rules[0].getKey());
+    assertThat(underTest.findAll(repoAndConfigKeyQuery))
+      .extracting(CachingRuleFinderIT::toRuleKey)
+      .containsExactly(rules[2].getKey(), rules[0].getKey());
+    assertThat(underTest.findAll(repoAndKeyQuery))
+      .extracting(CachingRuleFinderIT::toRuleKey)
+      .containsExactly(rules[0].getKey());
+    assertThat(underTest.findAll(repoKeyQuery))
+      .extracting(CachingRuleFinderIT::toRuleKey)
+      .containsExactly(rules[2].getKey(), rules[0].getKey());
+    assertThat(underTest.findAll(ruleKeyQuery))
+      .extracting(CachingRuleFinderIT::toRuleKey)
+      .containsExactly(rules[1].getKey(), rules[0].getKey());
+    assertThat(underTest.findAll(configKeyQuery))
+      .extracting(CachingRuleFinderIT::toRuleKey)
+      .containsExactly(rules[2].getKey(), rules[1].getKey(), rules[0].getKey());
+  }
+
+  @Test
+  public void findDtoByKey_finds_rules() {
+    for(RuleDto dto : ruleDtos) {
+      assertThat(underTest.findDtoByKey(dto.getKey())).contains(dto);
+    }
+  }
+
+  @Test
+  public void findDtoByUuid_finds_rules() {
+    for(RuleDto dto : ruleDtos) {
+      assertThat(underTest.findDtoByUuid(dto.getUuid())).contains(dto);
+    }
+  }
+
+  @Test
+  public void findDtoByKey_returns_empty_if_rule_not_found() {
+    assertThat(underTest.findDtoByKey(RuleKey.of("unknown", "unknown"))).isEmpty();
+  }
+
+  @Test
+  public void findDtoByUuid_returns_empty_if_rule_not_found() {
+    assertThat(underTest.findDtoByUuid("unknown")).isEmpty();
+  }
+
+  @Test
+  public void findAll_returns_all_rules() {
+    assertThat(underTest.findAll()).containsOnly(ruleDtos);
+  }
+
+  private static RuleKey toRuleKey(Rule rule) {
+    return RuleKey.of(rule.getRepositoryKey(), rule.getKey());
+  }
+
+  private void verifyRule(@Nullable Rule rule, RuleDto ruleDto, RuleParamDto ruleParam) {
+    assertThat(rule).isNotNull();
+
+    assertThat(rule.getName()).isEqualTo(ruleDto.getName());
+    assertThat(rule.getLanguage()).isEqualTo(ruleDto.getLanguage());
+    assertThat(rule.getKey()).isEqualTo(ruleDto.getRuleKey());
+    assertThat(rule.getConfigKey()).isEqualTo(ruleDto.getConfigKey());
+    assertThat(rule.isTemplate()).isEqualTo(ruleDto.isTemplate());
+    assertThat(rule.getCreatedAt().getTime()).isEqualTo(ruleDto.getCreatedAt());
+    assertThat(rule.getUpdatedAt().getTime()).isEqualTo(ruleDto.getUpdatedAt());
+    assertThat(rule.getRepositoryKey()).isEqualTo(ruleDto.getRepositoryKey());
+    assertThat(rule.getSeverity().name()).isEqualTo(ruleDto.getSeverityString());
+    assertThat(rule.getSystemTags()).isEqualTo(ruleDto.getSystemTags().toArray(new String[0]));
+    assertThat(rule.getTags()).isEmpty();
+    assertThat(rule.getDescription()).isEqualTo(ruleDto.getDefaultRuleDescriptionSection().getContent());
+
+    assertThat(rule.getParams()).hasSize(1);
+    org.sonar.api.rules.RuleParam param = rule.getParams().iterator().next();
+    assertThat(param.getRule()).isSameAs(rule);
+    assertThat(param.getKey()).isEqualTo(ruleParam.getName());
+    assertThat(param.getDescription()).isEqualTo(ruleParam.getDescription());
+    assertThat(param.getType()).isEqualTo(ruleParam.getType());
+    assertThat(param.getDefaultValue()).isEqualTo(ruleParam.getDefaultValue());
+  }
+}
diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/DetectPluginChangeTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/DetectPluginChangeTest.java
deleted file mode 100644 (file)
index a0451f7..0000000
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.plugins;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.Plugin;
-import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.plugin.PluginType;
-import org.sonar.db.DbTester;
-import org.sonar.db.plugin.PluginDto;
-import org.sonar.server.plugins.PluginFilesAndMd5.FileAndMd5;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.assertThrows;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class DetectPluginChangeTest {
-  @Rule
-  public DbTester dbTester = DbTester.create();
-
-  private final ServerPluginRepository pluginRepository = new ServerPluginRepository();
-  private final DetectPluginChange detectPluginChange = new DetectPluginChange(pluginRepository, dbTester.getDbClient());
-
-  @Test
-  public void detect_changed_plugin() {
-    addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED);
-    addPluginToFs("plugin1", "hash2", PluginType.BUNDLED);
-
-    detectPluginChange.start();
-    assertThat(detectPluginChange.anyPluginChanged()).isTrue();
-  }
-
-  @Test
-  public void detect_changed_type() {
-    addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED);
-    addPluginToFs("plugin1", "hash1", PluginType.EXTERNAL);
-
-    detectPluginChange.start();
-    assertThat(detectPluginChange.anyPluginChanged()).isTrue();
-  }
-
-  @Test
-  public void detect_new_plugin() {
-    addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED);
-    addPluginToFs("plugin2", "hash1", PluginType.BUNDLED);
-
-    detectPluginChange.start();
-    assertThat(detectPluginChange.anyPluginChanged()).isTrue();
-  }
-
-  @Test
-  public void detect_removed_plugin() {
-    addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED, true);
-    addPluginToFs("plugin1", "hash1", PluginType.BUNDLED);
-
-    detectPluginChange.start();
-    assertThat(detectPluginChange.anyPluginChanged()).isTrue();
-  }
-
-  @Test
-  public void detect_missing_plugin() {
-    addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED);
-
-    detectPluginChange.start();
-    assertThat(detectPluginChange.anyPluginChanged()).isTrue();
-  }
-
-  @Test
-  public void detect_no_changes() {
-    addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED);
-    addPluginToFs("plugin1", "hash1", PluginType.BUNDLED);
-    addPluginToDb("plugin2", "hash2", PluginDto.Type.EXTERNAL);
-    addPluginToFs("plugin2", "hash2", PluginType.EXTERNAL);
-
-    detectPluginChange.start();
-    assertThat(detectPluginChange.anyPluginChanged()).isFalse();
-  }
-
-  @Test
-  public void fail_if_start_twice() {
-    detectPluginChange.start();
-    assertThrows(IllegalStateException.class, detectPluginChange::start);
-  }
-
-  @Test
-  public void fail_if_not_started() {
-    assertThrows(NullPointerException.class, detectPluginChange::anyPluginChanged);
-  }
-
-  private void addPluginToDb(String key, String hash, PluginDto.Type type) {
-    addPluginToDb(key, hash, type,  false);
-  }
-
-  private void addPluginToDb(String key, String hash, PluginDto.Type type, boolean removed) {
-    dbTester.pluginDbTester().insertPlugin(p -> p.setKee(key).setFileHash(hash).setType(type).setRemoved(removed));
-  }
-
-  private void addPluginToFs(String key, String hash, PluginType type) {
-    PluginInfo pluginInfo = new PluginInfo(key);
-    Plugin plugin = mock(Plugin.class);
-    FileAndMd5 fileAndMd5 = mock(FileAndMd5.class);
-    when(fileAndMd5.getMd5()).thenReturn(hash);
-    ServerPlugin serverPlugin = new ServerPlugin(pluginInfo, type, plugin, fileAndMd5, null);
-    pluginRepository.addPlugin(serverPlugin);
-  }
-
-}
diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/rule/CachingRuleFinderTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/rule/CachingRuleFinderTest.java
deleted file mode 100644 (file)
index 4cd4c1c..0000000
+++ /dev/null
@@ -1,437 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.rule;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.function.Consumer;
-import javax.annotation.Nullable;
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleQuery;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.rule.RuleDao;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.db.rule.RuleParamDto;
-import org.sonar.db.rule.RuleTesting;
-
-import static java.util.stream.Collectors.toList;
-import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-public class CachingRuleFinderTest {
-  @org.junit.Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private final DbClient dbClient = dbTester.getDbClient();
-  private final AlwaysIncreasingSystem2 system2 = new AlwaysIncreasingSystem2();
-  private RuleDto[] ruleDtos;
-  private RuleParamDto[] ruleParams;
-  private CachingRuleFinder underTest;
-  private RuleDescriptionFormatter ruleDescriptionFormatter = new RuleDescriptionFormatter();
-
-  @Before()
-  public void setUp() {
-    Consumer<RuleDto> setUpdatedAt = rule -> rule.setUpdatedAt(system2.now());
-    this.ruleDtos = new RuleDto[] {
-      dbTester.rules().insert(setUpdatedAt),
-      dbTester.rules().insert(setUpdatedAt),
-      dbTester.rules().insert(setUpdatedAt),
-      dbTester.rules().insert(setUpdatedAt),
-      dbTester.rules().insert(setUpdatedAt),
-      dbTester.rules().insert(setUpdatedAt)
-    };
-    this.ruleParams = Arrays.stream(ruleDtos)
-      .map(rule -> dbTester.rules().insertRuleParam(rule))
-      .toArray(RuleParamDto[]::new);
-
-    underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter);
-
-    // delete all data from DB to ensure tests rely on cache exclusively
-    dbTester.executeUpdateSql("delete from rules");
-    dbTester.executeUpdateSql("delete from rules_parameters");
-    assertThat(dbTester.countRowsOfTable("rules")).isZero();
-    assertThat(dbTester.countRowsOfTable("rules_parameters")).isZero();
-  }
-
-  @Test
-  public void constructor_reads_rules_from_DB() {
-    DbClient dbClient = mock(DbClient.class);
-    DbSession dbSession = mock(DbSession.class);
-    RuleDao ruleDao = mock(RuleDao.class);
-    when(dbClient.openSession(anyBoolean())).thenReturn(dbSession);
-    when(dbClient.ruleDao()).thenReturn(ruleDao);
-
-    new CachingRuleFinder(dbClient, ruleDescriptionFormatter);
-
-    verify(dbClient).openSession(anyBoolean());
-    verify(ruleDao).selectAll(dbSession);
-    verify(ruleDao).selectAllRuleParams(dbSession);
-    verifyNoMoreInteractions(ruleDao);
-  }
-
-  @Test
-  public void constructor_reads_parameters_from_DB() {
-    DbClient dbClient = mock(DbClient.class);
-    DbSession dbSession = mock(DbSession.class);
-    RuleDao ruleDao = mock(RuleDao.class);
-    when(dbClient.openSession(anyBoolean())).thenReturn(dbSession);
-    when(dbClient.ruleDao()).thenReturn(ruleDao);
-    List<RuleKey> ruleKeys = Arrays.asList(RuleKey.of("A", "B"), RuleKey.of("C", "D"), RuleKey.of("E", "F"));
-    when(ruleDao.selectAll(dbSession)).thenReturn(ruleKeys.stream().map(RuleTesting::newRule).collect(toList()));
-
-    new CachingRuleFinder(dbClient, ruleDescriptionFormatter);
-
-    verify(ruleDao).selectAllRuleParams(dbSession);
-  }
-
-  @Test
-  public void findByKey_returns_all_loaded_rules() {
-    for (int i = 0; i < ruleDtos.length; i++) {
-      RuleDto ruleDto = ruleDtos[i];
-      RuleParamDto ruleParam = ruleParams[i];
-
-      org.sonar.api.rules.Rule rule = underTest.findByKey(ruleDto.getKey());
-      verifyRule(rule, ruleDto, ruleParam);
-      assertThat(underTest.findByKey(ruleDto.getRepositoryKey(), ruleDto.getRuleKey()))
-        .isSameAs(rule);
-    }
-  }
-
-  @Test
-  public void findByKey_returns_null_when_RuleKey_is_null() {
-    assertThat(underTest.findByKey(null)).isNull();
-  }
-
-  @Test
-  public void findByKey_returns_null_when_repository_key_is_null() {
-    assertThat(underTest.findByKey(null, randomAlphabetic(2))).isNull();
-  }
-
-  @Test
-  public void findByKey_returns_null_when_key_is_null() {
-    assertThat(underTest.findByKey(randomAlphabetic(2), null)).isNull();
-  }
-
-  @Test
-  public void findByKey_returns_null_when_both_repository_key_and_key_are_null() {
-    assertThat(underTest.findByKey(null, null)).isNull();
-  }
-
-  @Test
-  public void find_returns_null_when_RuleQuery_is_empty() {
-    assertThat(underTest.find(null)).isNull();
-  }
-
-  @Test
-  public void find_returns_most_recent_rule_when_RuleQuery_has_no_non_null_field() {
-    Rule rule = underTest.find(RuleQuery.create());
-
-    assertThat(toRuleKey(rule)).isEqualTo(ruleDtos[5].getKey());
-  }
-
-  @Test
-  public void find_searches_by_exact_match_of_repository_key_and_returns_most_recent_rule() {
-    String repoKey = "ABCD";
-    RuleDto[] sameRepoKey = {
-      dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setUpdatedAt(system2.now())),
-      dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setUpdatedAt(system2.now()))
-    };
-    RuleDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now()));
-
-    CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter);
-
-    assertThat(toRuleKey(underTest.find(RuleQuery.create().withRepositoryKey(repoKey))))
-      .isEqualTo(sameRepoKey[1].getKey());
-    assertThat(toRuleKey(underTest.find(RuleQuery.create().withRepositoryKey(otherRule.getRepositoryKey()))))
-      .isEqualTo(otherRule.getKey());
-    assertThat(underTest.find(RuleQuery.create().withRepositoryKey(repoKey.toLowerCase())))
-      .isNull();
-    assertThat(underTest.find(RuleQuery.create().withRepositoryKey(randomAlphabetic(3))))
-      .isNull();
-  }
-
-  @Test
-  public void find_searches_by_exact_match_of_ruleKey_and_returns_most_recent_rule() {
-    String ruleKey = "ABCD";
-    RuleDto[] sameRuleKey = {
-      dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setUpdatedAt(system2.now())),
-      dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setUpdatedAt(system2.now()))
-    };
-    RuleDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now()));
-
-    CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter);
-
-    assertThat(toRuleKey(underTest.find(RuleQuery.create().withKey(ruleKey))))
-      .isEqualTo(sameRuleKey[1].getKey());
-    assertThat(toRuleKey(underTest.find(RuleQuery.create().withKey(otherRule.getRuleKey()))))
-      .isEqualTo(otherRule.getKey());
-    assertThat(underTest.find(RuleQuery.create().withKey(ruleKey.toLowerCase())))
-      .isNull();
-    assertThat(underTest.find(RuleQuery.create().withKey(randomAlphabetic(3))))
-      .isNull();
-  }
-
-  @Test
-  public void find_searches_by_exact_match_of_configKey_and_returns_most_recent_rule() {
-    String configKey = "ABCD";
-    RuleDto[] sameConfigKey = {
-      dbTester.rules().insert(rule -> rule.setConfigKey(configKey).setUpdatedAt(system2.now())),
-      dbTester.rules().insert(rule -> rule.setConfigKey(configKey).setUpdatedAt(system2.now()))
-    };
-    RuleDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now()));
-
-    CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter);
-
-    assertThat(toRuleKey(underTest.find(RuleQuery.create().withConfigKey(configKey))))
-      .isEqualTo(sameConfigKey[1].getKey());
-    assertThat(toRuleKey(underTest.find(RuleQuery.create().withConfigKey(otherRule.getConfigKey()))))
-      .isEqualTo(otherRule.getKey());
-    assertThat(underTest.find(RuleQuery.create().withConfigKey(configKey.toLowerCase())))
-      .isNull();
-    assertThat(underTest.find(RuleQuery.create().withConfigKey(randomAlphabetic(3))))
-      .isNull();
-  }
-
-  @Test
-  public void find_searches_by_exact_match_and_match_on_all_criterias_and_returns_most_recent_match() {
-    String repoKey = "ABCD";
-    String ruleKey = "EFGH";
-    String configKey = "IJKL";
-    RuleDto[] rules = {
-      dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setRuleKey(ruleKey).setConfigKey(configKey).setUpdatedAt(system2.now())),
-      dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setConfigKey(configKey).setUpdatedAt(system2.now())),
-      dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setConfigKey(configKey).setUpdatedAt(system2.now())),
-      dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now()))
-    };
-    RuleQuery allQuery = RuleQuery.create().withRepositoryKey(repoKey).withKey(ruleKey).withConfigKey(configKey);
-    RuleQuery ruleAndConfigKeyQuery = RuleQuery.create().withKey(ruleKey).withConfigKey(configKey);
-    RuleQuery repoAndConfigKeyQuery = RuleQuery.create().withRepositoryKey(repoKey).withConfigKey(configKey);
-    RuleQuery repoAndKeyQuery = RuleQuery.create().withRepositoryKey(repoKey).withKey(ruleKey);
-    RuleQuery configKeyQuery = RuleQuery.create().withConfigKey(configKey);
-    RuleQuery ruleKeyQuery = RuleQuery.create().withKey(ruleKey);
-    RuleQuery repoKeyQuery = RuleQuery.create().withRepositoryKey(repoKey);
-
-    CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter);
-
-    assertThat(toRuleKey(underTest.find(allQuery))).isEqualTo(rules[0].getKey());
-    assertThat(toRuleKey(underTest.find(ruleAndConfigKeyQuery))).isEqualTo(rules[1].getKey());
-    assertThat(toRuleKey(underTest.find(repoAndConfigKeyQuery))).isEqualTo(rules[2].getKey());
-    assertThat(toRuleKey(underTest.find(repoAndKeyQuery))).isEqualTo(rules[0].getKey());
-    assertThat(toRuleKey(underTest.find(repoKeyQuery))).isEqualTo(rules[2].getKey());
-    assertThat(toRuleKey(underTest.find(ruleKeyQuery))).isEqualTo(rules[1].getKey());
-    assertThat(toRuleKey(underTest.find(configKeyQuery))).isEqualTo(rules[2].getKey());
-  }
-
-  @Test
-  public void findAll_returns_empty_when_RuleQuery_is_empty() {
-    assertThat(underTest.findAll(null)).isEmpty();
-  }
-
-  @Test
-  public void findAll_returns_all_rules_when_RuleQuery_has_no_non_null_field() {
-    assertThat(underTest.findAll(RuleQuery.create()))
-      .extracting(CachingRuleFinderTest::toRuleKey)
-      .containsOnly(Arrays.stream(ruleDtos).map(RuleDto::getKey).toArray(RuleKey[]::new));
-  }
-
-  @Test
-  public void findAll_returns_all_rules_with_exact_same_repository_key_and_order_them_most_recent_first() {
-    String repoKey = "ABCD";
-    long currentTimeMillis = System.currentTimeMillis();
-    RuleDto[] sameRepoKey = {
-      dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setUpdatedAt(currentTimeMillis + system2.now())),
-      dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setUpdatedAt(currentTimeMillis + system2.now()))
-    };
-    RuleDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(currentTimeMillis + system2.now()));
-
-    CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter);
-
-    assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey(repoKey)))
-      .extracting(CachingRuleFinderTest::toRuleKey)
-      .containsExactly(sameRepoKey[1].getKey(), sameRepoKey[0].getKey());
-    assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey(otherRule.getRepositoryKey())))
-      .extracting(CachingRuleFinderTest::toRuleKey)
-      .containsExactly(otherRule.getKey());
-    assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey(repoKey.toLowerCase())))
-      .isEmpty();
-    assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey(randomAlphabetic(3))))
-      .isEmpty();
-  }
-
-  @Test
-  public void findAll_returns_all_rules_with_exact_same_rulekey_and_order_them_most_recent_first() {
-    String ruleKey = "ABCD";
-    RuleDto[] sameRuleKey = {
-      dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setUpdatedAt(system2.now())),
-      dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setUpdatedAt(system2.now()))
-    };
-    RuleDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now()));
-
-    CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter);
-
-    assertThat(underTest.findAll(RuleQuery.create().withKey(ruleKey)))
-      .extracting(CachingRuleFinderTest::toRuleKey)
-      .containsExactly(sameRuleKey[1].getKey(), sameRuleKey[0].getKey());
-    assertThat(underTest.findAll(RuleQuery.create().withKey(otherRule.getRuleKey())))
-      .extracting(CachingRuleFinderTest::toRuleKey)
-      .containsExactly(otherRule.getKey());
-    assertThat(underTest.findAll(RuleQuery.create().withKey(ruleKey.toLowerCase())))
-      .isEmpty();
-    assertThat(underTest.findAll(RuleQuery.create().withKey(randomAlphabetic(3))))
-      .isEmpty();
-  }
-
-  @Test
-  public void findAll_returns_all_rules_with_exact_same_configkey_and_order_them_most_recent_first() {
-    String configKey = "ABCD";
-    RuleDto[] sameConfigKey = {
-      dbTester.rules().insert(rule -> rule.setConfigKey(configKey).setUpdatedAt(system2.now())),
-      dbTester.rules().insert(rule -> rule.setConfigKey(configKey).setUpdatedAt(system2.now()))
-    };
-    RuleDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now()));
-
-    CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter);
-
-    assertThat(underTest.findAll(RuleQuery.create().withConfigKey(configKey)))
-      .extracting(CachingRuleFinderTest::toRuleKey)
-      .containsExactly(sameConfigKey[1].getKey(), sameConfigKey[0].getKey());
-    assertThat(underTest.findAll(RuleQuery.create().withConfigKey(otherRule.getConfigKey())))
-      .extracting(CachingRuleFinderTest::toRuleKey)
-      .containsExactly(otherRule.getKey());
-    assertThat(underTest.findAll(RuleQuery.create().withConfigKey(configKey.toLowerCase())))
-      .isEmpty();
-    assertThat(underTest.findAll(RuleQuery.create().withConfigKey(randomAlphabetic(3))))
-      .isEmpty();
-  }
-
-  @Test
-  public void findAll_returns_all_rules_which_match_exactly_all_criteria_and_order_then_by_most_recent_first() {
-    String repoKey = "ABCD";
-    String ruleKey = "EFGH";
-    String configKey = "IJKL";
-    RuleDto[] rules = {
-      dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setRuleKey(ruleKey).setConfigKey(configKey).setUpdatedAt(system2.now())),
-      dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setConfigKey(configKey).setUpdatedAt(system2.now())),
-      dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setConfigKey(configKey).setUpdatedAt(system2.now())),
-      dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now()))
-    };
-    RuleQuery allQuery = RuleQuery.create().withRepositoryKey(repoKey).withKey(ruleKey).withConfigKey(configKey);
-    RuleQuery ruleAndConfigKeyQuery = RuleQuery.create().withKey(ruleKey).withConfigKey(configKey);
-    RuleQuery repoAndConfigKeyQuery = RuleQuery.create().withRepositoryKey(repoKey).withConfigKey(configKey);
-    RuleQuery repoAndKeyQuery = RuleQuery.create().withRepositoryKey(repoKey).withKey(ruleKey);
-    RuleQuery configKeyQuery = RuleQuery.create().withConfigKey(configKey);
-    RuleQuery ruleKeyQuery = RuleQuery.create().withKey(ruleKey);
-    RuleQuery repoKeyQuery = RuleQuery.create().withRepositoryKey(repoKey);
-
-    CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter);
-
-    assertThat(underTest.findAll(allQuery))
-      .extracting(CachingRuleFinderTest::toRuleKey)
-      .containsExactly(rules[0].getKey());
-    assertThat(underTest.findAll(ruleAndConfigKeyQuery))
-      .extracting(CachingRuleFinderTest::toRuleKey)
-      .containsExactly(rules[1].getKey(), rules[0].getKey());
-    assertThat(underTest.findAll(repoAndConfigKeyQuery))
-      .extracting(CachingRuleFinderTest::toRuleKey)
-      .containsExactly(rules[2].getKey(), rules[0].getKey());
-    assertThat(underTest.findAll(repoAndKeyQuery))
-      .extracting(CachingRuleFinderTest::toRuleKey)
-      .containsExactly(rules[0].getKey());
-    assertThat(underTest.findAll(repoKeyQuery))
-      .extracting(CachingRuleFinderTest::toRuleKey)
-      .containsExactly(rules[2].getKey(), rules[0].getKey());
-    assertThat(underTest.findAll(ruleKeyQuery))
-      .extracting(CachingRuleFinderTest::toRuleKey)
-      .containsExactly(rules[1].getKey(), rules[0].getKey());
-    assertThat(underTest.findAll(configKeyQuery))
-      .extracting(CachingRuleFinderTest::toRuleKey)
-      .containsExactly(rules[2].getKey(), rules[1].getKey(), rules[0].getKey());
-  }
-
-  @Test
-  public void findDtoByKey_finds_rules() {
-    for(RuleDto dto : ruleDtos) {
-      assertThat(underTest.findDtoByKey(dto.getKey())).contains(dto);
-    }
-  }
-
-  @Test
-  public void findDtoByUuid_finds_rules() {
-    for(RuleDto dto : ruleDtos) {
-      assertThat(underTest.findDtoByUuid(dto.getUuid())).contains(dto);
-    }
-  }
-
-  @Test
-  public void findDtoByKey_returns_empty_if_rule_not_found() {
-    assertThat(underTest.findDtoByKey(RuleKey.of("unknown", "unknown"))).isEmpty();
-  }
-
-  @Test
-  public void findDtoByUuid_returns_empty_if_rule_not_found() {
-    assertThat(underTest.findDtoByUuid("unknown")).isEmpty();
-  }
-
-  @Test
-  public void findAll_returns_all_rules() {
-    assertThat(underTest.findAll()).containsOnly(ruleDtos);
-  }
-
-  private static RuleKey toRuleKey(Rule rule) {
-    return RuleKey.of(rule.getRepositoryKey(), rule.getKey());
-  }
-
-  private void verifyRule(@Nullable Rule rule, RuleDto ruleDto, RuleParamDto ruleParam) {
-    assertThat(rule).isNotNull();
-
-    assertThat(rule.getName()).isEqualTo(ruleDto.getName());
-    assertThat(rule.getLanguage()).isEqualTo(ruleDto.getLanguage());
-    assertThat(rule.getKey()).isEqualTo(ruleDto.getRuleKey());
-    assertThat(rule.getConfigKey()).isEqualTo(ruleDto.getConfigKey());
-    assertThat(rule.isTemplate()).isEqualTo(ruleDto.isTemplate());
-    assertThat(rule.getCreatedAt().getTime()).isEqualTo(ruleDto.getCreatedAt());
-    assertThat(rule.getUpdatedAt().getTime()).isEqualTo(ruleDto.getUpdatedAt());
-    assertThat(rule.getRepositoryKey()).isEqualTo(ruleDto.getRepositoryKey());
-    assertThat(rule.getSeverity().name()).isEqualTo(ruleDto.getSeverityString());
-    assertThat(rule.getSystemTags()).isEqualTo(ruleDto.getSystemTags().toArray(new String[0]));
-    assertThat(rule.getTags()).isEmpty();
-    assertThat(rule.getDescription()).isEqualTo(ruleDto.getDefaultRuleDescriptionSection().getContent());
-
-    assertThat(rule.getParams()).hasSize(1);
-    org.sonar.api.rules.RuleParam param = rule.getParams().iterator().next();
-    assertThat(param.getRule()).isSameAs(rule);
-    assertThat(param.getKey()).isEqualTo(ruleParam.getName());
-    assertThat(param.getDescription()).isEqualTo(ruleParam.getDescription());
-    assertThat(param.getType()).isEqualTo(ruleParam.getType());
-    assertThat(param.getDefaultValue()).isEqualTo(ruleParam.getDefaultValue());
-  }
-}
diff --git a/server/sonar-webserver-core/src/it/java/org/sonar/server/platform/PersistentSettingsIT.java b/server/sonar-webserver-core/src/it/java/org/sonar/server/platform/PersistentSettingsIT.java
new file mode 100644 (file)
index 0000000..efa7181
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.platform;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.config.internal.Settings;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+import org.sonar.server.setting.SettingsChangeNotifier;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public class PersistentSettingsIT {
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  private Settings delegate = new MapSettings();
+  private SettingsChangeNotifier changeNotifier = mock(SettingsChangeNotifier.class);
+  private PersistentSettings underTest = new PersistentSettings(delegate, dbTester.getDbClient(), changeNotifier);
+
+  @Test
+  public void insert_property_into_database_and_notify_extensions() {
+    assertThat(underTest.getString("foo")).isNull();
+
+    underTest.saveProperty("foo", "bar");
+
+    assertThat(underTest.getString("foo")).isEqualTo("bar");
+    assertThat(dbTester.getDbClient().propertiesDao().selectGlobalProperty("foo").getValue()).isEqualTo("bar");
+    verify(changeNotifier).onGlobalPropertyChange("foo", "bar");
+  }
+
+  @Test
+  public void delete_property_from_database_and_notify_extensions() {
+    underTest.saveProperty("foo", "bar");
+    underTest.saveProperty("foo", null);
+
+    assertThat(underTest.getString("foo")).isNull();
+    assertThat(dbTester.getDbClient().propertiesDao().selectGlobalProperty("foo")).isNull();
+    verify(changeNotifier).onGlobalPropertyChange("foo", null);
+  }
+
+  @Test
+  public void getSettings_returns_delegate() {
+    assertThat(underTest.getSettings()).isSameAs(delegate);
+  }
+}
diff --git a/server/sonar-webserver-core/src/it/java/org/sonar/server/platform/StartupMetadataPersisterIT.java b/server/sonar-webserver-core/src/it/java/org/sonar/server/platform/StartupMetadataPersisterIT.java
new file mode 100644 (file)
index 0000000..89d7050
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.platform;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+import org.sonar.db.property.PropertyDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class StartupMetadataPersisterIT {
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private StartupMetadata metadata = new StartupMetadata(123_456_789L);
+  private StartupMetadataPersister underTest = new StartupMetadataPersister(metadata, dbTester.getDbClient());
+
+  @Test
+  public void persist_metadata_at_startup() {
+    underTest.start();
+
+    assertPersistedProperty(CoreProperties.SERVER_STARTTIME, DateUtils.formatDateTime(metadata.getStartedAt()));
+
+    underTest.stop();
+  }
+
+  private void assertPersistedProperty(String propertyKey, String expectedValue) {
+    PropertyDto prop = dbTester.getDbClient().propertiesDao().selectGlobalProperty(dbTester.getSession(), propertyKey);
+    assertThat(prop.getValue()).isEqualTo(expectedValue);
+  }
+}
diff --git a/server/sonar-webserver-core/src/it/java/org/sonar/server/platform/serverid/ServerIdManagerIT.java b/server/sonar-webserver-core/src/it/java/org/sonar/server/platform/serverid/ServerIdManagerIT.java
new file mode 100644 (file)
index 0000000..85754d2
--- /dev/null
@@ -0,0 +1,292 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.platform.serverid;
+
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.SonarEdition;
+import org.sonar.api.SonarQubeSide;
+import org.sonar.api.internal.SonarRuntimeImpl;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.Version;
+import org.sonar.core.platform.ServerId;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.property.PropertyDto;
+import org.sonar.server.platform.NodeInformation;
+import org.sonar.server.property.InternalProperties;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.SonarQubeSide.COMPUTE_ENGINE;
+import static org.sonar.api.SonarQubeSide.SERVER;
+import static org.sonar.core.platform.ServerId.DATABASE_ID_LENGTH;
+import static org.sonar.core.platform.ServerId.NOT_UUID_DATASET_ID_LENGTH;
+import static org.sonar.core.platform.ServerId.UUID_DATASET_ID_LENGTH;
+
+@RunWith(DataProviderRunner.class)
+public class ServerIdManagerIT {
+
+  private static final ServerId WITH_DATABASE_ID_SERVER_ID = ServerId.of(randomAlphanumeric(DATABASE_ID_LENGTH), randomAlphanumeric(NOT_UUID_DATASET_ID_LENGTH));
+  private static final String CHECKSUM_1 = randomAlphanumeric(12);
+
+  @Rule
+  public final DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private final ServerIdChecksum serverIdChecksum = mock(ServerIdChecksum.class);
+  private final ServerIdFactory serverIdFactory = mock(ServerIdFactory.class);
+  private final DbClient dbClient = dbTester.getDbClient();
+  private final DbSession dbSession = dbTester.getSession();
+  private final NodeInformation nodeInformation = mock(NodeInformation.class);
+  private ServerIdManager underTest;
+
+  @After
+  public void tearDown() {
+    if (underTest != null) {
+      underTest.stop();
+    }
+  }
+
+  @Test
+  public void web_leader_persists_new_server_id_if_missing() {
+    mockCreateNewServerId();
+    mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+    when(nodeInformation.isStartupLeader()).thenReturn(true);
+
+    test(SERVER);
+
+    verifyDb(CHECKSUM_1);
+    verifyCreateNewServerIdFromScratch();
+  }
+
+  @Test
+  public void web_leader_persists_new_server_id_if_value_is_empty() {
+    insertServerId("");
+    mockCreateNewServerId();
+    mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+    when(nodeInformation.isStartupLeader()).thenReturn(true);
+
+    test(SERVER);
+
+    verifyDb(CHECKSUM_1);
+    verifyCreateNewServerIdFromScratch();
+  }
+
+  @Test
+  public void web_leader_keeps_existing_server_id_if_valid() {
+    insertServerId(WITH_DATABASE_ID_SERVER_ID);
+    insertChecksum(CHECKSUM_1);
+    mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+    when(nodeInformation.isStartupLeader()).thenReturn(true);
+
+    test(SERVER);
+
+    verifyDb(CHECKSUM_1);
+  }
+
+  @Test
+  public void web_leader_creates_server_id_from_current_serverId_with_databaseId_if_checksum_fails() {
+    ServerId currentServerId = ServerId.of(randomAlphanumeric(DATABASE_ID_LENGTH), randomAlphanumeric(UUID_DATASET_ID_LENGTH));
+    insertServerId(currentServerId);
+    insertChecksum("does_not_match_WITH_DATABASE_ID_SERVER_ID");
+    mockChecksumOf(currentServerId, "matches_WITH_DATABASE_ID_SERVER_ID");
+    mockCreateNewServerIdFrom(currentServerId);
+    mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+    when(nodeInformation.isStartupLeader()).thenReturn(true);
+
+    test(SERVER);
+
+    verifyDb(CHECKSUM_1);
+    verifyCreateNewServerIdFrom(currentServerId);
+  }
+
+  @Test
+  public void web_leader_generates_missing_checksum_for_current_serverId_with_databaseId() {
+    insertServerId(WITH_DATABASE_ID_SERVER_ID);
+    mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+    when(nodeInformation.isStartupLeader()).thenReturn(true);
+
+    test(SERVER);
+
+    verifyDb(CHECKSUM_1);
+  }
+
+  @Test
+  public void web_follower_does_not_fail_if_server_id_matches_checksum() {
+    insertServerId(WITH_DATABASE_ID_SERVER_ID);
+    insertChecksum(CHECKSUM_1);
+    mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+    when(nodeInformation.isStartupLeader()).thenReturn(false);
+
+    test(SERVER);
+
+    // no changes
+    verifyDb(CHECKSUM_1);
+  }
+
+  @Test
+  public void web_follower_fails_if_server_id_is_missing() {
+    when(nodeInformation.isStartupLeader()).thenReturn(false);
+
+    expectMissingServerIdException(() -> test(SERVER));
+  }
+
+  @Test
+  public void web_follower_fails_if_server_id_is_empty() {
+    insertServerId("");
+    when(nodeInformation.isStartupLeader()).thenReturn(false);
+
+    expectEmptyServerIdException(() -> test(SERVER));
+  }
+
+  @Test
+  public void web_follower_fails_if_checksum_does_not_match() {
+    String dbChecksum = "boom";
+    insertServerId(WITH_DATABASE_ID_SERVER_ID);
+    insertChecksum(dbChecksum);
+    mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+    when(nodeInformation.isStartupLeader()).thenReturn(false);
+
+    try {
+      test(SERVER);
+      fail("An ISE should have been raised");
+    } catch (IllegalStateException e) {
+      assertThat(e.getMessage()).isEqualTo("Server ID is invalid");
+      // no changes
+      verifyDb(dbChecksum);
+    }
+  }
+
+  @Test
+  public void compute_engine_does_not_fail_if_server_id_is_valid() {
+    insertServerId(WITH_DATABASE_ID_SERVER_ID);
+    insertChecksum(CHECKSUM_1);
+    mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+
+    test(COMPUTE_ENGINE);
+
+    // no changes
+    verifyDb(CHECKSUM_1);
+  }
+
+  @Test
+  public void compute_engine_fails_if_server_id_is_missing() {
+    expectMissingServerIdException(() -> test(COMPUTE_ENGINE));
+  }
+
+  @Test
+  public void compute_engine_fails_if_server_id_is_empty() {
+    insertServerId("");
+
+    expectEmptyServerIdException(() -> test(COMPUTE_ENGINE));
+  }
+
+  @Test
+  public void compute_engine_fails_if_server_id_is_invalid() {
+    String dbChecksum = "boom";
+    insertServerId(WITH_DATABASE_ID_SERVER_ID);
+    insertChecksum(dbChecksum);
+    mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
+
+    try {
+      test(SERVER);
+      fail("An ISE should have been raised");
+    } catch (IllegalStateException e) {
+      assertThat(e.getMessage()).isEqualTo("Server ID is invalid");
+      // no changes
+      verifyDb(dbChecksum);
+    }
+  }
+
+  private void expectEmptyServerIdException(ThrowingCallable callback) {
+    assertThatThrownBy(callback)
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Property sonar.core.id is empty in database");
+  }
+
+  private void expectMissingServerIdException(ThrowingCallable callback) {
+    assertThatThrownBy(callback)
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Property sonar.core.id is missing in database");
+  }
+
+  private void verifyDb(String expectedChecksum) {
+    assertThat(dbClient.propertiesDao().selectGlobalProperty(dbSession, CoreProperties.SERVER_ID))
+      .extracting(PropertyDto::getValue)
+      .isEqualTo(ServerIdManagerIT.WITH_DATABASE_ID_SERVER_ID.toString());
+    assertThat(dbClient.internalPropertiesDao().selectByKey(dbSession, InternalProperties.SERVER_ID_CHECKSUM))
+      .hasValue(expectedChecksum);
+  }
+
+  private void mockCreateNewServerId() {
+    when(serverIdFactory.create()).thenReturn(WITH_DATABASE_ID_SERVER_ID);
+    when(serverIdFactory.create(any())).thenThrow(new IllegalStateException("new ServerId should not be created from current server id"));
+  }
+
+  private void mockCreateNewServerIdFrom(ServerId currentServerId) {
+    when(serverIdFactory.create()).thenThrow(new IllegalStateException("new ServerId should be created from current server id"));
+    when(serverIdFactory.create(currentServerId)).thenReturn(ServerIdManagerIT.WITH_DATABASE_ID_SERVER_ID);
+  }
+
+  private void verifyCreateNewServerIdFromScratch() {
+    verify(serverIdFactory).create();
+  }
+
+  private void verifyCreateNewServerIdFrom(ServerId currentServerId) {
+    verify(serverIdFactory).create(currentServerId);
+  }
+
+  private void mockChecksumOf(ServerId serverId, String checksum1) {
+    when(serverIdChecksum.computeFor(serverId.toString())).thenReturn(checksum1);
+  }
+
+  private void insertServerId(ServerId serverId) {
+    insertServerId(serverId.toString());
+  }
+
+  private void insertServerId(String serverId) {
+    dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey(CoreProperties.SERVER_ID).setValue(serverId),
+      null, null, null, null);
+    dbSession.commit();
+  }
+
+  private void insertChecksum(String value) {
+    dbClient.internalPropertiesDao().save(dbSession, InternalProperties.SERVER_ID_CHECKSUM, value);
+    dbSession.commit();
+  }
+
+  private void test(SonarQubeSide side) {
+    underTest = new ServerIdManager(serverIdChecksum, serverIdFactory, dbClient, SonarRuntimeImpl
+      .forSonarQube(Version.create(6, 7), side, SonarEdition.COMMUNITY), nodeInformation);
+    underTest.start();
+  }
+}
diff --git a/server/sonar-webserver-core/src/it/java/org/sonar/server/startup/RegisterMetricsIT.java b/server/sonar-webserver-core/src/it/java/org/sonar/server/startup/RegisterMetricsIT.java
new file mode 100644 (file)
index 0000000..1f1b1db
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.startup;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.stream.IntStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.measures.Metrics;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.SequenceUuidFactory;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.metric.MetricDto;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
+
+public class RegisterMetricsIT {
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private final UuidFactory uuidFactory = new SequenceUuidFactory();
+  private final DbClient dbClient = dbTester.getDbClient();
+  private final RegisterMetrics register = new RegisterMetrics(dbClient, uuidFactory);
+
+  /**
+   * Insert new metrics, including custom metrics
+   */
+  @Test
+  public void insert_new_metrics() {
+    Metric m1 = new Metric.Builder("m1", "One", Metric.ValueType.FLOAT)
+      .setDescription("desc1")
+      .setDirection(1)
+      .setQualitative(true)
+      .setDomain("domain1")
+      .setUserManaged(false)
+      .create();
+    Metric custom = new Metric.Builder("custom", "Custom", Metric.ValueType.FLOAT)
+      .setDescription("This is a custom metric")
+      .setUserManaged(true)
+      .create();
+
+    register.register(asList(m1, custom));
+
+    Map<String, MetricDto> metricsByKey = selectAllMetrics();
+    assertThat(metricsByKey).hasSize(2);
+    assertEquals(m1, metricsByKey.get("m1"));
+    assertEquals(custom, metricsByKey.get("custom"));
+  }
+
+  /**
+   * Update existing metrics
+   */
+  @Test
+  public void update_metrics() {
+    dbTester.measures().insertMetric(t -> t.setKey("m1")
+      .setShortName("name")
+      .setValueType(Metric.ValueType.INT.name())
+      .setDescription("old desc")
+      .setDomain("old domain")
+      .setShortName("old short name")
+      .setQualitative(false)
+      .setEnabled(true)
+      .setOptimizedBestValue(false)
+      .setDirection(1)
+      .setHidden(false));
+
+    Metric m1 = new Metric.Builder("m1", "New name", Metric.ValueType.FLOAT)
+      .setDescription("new description")
+      .setDirection(-1)
+      .setQualitative(true)
+      .setDomain("new domain")
+      .setUserManaged(false)
+      .setDecimalScale(3)
+      .setHidden(true)
+      .create();
+    register.register(asList(m1));
+
+    Map<String, MetricDto> metricsByKey = selectAllMetrics();
+    assertThat(metricsByKey).hasSize(1);
+    assertEquals(m1, metricsByKey.get("m1"));
+  }
+
+  @Test
+  public void disable_undefined_metrics() {
+    Random random = new Random();
+    int count = 1 + random.nextInt(10);
+    IntStream.range(0, count)
+      .forEach(t -> dbTester.measures().insertMetric(m -> m.setEnabled(random.nextBoolean())));
+
+    register.register(Collections.emptyList());
+
+    assertThat(selectAllMetrics().values().stream())
+      .extracting(MetricDto::isEnabled)
+      .containsOnly(IntStream.range(0, count).mapToObj(t -> false).toArray(Boolean[]::new));
+  }
+
+  @Test
+  public void enable_disabled_metrics() {
+    MetricDto enabledMetric = dbTester.measures().insertMetric(t -> t.setEnabled(true));
+    MetricDto disabledMetric = dbTester.measures().insertMetric(t -> t.setEnabled(false));
+
+    register.register(asList(builderOf(enabledMetric).create(), builderOf(disabledMetric).create()));
+
+    assertThat(selectAllMetrics().values())
+      .extracting(MetricDto::isEnabled)
+      .containsOnly(true, true);
+  }
+
+  @Test
+  public void insert_core_metrics() {
+    register.start();
+
+    assertThat(dbTester.countRowsOfTable("metrics")).isEqualTo(CoreMetrics.getMetrics().size());
+  }
+
+  @Test
+  public void fail_if_duplicated_plugin_metrics() {
+    Metrics plugin1 = new TestMetrics(new Metric.Builder("m1", "In first plugin", Metric.ValueType.FLOAT).create());
+    Metrics plugin2 = new TestMetrics(new Metric.Builder("m1", "In second plugin", Metric.ValueType.FLOAT).create());
+
+    assertThatThrownBy(() -> new RegisterMetrics(dbClient, uuidFactory, new Metrics[] {plugin1, plugin2}).start())
+      .isInstanceOf(IllegalStateException.class);
+  }
+
+  @Test
+  public void fail_if_plugin_duplicates_core_metric() {
+    Metrics plugin = new TestMetrics(new Metric.Builder("ncloc", "In plugin", Metric.ValueType.FLOAT).create());
+
+    assertThatThrownBy(() -> new RegisterMetrics(dbClient, uuidFactory, new Metrics[] {plugin}).start())
+      .isInstanceOf(IllegalStateException.class);
+  }
+
+  private static class TestMetrics implements Metrics {
+    private final List<Metric> metrics;
+
+    public TestMetrics(Metric... metrics) {
+      this.metrics = asList(metrics);
+    }
+
+    @Override
+    public List<Metric> getMetrics() {
+      return metrics;
+    }
+  }
+
+  private Map<String, MetricDto> selectAllMetrics() {
+    return dbTester.getDbClient().metricDao().selectAll(dbTester.getSession())
+      .stream()
+      .collect(uniqueIndex(MetricDto::getKey));
+  }
+
+  private void assertEquals(Metric expected, MetricDto actual) {
+    assertThat(actual.getKey()).isEqualTo(expected.getKey());
+    assertThat(actual.getShortName()).isEqualTo(expected.getName());
+    assertThat(actual.getValueType()).isEqualTo(expected.getType().name());
+    assertThat(actual.getDescription()).isEqualTo(expected.getDescription());
+    assertThat(actual.getDirection()).isEqualTo(expected.getDirection());
+    assertThat(actual.isQualitative()).isEqualTo(expected.getQualitative());
+  }
+
+  private static Metric.Builder builderOf(MetricDto enabledMetric) {
+    return new Metric.Builder(enabledMetric.getKey(), enabledMetric.getShortName(), Metric.ValueType.valueOf(enabledMetric.getValueType()))
+      .setDescription(enabledMetric.getDescription())
+      .setDirection(enabledMetric.getDirection())
+      .setQualitative(enabledMetric.isQualitative())
+      .setQualitative(enabledMetric.isQualitative())
+      .setDomain(enabledMetric.getDomain())
+      .setHidden(enabledMetric.isHidden());
+  }
+}
diff --git a/server/sonar-webserver-core/src/it/java/org/sonar/server/startup/RegisterPluginsIT.java b/server/sonar-webserver-core/src/it/java/org/sonar/server/startup/RegisterPluginsIT.java
new file mode 100644 (file)
index 0000000..52f24f0
--- /dev/null
@@ -0,0 +1,238 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.startup;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.utils.System2;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.plugin.PluginDto;
+import org.sonar.db.plugin.PluginDto.Type;
+import org.sonar.server.plugins.PluginFilesAndMd5;
+import org.sonar.core.plugin.PluginType;
+import org.sonar.server.plugins.ServerPlugin;
+import org.sonar.server.plugins.ServerPluginRepository;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
+
+public class RegisterPluginsIT {
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private final long now = 12345L;
+  private final DbClient dbClient = dbTester.getDbClient();
+  private final ServerPluginRepository serverPluginRepository = new ServerPluginRepository();
+  private final UuidFactory uuidFactory = mock(UuidFactory.class);
+  private final System2 system2 = mock(System2.class);
+  private final RegisterPlugins register = new RegisterPlugins(serverPluginRepository, dbClient, uuidFactory, system2);
+
+
+  @Before
+  public void setUp() {
+    when(system2.now()).thenReturn(now);
+  }
+
+  /**
+   * Insert new plugins
+   */
+  @Test
+  public void insert_new_plugins() throws IOException {
+    addPlugin("java", null);
+    addPlugin("javacustom", "java");
+    when(uuidFactory.create()).thenReturn("a").thenReturn("b").thenThrow(new IllegalStateException("Should be called only twice"));
+    register.start();
+
+    Map<String, PluginDto> pluginsByKey = selectAllPlugins();
+    assertThat(pluginsByKey).hasSize(2);
+    verify(pluginsByKey.get("java"), Type.BUNDLED, null, "93f725a07423fe1c889f448b33d21f46", false, now, now);
+    verify(pluginsByKey.get("javacustom"), Type.BUNDLED, "java", "bf8b3d4cb91efc5f9ef0bcd02f128ebf", false, now, now);
+
+    register.stop();
+  }
+
+  @Test
+  public void update_removed_plugins() throws IOException {
+    // will be flagged as removed
+    dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto()
+      .setUuid("a")
+      .setKee("java")
+      .setBasePluginKey(null)
+      .setFileHash("bd451e47a1aa76e73da0359cef63dd63")
+      .setType(Type.BUNDLED)
+      .setCreatedAt(1L)
+      .setUpdatedAt(1L));
+    // already flagged as removed
+    dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto()
+      .setUuid("b")
+      .setKee("java2")
+      .setBasePluginKey(null)
+      .setFileHash("bd451e47a1aa76e73da0359cef63dd63")
+      .setType(Type.BUNDLED)
+      .setRemoved(true)
+      .setCreatedAt(1L)
+      .setUpdatedAt(1L));
+    dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto()
+      .setUuid("c")
+      .setKee("csharp")
+      .setBasePluginKey(null)
+      .setFileHash("a20d785dbacb8f41a3b7392aa7d03b78")
+      .setType(Type.EXTERNAL)
+      .setCreatedAt(1L)
+      .setUpdatedAt(1L));
+    dbTester.commit();
+
+    addPlugin("csharp", PluginType.EXTERNAL, null);
+    register.start();
+
+    Map<String, PluginDto> pluginsByKey = selectAllPlugins();
+    assertThat(pluginsByKey).hasSize(3);
+    verify(pluginsByKey.get("java"), Type.BUNDLED, null, "bd451e47a1aa76e73da0359cef63dd63", true, 1L, now);
+    verify(pluginsByKey.get("java2"), Type.BUNDLED, null, "bd451e47a1aa76e73da0359cef63dd63", true, 1L, 1L);
+    verify(pluginsByKey.get("csharp"), Type.EXTERNAL, null, "a20d785dbacb8f41a3b7392aa7d03b78", false, 1L, 1L);
+  }
+
+  @Test
+  public void re_add_previously_removed_plugin() throws IOException {
+    dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto()
+      .setUuid("c")
+      .setKee("csharp")
+      .setBasePluginKey(null)
+      .setFileHash("a20d785dbacb8f41a3b7392aa7d03b78")
+      .setType(Type.EXTERNAL)
+      .setRemoved(true)
+      .setCreatedAt(1L)
+      .setUpdatedAt(1L));
+    dbTester.commit();
+
+    addPlugin("csharp", PluginType.EXTERNAL, null);
+    register.start();
+
+    Map<String, PluginDto> pluginsByKey = selectAllPlugins();
+    assertThat(pluginsByKey).hasSize(1);
+    verify(pluginsByKey.get("csharp"), Type.EXTERNAL, null, "a20d785dbacb8f41a3b7392aa7d03b78", false, 1L, now);
+  }
+
+  /**
+   * Update existing plugins, only when checksum is different and don't remove uninstalled plugins
+   */
+  @Test
+  public void update_changed_plugins() throws IOException {
+    dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto()
+      .setUuid("a")
+      .setKee("java")
+      .setBasePluginKey(null)
+      .setFileHash("bd451e47a1aa76e73da0359cef63dd63")
+      .setType(Type.BUNDLED)
+      .setCreatedAt(1L)
+      .setUpdatedAt(1L));
+    dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto()
+      .setUuid("b")
+      .setKee("javacustom")
+      .setBasePluginKey("java")
+      .setFileHash("de9b2de3ddc0680904939686c0dba5be")
+      .setType(Type.BUNDLED)
+      .setCreatedAt(1L)
+      .setUpdatedAt(1L));
+    dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto()
+      .setUuid("c")
+      .setKee("csharp")
+      .setBasePluginKey(null)
+      .setFileHash("a20d785dbacb8f41a3b7392aa7d03b78")
+      .setType(Type.EXTERNAL)
+      .setCreatedAt(1L)
+      .setUpdatedAt(1L));
+    dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto()
+      .setUuid("d")
+      .setKee("new-measures")
+      .setBasePluginKey(null)
+      .setFileHash("6d24712cf701c41ce5eaa948e0bd6d22")
+      .setType(Type.EXTERNAL)
+      .setCreatedAt(1L)
+      .setUpdatedAt(1L));
+
+    dbTester.commit();
+
+    addPlugin("javacustom", PluginType.BUNDLED, "java2");
+    // csharp plugin type changed
+    addPlugin("csharp", PluginType.BUNDLED, null);
+
+    register.start();
+
+    Map<String, PluginDto> pluginsByKey = selectAllPlugins();
+    assertThat(pluginsByKey).hasSize(4);
+    verify(pluginsByKey.get("java"), Type.BUNDLED, null, "bd451e47a1aa76e73da0359cef63dd63", true, 1L, now);
+    verify(pluginsByKey.get("javacustom"), Type.BUNDLED, "java2", "bf8b3d4cb91efc5f9ef0bcd02f128ebf", false, 1L, now);
+    verify(pluginsByKey.get("csharp"), Type.BUNDLED, null, "a20d785dbacb8f41a3b7392aa7d03b78", false, 1L, now);
+    verify(pluginsByKey.get("new-measures"), Type.EXTERNAL, null, "6d24712cf701c41ce5eaa948e0bd6d22", true, 1L, now);
+  }
+
+  private ServerPlugin addPlugin(String key, @Nullable String basePlugin) throws IOException {
+    return addPlugin(key, PluginType.BUNDLED, basePlugin);
+  }
+
+  private ServerPlugin addPlugin(String key, PluginType type, @Nullable String basePlugin) throws IOException {
+    File file = createPluginFile(key);
+    PluginFilesAndMd5.FileAndMd5 jar = new PluginFilesAndMd5.FileAndMd5(file);
+    PluginInfo info = new PluginInfo(key)
+      .setBasePlugin(basePlugin)
+      .setJarFile(file);
+    ServerPlugin serverPlugin = new ServerPlugin(info, type, null, jar, null);
+    serverPluginRepository.addPlugin(serverPlugin);
+    return serverPlugin;
+  }
+
+  private File createPluginFile(String key) throws IOException {
+    File pluginJar = temp.newFile();
+    FileUtils.write(pluginJar, key, StandardCharsets.UTF_8);
+    return pluginJar;
+  }
+
+  private Map<String, PluginDto> selectAllPlugins() {
+    return dbTester.getDbClient().pluginDao().selectAll(dbTester.getSession()).stream()
+      .collect(uniqueIndex(PluginDto::getKee));
+  }
+
+  private void verify(PluginDto pluginDto, Type type, @Nullable String basePluginKey, String fileHash, boolean removed, @Nullable Long createdAt, long updatedAt) {
+    assertThat(pluginDto.getBasePluginKey()).isEqualTo(basePluginKey);
+    assertThat(pluginDto.getType()).isEqualTo(type);
+    assertThat(pluginDto.getFileHash()).isEqualTo(fileHash);
+    assertThat(pluginDto.isRemoved()).isEqualTo(removed);
+    assertThat(pluginDto.getCreatedAt()).isEqualTo(createdAt);
+    assertThat(pluginDto.getUpdatedAt()).isEqualTo(updatedAt);
+  }
+
+}
diff --git a/server/sonar-webserver-core/src/it/java/org/sonar/server/startup/UpgradeSuggestionsCleanerIT.java b/server/sonar-webserver-core/src/it/java/org/sonar/server/startup/UpgradeSuggestionsCleanerIT.java
new file mode 100644 (file)
index 0000000..fdf008a
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.startup;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.sonar.api.SonarEdition;
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+import org.sonar.db.ce.CeTaskMessageDto;
+import org.sonar.db.ce.CeTaskMessageType;
+import org.sonar.db.user.UserDismissedMessageDto;
+import org.sonar.db.user.UserDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(DataProviderRunner.class)
+public class UpgradeSuggestionsCleanerIT {
+  private static final String TASK_UUID = "b8d564dd-4ceb-4dba-8a3d-5fafa2d72cdf";
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private final SonarRuntime sonarRuntime = mock(SonarRuntime.class);
+  private final UpgradeSuggestionsCleaner underTest = new UpgradeSuggestionsCleaner(dbTester.getDbClient(), sonarRuntime);
+
+  private UserDto user;
+
+  @Before
+  public void setup() {
+    user = dbTester.users().insertUser();
+  }
+
+  @DataProvider
+  public static Object[][] editionsWithCleanup() {
+    return new Object[][] {
+      {SonarEdition.DEVELOPER},
+      {SonarEdition.ENTERPRISE},
+      {SonarEdition.DATACENTER}
+    };
+  }
+
+  @DataProvider
+  public static Object[][] allEditions() {
+    return new Object[][] {
+      {SonarEdition.COMMUNITY},
+      {SonarEdition.DEVELOPER},
+      {SonarEdition.ENTERPRISE},
+      {SonarEdition.DATACENTER}
+    };
+  }
+
+  @Test
+  @UseDataProvider("editionsWithCleanup")
+  public void start_cleans_up_obsolete_upgrade_suggestions(SonarEdition edition) {
+    when(sonarRuntime.getEdition()).thenReturn(edition);
+    insertCeTaskMessage("ctm1", CeTaskMessageType.GENERIC, "msg1");
+    insertCeTaskMessage("ctm2", CeTaskMessageType.GENERIC, "msg2");
+    insertCeTaskMessage("ctm3", CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE, "upgrade-msg-1");
+    insertInUserDismissedMessages("u1", CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE);
+    insertInUserDismissedMessages("u2", CeTaskMessageType.GENERIC);
+
+    underTest.start();
+    underTest.stop();
+
+    assertThat(dbTester.getDbClient().ceTaskMessageDao().selectByTask(dbTester.getSession(), TASK_UUID))
+      .extracting(CeTaskMessageDto::getUuid)
+      .containsExactly("ctm1", "ctm2");
+    assertThat(dbTester.getDbClient().userDismissedMessagesDao().selectByUser(dbTester.getSession(), user))
+      .extracting(UserDismissedMessageDto::getUuid)
+      .containsExactly("u2");
+  }
+
+  @Test
+  public void start_does_nothing_in_community_edition() {
+    when(sonarRuntime.getEdition()).thenReturn(SonarEdition.COMMUNITY);
+    insertCeTaskMessage("ctm1", CeTaskMessageType.GENERIC, "msg1");
+    insertCeTaskMessage("ctm2", CeTaskMessageType.GENERIC, "msg2");
+    insertCeTaskMessage("ctm3", CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE, "upgrade-msg-1");
+    insertInUserDismissedMessages("u1", CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE);
+    insertInUserDismissedMessages("u2", CeTaskMessageType.GENERIC);
+
+    underTest.start();
+
+    assertThat(dbTester.getDbClient().ceTaskMessageDao().selectByTask(dbTester.getSession(), TASK_UUID))
+      .extracting(CeTaskMessageDto::getUuid)
+      .containsExactly("ctm1", "ctm2", "ctm3");
+    assertThat(dbTester.getDbClient().userDismissedMessagesDao().selectByUser(dbTester.getSession(), user))
+      .extracting(UserDismissedMessageDto::getUuid)
+      .containsExactlyInAnyOrder("u1", "u2");
+  }
+
+  @Test
+  @UseDataProvider("allEditions")
+  public void start_does_nothing_when_no_suggest_upgrade_messages(SonarEdition edition) {
+    when(sonarRuntime.getEdition()).thenReturn(edition);
+
+    underTest.start();
+
+    assertThat(dbTester.getDbClient().ceTaskMessageDao().selectByTask(dbTester.getSession(), TASK_UUID)).isEmpty();
+    assertThat(dbTester.getDbClient().userDismissedMessagesDao().selectByUser(dbTester.getSession(), user)).isEmpty();
+  }
+
+  private void insertCeTaskMessage(String uuid, CeTaskMessageType messageType, String msg) {
+    CeTaskMessageDto dto = new CeTaskMessageDto()
+      .setUuid(uuid)
+      .setMessage(msg)
+      .setType(messageType)
+      .setTaskUuid(TASK_UUID);
+    dbTester.getDbClient().ceTaskMessageDao().insert(dbTester.getSession(), dto);
+    dbTester.getSession().commit();
+  }
+
+  private void insertInUserDismissedMessages(String uuid, CeTaskMessageType messageType) {
+    UserDismissedMessageDto dto = new UserDismissedMessageDto()
+      .setUuid(uuid)
+      .setUserUuid(user.getUuid())
+      .setProjectUuid("PROJECT_1")
+      .setCeMessageType(messageType);
+    dbTester.getDbClient().userDismissedMessagesDao().insert(dbTester.getSession(), dto);
+    dbTester.getSession().commit();
+  }
+}
diff --git a/server/sonar-webserver-core/src/it/java/org/sonar/server/webhook/WebhookQGChangeEventListenerIT.java b/server/sonar-webserver-core/src/it/java/org/sonar/server/webhook/WebhookQGChangeEventListenerIT.java
new file mode 100644 (file)
index 0000000..31d3145
--- /dev/null
@@ -0,0 +1,329 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.webhook;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Random;
+import java.util.Set;
+import java.util.function.Supplier;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.UuidFactoryFast;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.AnalysisPropertyDto;
+import org.sonar.db.component.BranchDto;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.server.qualitygate.EvaluatedQualityGate;
+import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
+import org.sonar.server.qualitygate.changeevent.QGChangeEventListener;
+
+import static java.util.Arrays.stream;
+import static java.util.Collections.emptySet;
+import static java.util.stream.Stream.concat;
+import static java.util.stream.Stream.of;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.core.util.stream.MoreCollectors.toArrayList;
+import static org.sonar.db.component.BranchType.BRANCH;
+
+@RunWith(DataProviderRunner.class)
+public class WebhookQGChangeEventListenerIT {
+
+  private static final Set<QGChangeEventListener.ChangedIssue> CHANGED_ISSUES_ARE_IGNORED = emptySet();
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private DbClient dbClient = dbTester.getDbClient();
+
+  private EvaluatedQualityGate newQualityGate = mock(EvaluatedQualityGate.class);
+  private WebHooks webHooks = mock(WebHooks.class);
+  private WebhookPayloadFactory webhookPayloadFactory = mock(WebhookPayloadFactory.class);
+  private DbClient spiedOnDbClient = Mockito.spy(dbClient);
+  private WebhookQGChangeEventListener underTest = new WebhookQGChangeEventListener(webHooks, webhookPayloadFactory, spiedOnDbClient);
+  private DbClient mockedDbClient = mock(DbClient.class);
+  private WebhookQGChangeEventListener mockedUnderTest = new WebhookQGChangeEventListener(webHooks, webhookPayloadFactory, mockedDbClient);
+
+  @Test
+  @UseDataProvider("allCombinationsOfStatuses")
+  public void onIssueChanges_has_no_effect_if_no_webhook_is_configured(Metric.Level previousStatus, Metric.Level newStatus) {
+    Configuration configuration1 = mock(Configuration.class);
+    when(newQualityGate.getStatus()).thenReturn(newStatus);
+    QGChangeEvent qualityGateEvent = newQGChangeEvent(configuration1, previousStatus, newQualityGate);
+    mockWebhookDisabled(qualityGateEvent.getProject());
+
+    mockedUnderTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED);
+
+    verify(webHooks).isEnabled(qualityGateEvent.getProject());
+    verifyNoInteractions(webhookPayloadFactory, mockedDbClient);
+  }
+
+  @DataProvider
+  public static Object[][] allCombinationsOfStatuses() {
+    Metric.Level[] levelsAndNull = concat(of((Metric.Level) null), stream(Metric.Level.values()))
+      .toArray(Metric.Level[]::new);
+    Object[][] res = new Object[levelsAndNull.length * levelsAndNull.length][2];
+    int i = 0;
+    for (Metric.Level previousStatus : levelsAndNull) {
+      for (Metric.Level newStatus : levelsAndNull) {
+        res[i][0] = previousStatus;
+        res[i][1] = newStatus;
+        i++;
+      }
+    }
+    return res;
+  }
+
+  @Test
+  public void onIssueChanges_has_no_effect_if_event_has_neither_previousQGStatus_nor_qualityGate() {
+    Configuration configuration = mock(Configuration.class);
+    QGChangeEvent qualityGateEvent = newQGChangeEvent(configuration, null, null);
+    mockWebhookEnabled(qualityGateEvent.getProject());
+
+    underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED);
+
+    verifyNoInteractions(webhookPayloadFactory, mockedDbClient);
+  }
+
+  @Test
+  public void onIssueChanges_has_no_effect_if_event_has_same_status_in_previous_and_new_QG() {
+    Configuration configuration = mock(Configuration.class);
+    Metric.Level previousStatus = randomLevel();
+    when(newQualityGate.getStatus()).thenReturn(previousStatus);
+    QGChangeEvent qualityGateEvent = newQGChangeEvent(configuration, previousStatus, newQualityGate);
+    mockWebhookEnabled(qualityGateEvent.getProject());
+
+    underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED);
+
+    verifyNoInteractions(webhookPayloadFactory, mockedDbClient);
+  }
+
+  @Test
+  @UseDataProvider("newQGorNot")
+  public void onIssueChanges_calls_webhook_for_changeEvent_with_webhook_enabled(@Nullable EvaluatedQualityGate newQualityGate) {
+    ProjectAndBranch projectBranch = insertBranch(BRANCH, "foo");
+    SnapshotDto analysis = insertAnalysisTask(projectBranch);
+    Configuration configuration = mock(Configuration.class);
+    mockPayloadSupplierConsumedByWebhooks();
+    Map<String, String> properties = new HashMap<>();
+    properties.put("sonar.analysis.test1", randomAlphanumeric(50));
+    properties.put("sonar.analysis.test2", randomAlphanumeric(5000));
+    insertPropertiesFor(analysis.getUuid(), properties);
+    QGChangeEvent qualityGateEvent = newQGChangeEvent(projectBranch, analysis, configuration, newQualityGate);
+    mockWebhookEnabled(qualityGateEvent.getProject());
+
+    underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED);
+
+    ProjectAnalysis projectAnalysis = verifyWebhookCalledAndExtractPayloadFactoryArgument(projectBranch, analysis, qualityGateEvent.getProject());
+    assertThat(projectAnalysis).isEqualTo(
+      new ProjectAnalysis(
+        new Project(projectBranch.project.getUuid(), projectBranch.project.getKey(), projectBranch.project.getName()),
+        null,
+        new Analysis(analysis.getUuid(), analysis.getCreatedAt(), analysis.getRevision()),
+        new Branch(false, "foo", Branch.Type.BRANCH),
+        newQualityGate,
+        null,
+        properties));
+  }
+
+  @Test
+  @UseDataProvider("newQGorNot")
+  public void onIssueChanges_calls_webhook_on_main_branch(@Nullable EvaluatedQualityGate newQualityGate) {
+    ProjectAndBranch mainBranch = insertMainBranch();
+    SnapshotDto analysis = insertAnalysisTask(mainBranch);
+    Configuration configuration = mock(Configuration.class);
+    QGChangeEvent qualityGateEvent = newQGChangeEvent(mainBranch, analysis, configuration, newQualityGate);
+    mockWebhookEnabled(qualityGateEvent.getProject());
+
+    underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED);
+
+    verifyWebhookCalled(mainBranch, analysis, qualityGateEvent.getProject());
+  }
+
+  @Test
+  public void onIssueChanges_calls_webhook_on_branch() {
+    onIssueChangesCallsWebhookOnBranch(BRANCH);
+  }
+
+  @Test
+  public void onIssueChanges_calls_webhook_on_pr() {
+    onIssueChangesCallsWebhookOnBranch(BranchType.PULL_REQUEST);
+  }
+
+  public void onIssueChangesCallsWebhookOnBranch(BranchType branchType) {
+    ProjectAndBranch nonMainBranch = insertBranch(branchType, "foo");
+    SnapshotDto analysis = insertAnalysisTask(nonMainBranch);
+    Configuration configuration = mock(Configuration.class);
+    QGChangeEvent qualityGateEvent = newQGChangeEvent(nonMainBranch, analysis, configuration, null);
+    mockWebhookEnabled(qualityGateEvent.getProject());
+
+    underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED);
+
+    verifyWebhookCalled(nonMainBranch, analysis, qualityGateEvent.getProject());
+  }
+
+  @DataProvider
+  public static Object[][] newQGorNot() {
+    EvaluatedQualityGate newQualityGate = mock(EvaluatedQualityGate.class);
+    return new Object[][] {
+      {null},
+      {newQualityGate}
+    };
+  }
+
+  private void mockWebhookEnabled(ProjectDto... projects) {
+    for (ProjectDto dto : projects) {
+      when(webHooks.isEnabled(dto)).thenReturn(true);
+    }
+  }
+
+  private void mockWebhookDisabled(ProjectDto... projects) {
+    for (ProjectDto dto : projects) {
+      when(webHooks.isEnabled(dto)).thenReturn(false);
+    }
+  }
+
+  private void mockPayloadSupplierConsumedByWebhooks() {
+    Mockito.doAnswer(invocationOnMock -> {
+      Supplier<WebhookPayload> supplier = (Supplier<WebhookPayload>) invocationOnMock.getArguments()[1];
+      supplier.get();
+      return null;
+    }).when(webHooks)
+      .sendProjectAnalysisUpdate(any(), any());
+  }
+
+  private void insertPropertiesFor(String snapshotUuid, Map<String, String> properties) {
+    List<AnalysisPropertyDto> analysisProperties = properties.entrySet().stream()
+      .map(entry -> new AnalysisPropertyDto()
+        .setUuid(UuidFactoryFast.getInstance().create())
+        .setAnalysisUuid(snapshotUuid)
+        .setKey(entry.getKey())
+        .setValue(entry.getValue()))
+      .collect(toArrayList(properties.size()));
+    dbTester.getDbClient().analysisPropertiesDao().insert(dbTester.getSession(), analysisProperties);
+    dbTester.getSession().commit();
+  }
+
+  private SnapshotDto insertAnalysisTask(ProjectAndBranch projectAndBranch) {
+    return dbTester.components().insertSnapshot(projectAndBranch.getBranch());
+  }
+
+  private ProjectAnalysis verifyWebhookCalledAndExtractPayloadFactoryArgument(ProjectAndBranch projectAndBranch, SnapshotDto analysis, ProjectDto project) {
+    verifyWebhookCalled(projectAndBranch, analysis, project);
+
+    return extractPayloadFactoryArguments(1).iterator().next();
+  }
+
+  private void verifyWebhookCalled(ProjectAndBranch projectAndBranch, SnapshotDto analysis, ProjectDto project) {
+    verify(webHooks).isEnabled(project);
+    verify(webHooks).sendProjectAnalysisUpdate(
+      eq(new WebHooks.Analysis(projectAndBranch.uuid(), analysis.getUuid(), null)),
+      any());
+  }
+
+  private List<ProjectAnalysis> extractPayloadFactoryArguments(int time) {
+    ArgumentCaptor<ProjectAnalysis> projectAnalysisCaptor = ArgumentCaptor.forClass(ProjectAnalysis.class);
+    verify(webhookPayloadFactory, Mockito.times(time)).create(projectAnalysisCaptor.capture());
+    return projectAnalysisCaptor.getAllValues();
+  }
+
+  public ProjectAndBranch insertMainBranch() {
+    ProjectDto project = dbTester.components().insertPrivateProjectDto();
+    BranchDto branch = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), project.getUuid()).get();
+    dbTester.commit();
+    return new ProjectAndBranch(project, branch);
+  }
+
+  public ProjectAndBranch insertBranch(BranchType type, String branchKey) {
+    ProjectDto project = dbTester.components().insertPrivateProjectDto();
+    BranchDto branch = dbTester.components().insertProjectBranch(project, b -> b.setKey(branchKey).setBranchType(type));
+    return new ProjectAndBranch(project, branch);
+  }
+
+  public ProjectAndBranch insertBranch(ProjectDto project, BranchType type, String branchKey) {
+    BranchDto branch = dbTester.components().insertProjectBranch(project, b -> b.setKey(branchKey).setBranchType(type));
+    return new ProjectAndBranch(project, branch);
+  }
+
+  private static class ProjectAndBranch {
+    private final ProjectDto project;
+    private final BranchDto branch;
+
+    private ProjectAndBranch(ProjectDto project, BranchDto branch) {
+      this.project = project;
+      this.branch = branch;
+    }
+
+    public ProjectDto getProject() {
+      return project;
+    }
+
+    public BranchDto getBranch() {
+      return branch;
+    }
+
+    public String uuid() {
+      return project.getUuid();
+    }
+
+  }
+
+  private static QGChangeEvent newQGChangeEvent(Configuration configuration, @Nullable Metric.Level previousQQStatus, @Nullable EvaluatedQualityGate evaluatedQualityGate) {
+    return new QGChangeEvent(new ProjectDto(), new BranchDto(), new SnapshotDto(), configuration, previousQQStatus, () -> Optional.ofNullable(evaluatedQualityGate));
+  }
+
+  private static QGChangeEvent newQGChangeEvent(ProjectAndBranch branch, SnapshotDto analysis, Configuration configuration, @Nullable EvaluatedQualityGate evaluatedQualityGate) {
+    Metric.Level previousStatus = randomLevel();
+    if (evaluatedQualityGate != null) {
+      Metric.Level otherLevel = stream(Metric.Level.values())
+        .filter(s -> s != previousStatus)
+        .toArray(Metric.Level[]::new)[new Random().nextInt(Metric.Level.values().length - 1)];
+      when(evaluatedQualityGate.getStatus()).thenReturn(otherLevel);
+    }
+    return new QGChangeEvent(branch.project, branch.branch, analysis, configuration, previousStatus, () -> Optional.ofNullable(evaluatedQualityGate));
+  }
+
+  private static Metric.Level randomLevel() {
+    return Metric.Level.values()[new Random().nextInt(Metric.Level.values().length)];
+  }
+
+}
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/PersistentSettingsTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/PersistentSettingsTest.java
deleted file mode 100644 (file)
index e59874d..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.platform;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.api.config.internal.Settings;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
-import org.sonar.server.setting.SettingsChangeNotifier;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-public class PersistentSettingsTest {
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-  private Settings delegate = new MapSettings();
-  private SettingsChangeNotifier changeNotifier = mock(SettingsChangeNotifier.class);
-  private PersistentSettings underTest = new PersistentSettings(delegate, dbTester.getDbClient(), changeNotifier);
-
-  @Test
-  public void insert_property_into_database_and_notify_extensions() {
-    assertThat(underTest.getString("foo")).isNull();
-
-    underTest.saveProperty("foo", "bar");
-
-    assertThat(underTest.getString("foo")).isEqualTo("bar");
-    assertThat(dbTester.getDbClient().propertiesDao().selectGlobalProperty("foo").getValue()).isEqualTo("bar");
-    verify(changeNotifier).onGlobalPropertyChange("foo", "bar");
-  }
-
-  @Test
-  public void delete_property_from_database_and_notify_extensions() {
-    underTest.saveProperty("foo", "bar");
-    underTest.saveProperty("foo", null);
-
-    assertThat(underTest.getString("foo")).isNull();
-    assertThat(dbTester.getDbClient().propertiesDao().selectGlobalProperty("foo")).isNull();
-    verify(changeNotifier).onGlobalPropertyChange("foo", null);
-  }
-
-  @Test
-  public void getSettings_returns_delegate() {
-    assertThat(underTest.getSettings()).isSameAs(delegate);
-  }
-}
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/StartupMetadataPersisterTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/StartupMetadataPersisterTest.java
deleted file mode 100644 (file)
index 2307107..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.platform;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.utils.DateUtils;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
-import org.sonar.db.property.PropertyDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class StartupMetadataPersisterTest {
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private StartupMetadata metadata = new StartupMetadata(123_456_789L);
-  private StartupMetadataPersister underTest = new StartupMetadataPersister(metadata, dbTester.getDbClient());
-
-  @Test
-  public void persist_metadata_at_startup() {
-    underTest.start();
-
-    assertPersistedProperty(CoreProperties.SERVER_STARTTIME, DateUtils.formatDateTime(metadata.getStartedAt()));
-
-    underTest.stop();
-  }
-
-  private void assertPersistedProperty(String propertyKey, String expectedValue) {
-    PropertyDto prop = dbTester.getDbClient().propertiesDao().selectGlobalProperty(dbTester.getSession(), propertyKey);
-    assertThat(prop.getValue()).isEqualTo(expectedValue);
-  }
-}
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/DbConnectionSectionIT.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/DbConnectionSectionIT.java
new file mode 100644 (file)
index 0000000..f5f0e52
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.platform.monitoring;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.SonarQubeSide;
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+import org.sonar.server.platform.db.migration.version.DatabaseVersion;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
+
+public class DbConnectionSectionIT {
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private final DatabaseVersion databaseVersion = mock(DatabaseVersion.class);
+  private final SonarRuntime runtime = mock(SonarRuntime.class);
+  private final DbConnectionSection underTest = new DbConnectionSection(databaseVersion, dbTester.getDbClient(), runtime);
+
+  @Test
+  public void jmx_name_is_not_empty() {
+    assertThat(underTest.name()).isEqualTo("Database");
+  }
+
+  @Test
+  public void pool_info() {
+    ProtobufSystemInfo.Section section = underTest.toProtobuf();
+    assertThat(attribute(section, "Pool Total Connections").getLongValue()).isNotNegative();
+    assertThat(attribute(section, "Pool Active Connections").getLongValue()).isNotNegative();
+    assertThat(attribute(section, "Pool Idle Connections").getLongValue()).isNotNegative();
+    assertThat(attribute(section, "Pool Max Connections").getLongValue()).isNotNegative();
+    assertThat(attribute(section, "Pool Min Idle Connections")).isNotNull();
+    assertThat(attribute(section, "Pool Max Lifetime (ms)")).isNotNull();
+  }
+
+  @Test
+  public void section_name_depends_on_runtime_side() {
+    when(runtime.getSonarQubeSide()).thenReturn(SonarQubeSide.COMPUTE_ENGINE);
+    assertThat(underTest.toProtobuf().getName()).isEqualTo("Compute Engine Database Connection");
+
+    when(runtime.getSonarQubeSide()).thenReturn(SonarQubeSide.SERVER);
+    assertThat(underTest.toProtobuf().getName()).isEqualTo("Web Database Connection");
+  }
+}
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/DbConnectionSectionTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/DbConnectionSectionTest.java
deleted file mode 100644 (file)
index acc359b..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.platform.monitoring;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.SonarQubeSide;
-import org.sonar.api.SonarRuntime;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
-import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
-import org.sonar.server.platform.db.migration.version.DatabaseVersion;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.process.systeminfo.SystemInfoUtils.attribute;
-
-public class DbConnectionSectionTest {
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private final DatabaseVersion databaseVersion = mock(DatabaseVersion.class);
-  private final SonarRuntime runtime = mock(SonarRuntime.class);
-  private final DbConnectionSection underTest = new DbConnectionSection(databaseVersion, dbTester.getDbClient(), runtime);
-
-  @Test
-  public void jmx_name_is_not_empty() {
-    assertThat(underTest.name()).isEqualTo("Database");
-  }
-
-  @Test
-  public void pool_info() {
-    ProtobufSystemInfo.Section section = underTest.toProtobuf();
-    assertThat(attribute(section, "Pool Total Connections").getLongValue()).isNotNegative();
-    assertThat(attribute(section, "Pool Active Connections").getLongValue()).isNotNegative();
-    assertThat(attribute(section, "Pool Idle Connections").getLongValue()).isNotNegative();
-    assertThat(attribute(section, "Pool Max Connections").getLongValue()).isNotNegative();
-    assertThat(attribute(section, "Pool Min Idle Connections")).isNotNull();
-    assertThat(attribute(section, "Pool Max Lifetime (ms)")).isNotNull();
-  }
-
-  @Test
-  public void section_name_depends_on_runtime_side() {
-    when(runtime.getSonarQubeSide()).thenReturn(SonarQubeSide.COMPUTE_ENGINE);
-    assertThat(underTest.toProtobuf().getName()).isEqualTo("Compute Engine Database Connection");
-
-    when(runtime.getSonarQubeSide()).thenReturn(SonarQubeSide.SERVER);
-    assertThat(underTest.toProtobuf().getName()).isEqualTo("Web Database Connection");
-  }
-}
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/serverid/ServerIdManagerTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/serverid/ServerIdManagerTest.java
deleted file mode 100644 (file)
index 616f513..0000000
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.platform.serverid;
-
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
-import org.junit.After;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.SonarEdition;
-import org.sonar.api.SonarQubeSide;
-import org.sonar.api.internal.SonarRuntimeImpl;
-import org.sonar.api.utils.System2;
-import org.sonar.api.utils.Version;
-import org.sonar.core.platform.ServerId;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.property.PropertyDto;
-import org.sonar.server.platform.NodeInformation;
-import org.sonar.server.property.InternalProperties;
-
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.assertj.core.api.Assertions.fail;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.sonar.api.SonarQubeSide.COMPUTE_ENGINE;
-import static org.sonar.api.SonarQubeSide.SERVER;
-import static org.sonar.core.platform.ServerId.DATABASE_ID_LENGTH;
-import static org.sonar.core.platform.ServerId.NOT_UUID_DATASET_ID_LENGTH;
-import static org.sonar.core.platform.ServerId.UUID_DATASET_ID_LENGTH;
-
-@RunWith(DataProviderRunner.class)
-public class ServerIdManagerTest {
-
-  private static final ServerId WITH_DATABASE_ID_SERVER_ID = ServerId.of(randomAlphanumeric(DATABASE_ID_LENGTH), randomAlphanumeric(NOT_UUID_DATASET_ID_LENGTH));
-  private static final String CHECKSUM_1 = randomAlphanumeric(12);
-
-  @Rule
-  public final DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private final ServerIdChecksum serverIdChecksum = mock(ServerIdChecksum.class);
-  private final ServerIdFactory serverIdFactory = mock(ServerIdFactory.class);
-  private final DbClient dbClient = dbTester.getDbClient();
-  private final DbSession dbSession = dbTester.getSession();
-  private final NodeInformation nodeInformation = mock(NodeInformation.class);
-  private ServerIdManager underTest;
-
-  @After
-  public void tearDown() {
-    if (underTest != null) {
-      underTest.stop();
-    }
-  }
-
-  @Test
-  public void web_leader_persists_new_server_id_if_missing() {
-    mockCreateNewServerId();
-    mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
-    when(nodeInformation.isStartupLeader()).thenReturn(true);
-
-    test(SERVER);
-
-    verifyDb(CHECKSUM_1);
-    verifyCreateNewServerIdFromScratch();
-  }
-
-  @Test
-  public void web_leader_persists_new_server_id_if_value_is_empty() {
-    insertServerId("");
-    mockCreateNewServerId();
-    mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
-    when(nodeInformation.isStartupLeader()).thenReturn(true);
-
-    test(SERVER);
-
-    verifyDb(CHECKSUM_1);
-    verifyCreateNewServerIdFromScratch();
-  }
-
-  @Test
-  public void web_leader_keeps_existing_server_id_if_valid() {
-    insertServerId(WITH_DATABASE_ID_SERVER_ID);
-    insertChecksum(CHECKSUM_1);
-    mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
-    when(nodeInformation.isStartupLeader()).thenReturn(true);
-
-    test(SERVER);
-
-    verifyDb(CHECKSUM_1);
-  }
-
-  @Test
-  public void web_leader_creates_server_id_from_current_serverId_with_databaseId_if_checksum_fails() {
-    ServerId currentServerId = ServerId.of(randomAlphanumeric(DATABASE_ID_LENGTH), randomAlphanumeric(UUID_DATASET_ID_LENGTH));
-    insertServerId(currentServerId);
-    insertChecksum("does_not_match_WITH_DATABASE_ID_SERVER_ID");
-    mockChecksumOf(currentServerId, "matches_WITH_DATABASE_ID_SERVER_ID");
-    mockCreateNewServerIdFrom(currentServerId);
-    mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
-    when(nodeInformation.isStartupLeader()).thenReturn(true);
-
-    test(SERVER);
-
-    verifyDb(CHECKSUM_1);
-    verifyCreateNewServerIdFrom(currentServerId);
-  }
-
-  @Test
-  public void web_leader_generates_missing_checksum_for_current_serverId_with_databaseId() {
-    insertServerId(WITH_DATABASE_ID_SERVER_ID);
-    mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
-    when(nodeInformation.isStartupLeader()).thenReturn(true);
-
-    test(SERVER);
-
-    verifyDb(CHECKSUM_1);
-  }
-
-  @Test
-  public void web_follower_does_not_fail_if_server_id_matches_checksum() {
-    insertServerId(WITH_DATABASE_ID_SERVER_ID);
-    insertChecksum(CHECKSUM_1);
-    mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
-    when(nodeInformation.isStartupLeader()).thenReturn(false);
-
-    test(SERVER);
-
-    // no changes
-    verifyDb(CHECKSUM_1);
-  }
-
-  @Test
-  public void web_follower_fails_if_server_id_is_missing() {
-    when(nodeInformation.isStartupLeader()).thenReturn(false);
-
-    expectMissingServerIdException(() -> test(SERVER));
-  }
-
-  @Test
-  public void web_follower_fails_if_server_id_is_empty() {
-    insertServerId("");
-    when(nodeInformation.isStartupLeader()).thenReturn(false);
-
-    expectEmptyServerIdException(() -> test(SERVER));
-  }
-
-  @Test
-  public void web_follower_fails_if_checksum_does_not_match() {
-    String dbChecksum = "boom";
-    insertServerId(WITH_DATABASE_ID_SERVER_ID);
-    insertChecksum(dbChecksum);
-    mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
-    when(nodeInformation.isStartupLeader()).thenReturn(false);
-
-    try {
-      test(SERVER);
-      fail("An ISE should have been raised");
-    } catch (IllegalStateException e) {
-      assertThat(e.getMessage()).isEqualTo("Server ID is invalid");
-      // no changes
-      verifyDb(dbChecksum);
-    }
-  }
-
-  @Test
-  public void compute_engine_does_not_fail_if_server_id_is_valid() {
-    insertServerId(WITH_DATABASE_ID_SERVER_ID);
-    insertChecksum(CHECKSUM_1);
-    mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
-
-    test(COMPUTE_ENGINE);
-
-    // no changes
-    verifyDb(CHECKSUM_1);
-  }
-
-  @Test
-  public void compute_engine_fails_if_server_id_is_missing() {
-    expectMissingServerIdException(() -> test(COMPUTE_ENGINE));
-  }
-
-  @Test
-  public void compute_engine_fails_if_server_id_is_empty() {
-    insertServerId("");
-
-    expectEmptyServerIdException(() -> test(COMPUTE_ENGINE));
-  }
-
-  @Test
-  public void compute_engine_fails_if_server_id_is_invalid() {
-    String dbChecksum = "boom";
-    insertServerId(WITH_DATABASE_ID_SERVER_ID);
-    insertChecksum(dbChecksum);
-    mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1);
-
-    try {
-      test(SERVER);
-      fail("An ISE should have been raised");
-    } catch (IllegalStateException e) {
-      assertThat(e.getMessage()).isEqualTo("Server ID is invalid");
-      // no changes
-      verifyDb(dbChecksum);
-    }
-  }
-
-  private void expectEmptyServerIdException(ThrowingCallable callback) {
-    assertThatThrownBy(callback)
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Property sonar.core.id is empty in database");
-  }
-
-  private void expectMissingServerIdException(ThrowingCallable callback) {
-    assertThatThrownBy(callback)
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Property sonar.core.id is missing in database");
-  }
-
-  private void verifyDb(String expectedChecksum) {
-    assertThat(dbClient.propertiesDao().selectGlobalProperty(dbSession, CoreProperties.SERVER_ID))
-      .extracting(PropertyDto::getValue)
-      .isEqualTo(ServerIdManagerTest.WITH_DATABASE_ID_SERVER_ID.toString());
-    assertThat(dbClient.internalPropertiesDao().selectByKey(dbSession, InternalProperties.SERVER_ID_CHECKSUM))
-      .hasValue(expectedChecksum);
-  }
-
-  private void mockCreateNewServerId() {
-    when(serverIdFactory.create()).thenReturn(WITH_DATABASE_ID_SERVER_ID);
-    when(serverIdFactory.create(any())).thenThrow(new IllegalStateException("new ServerId should not be created from current server id"));
-  }
-
-  private void mockCreateNewServerIdFrom(ServerId currentServerId) {
-    when(serverIdFactory.create()).thenThrow(new IllegalStateException("new ServerId should be created from current server id"));
-    when(serverIdFactory.create(currentServerId)).thenReturn(ServerIdManagerTest.WITH_DATABASE_ID_SERVER_ID);
-  }
-
-  private void verifyCreateNewServerIdFromScratch() {
-    verify(serverIdFactory).create();
-  }
-
-  private void verifyCreateNewServerIdFrom(ServerId currentServerId) {
-    verify(serverIdFactory).create(currentServerId);
-  }
-
-  private void mockChecksumOf(ServerId serverId, String checksum1) {
-    when(serverIdChecksum.computeFor(serverId.toString())).thenReturn(checksum1);
-  }
-
-  private void insertServerId(ServerId serverId) {
-    insertServerId(serverId.toString());
-  }
-
-  private void insertServerId(String serverId) {
-    dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey(CoreProperties.SERVER_ID).setValue(serverId),
-      null, null, null, null);
-    dbSession.commit();
-  }
-
-  private void insertChecksum(String value) {
-    dbClient.internalPropertiesDao().save(dbSession, InternalProperties.SERVER_ID_CHECKSUM, value);
-    dbSession.commit();
-  }
-
-  private void test(SonarQubeSide side) {
-    underTest = new ServerIdManager(serverIdChecksum, serverIdFactory, dbClient, SonarRuntimeImpl
-      .forSonarQube(Version.create(6, 7), side, SonarEdition.COMMUNITY), nodeInformation);
-    underTest.start();
-  }
-}
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/RegisterMetricsTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/RegisterMetricsTest.java
deleted file mode 100644 (file)
index c66e2a6..0000000
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.startup;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.stream.IntStream;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.measures.CoreMetrics;
-import org.sonar.api.measures.Metric;
-import org.sonar.api.measures.Metrics;
-import org.sonar.api.utils.System2;
-import org.sonar.core.util.SequenceUuidFactory;
-import org.sonar.core.util.UuidFactory;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.db.metric.MetricDto;
-
-import static java.util.Arrays.asList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
-
-public class RegisterMetricsTest {
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private final UuidFactory uuidFactory = new SequenceUuidFactory();
-  private final DbClient dbClient = dbTester.getDbClient();
-  private final RegisterMetrics register = new RegisterMetrics(dbClient, uuidFactory);
-
-  /**
-   * Insert new metrics, including custom metrics
-   */
-  @Test
-  public void insert_new_metrics() {
-    Metric m1 = new Metric.Builder("m1", "One", Metric.ValueType.FLOAT)
-      .setDescription("desc1")
-      .setDirection(1)
-      .setQualitative(true)
-      .setDomain("domain1")
-      .setUserManaged(false)
-      .create();
-    Metric custom = new Metric.Builder("custom", "Custom", Metric.ValueType.FLOAT)
-      .setDescription("This is a custom metric")
-      .setUserManaged(true)
-      .create();
-
-    register.register(asList(m1, custom));
-
-    Map<String, MetricDto> metricsByKey = selectAllMetrics();
-    assertThat(metricsByKey).hasSize(2);
-    assertEquals(m1, metricsByKey.get("m1"));
-    assertEquals(custom, metricsByKey.get("custom"));
-  }
-
-  /**
-   * Update existing metrics
-   */
-  @Test
-  public void update_metrics() {
-    dbTester.measures().insertMetric(t -> t.setKey("m1")
-      .setShortName("name")
-      .setValueType(Metric.ValueType.INT.name())
-      .setDescription("old desc")
-      .setDomain("old domain")
-      .setShortName("old short name")
-      .setQualitative(false)
-      .setEnabled(true)
-      .setOptimizedBestValue(false)
-      .setDirection(1)
-      .setHidden(false));
-
-    Metric m1 = new Metric.Builder("m1", "New name", Metric.ValueType.FLOAT)
-      .setDescription("new description")
-      .setDirection(-1)
-      .setQualitative(true)
-      .setDomain("new domain")
-      .setUserManaged(false)
-      .setDecimalScale(3)
-      .setHidden(true)
-      .create();
-    register.register(asList(m1));
-
-    Map<String, MetricDto> metricsByKey = selectAllMetrics();
-    assertThat(metricsByKey).hasSize(1);
-    assertEquals(m1, metricsByKey.get("m1"));
-  }
-
-  @Test
-  public void disable_undefined_metrics() {
-    Random random = new Random();
-    int count = 1 + random.nextInt(10);
-    IntStream.range(0, count)
-      .forEach(t -> dbTester.measures().insertMetric(m -> m.setEnabled(random.nextBoolean())));
-
-    register.register(Collections.emptyList());
-
-    assertThat(selectAllMetrics().values().stream())
-      .extracting(MetricDto::isEnabled)
-      .containsOnly(IntStream.range(0, count).mapToObj(t -> false).toArray(Boolean[]::new));
-  }
-
-  @Test
-  public void enable_disabled_metrics() {
-    MetricDto enabledMetric = dbTester.measures().insertMetric(t -> t.setEnabled(true));
-    MetricDto disabledMetric = dbTester.measures().insertMetric(t -> t.setEnabled(false));
-
-    register.register(asList(builderOf(enabledMetric).create(), builderOf(disabledMetric).create()));
-
-    assertThat(selectAllMetrics().values())
-      .extracting(MetricDto::isEnabled)
-      .containsOnly(true, true);
-  }
-
-  @Test
-  public void insert_core_metrics() {
-    register.start();
-
-    assertThat(dbTester.countRowsOfTable("metrics")).isEqualTo(CoreMetrics.getMetrics().size());
-  }
-
-  @Test
-  public void fail_if_duplicated_plugin_metrics() {
-    Metrics plugin1 = new TestMetrics(new Metric.Builder("m1", "In first plugin", Metric.ValueType.FLOAT).create());
-    Metrics plugin2 = new TestMetrics(new Metric.Builder("m1", "In second plugin", Metric.ValueType.FLOAT).create());
-
-    assertThatThrownBy(() -> new RegisterMetrics(dbClient, uuidFactory, new Metrics[] {plugin1, plugin2}).start())
-      .isInstanceOf(IllegalStateException.class);
-  }
-
-  @Test
-  public void fail_if_plugin_duplicates_core_metric() {
-    Metrics plugin = new TestMetrics(new Metric.Builder("ncloc", "In plugin", Metric.ValueType.FLOAT).create());
-
-    assertThatThrownBy(() -> new RegisterMetrics(dbClient, uuidFactory, new Metrics[] {plugin}).start())
-      .isInstanceOf(IllegalStateException.class);
-  }
-
-  private static class TestMetrics implements Metrics {
-    private final List<Metric> metrics;
-
-    public TestMetrics(Metric... metrics) {
-      this.metrics = asList(metrics);
-    }
-
-    @Override
-    public List<Metric> getMetrics() {
-      return metrics;
-    }
-  }
-
-  private Map<String, MetricDto> selectAllMetrics() {
-    return dbTester.getDbClient().metricDao().selectAll(dbTester.getSession())
-      .stream()
-      .collect(uniqueIndex(MetricDto::getKey));
-  }
-
-  private void assertEquals(Metric expected, MetricDto actual) {
-    assertThat(actual.getKey()).isEqualTo(expected.getKey());
-    assertThat(actual.getShortName()).isEqualTo(expected.getName());
-    assertThat(actual.getValueType()).isEqualTo(expected.getType().name());
-    assertThat(actual.getDescription()).isEqualTo(expected.getDescription());
-    assertThat(actual.getDirection()).isEqualTo(expected.getDirection());
-    assertThat(actual.isQualitative()).isEqualTo(expected.getQualitative());
-  }
-
-  private static Metric.Builder builderOf(MetricDto enabledMetric) {
-    return new Metric.Builder(enabledMetric.getKey(), enabledMetric.getShortName(), Metric.ValueType.valueOf(enabledMetric.getValueType()))
-      .setDescription(enabledMetric.getDescription())
-      .setDirection(enabledMetric.getDirection())
-      .setQualitative(enabledMetric.isQualitative())
-      .setQualitative(enabledMetric.isQualitative())
-      .setDomain(enabledMetric.getDomain())
-      .setHidden(enabledMetric.isHidden());
-  }
-}
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/RegisterPluginsTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/RegisterPluginsTest.java
deleted file mode 100644 (file)
index c47aaef..0000000
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.startup;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.Map;
-import javax.annotation.Nullable;
-import org.apache.commons.io.FileUtils;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.api.utils.System2;
-import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.util.UuidFactory;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.db.plugin.PluginDto;
-import org.sonar.db.plugin.PluginDto.Type;
-import org.sonar.server.plugins.PluginFilesAndMd5;
-import org.sonar.core.plugin.PluginType;
-import org.sonar.server.plugins.ServerPlugin;
-import org.sonar.server.plugins.ServerPluginRepository;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
-
-public class RegisterPluginsTest {
-
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private final long now = 12345L;
-  private final DbClient dbClient = dbTester.getDbClient();
-  private final ServerPluginRepository serverPluginRepository = new ServerPluginRepository();
-  private final UuidFactory uuidFactory = mock(UuidFactory.class);
-  private final System2 system2 = mock(System2.class);
-  private final RegisterPlugins register = new RegisterPlugins(serverPluginRepository, dbClient, uuidFactory, system2);
-
-
-  @Before
-  public void setUp() {
-    when(system2.now()).thenReturn(now);
-  }
-
-  /**
-   * Insert new plugins
-   */
-  @Test
-  public void insert_new_plugins() throws IOException {
-    addPlugin("java", null);
-    addPlugin("javacustom", "java");
-    when(uuidFactory.create()).thenReturn("a").thenReturn("b").thenThrow(new IllegalStateException("Should be called only twice"));
-    register.start();
-
-    Map<String, PluginDto> pluginsByKey = selectAllPlugins();
-    assertThat(pluginsByKey).hasSize(2);
-    verify(pluginsByKey.get("java"), Type.BUNDLED, null, "93f725a07423fe1c889f448b33d21f46", false, now, now);
-    verify(pluginsByKey.get("javacustom"), Type.BUNDLED, "java", "bf8b3d4cb91efc5f9ef0bcd02f128ebf", false, now, now);
-
-    register.stop();
-  }
-
-  @Test
-  public void update_removed_plugins() throws IOException {
-    // will be flagged as removed
-    dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto()
-      .setUuid("a")
-      .setKee("java")
-      .setBasePluginKey(null)
-      .setFileHash("bd451e47a1aa76e73da0359cef63dd63")
-      .setType(Type.BUNDLED)
-      .setCreatedAt(1L)
-      .setUpdatedAt(1L));
-    // already flagged as removed
-    dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto()
-      .setUuid("b")
-      .setKee("java2")
-      .setBasePluginKey(null)
-      .setFileHash("bd451e47a1aa76e73da0359cef63dd63")
-      .setType(Type.BUNDLED)
-      .setRemoved(true)
-      .setCreatedAt(1L)
-      .setUpdatedAt(1L));
-    dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto()
-      .setUuid("c")
-      .setKee("csharp")
-      .setBasePluginKey(null)
-      .setFileHash("a20d785dbacb8f41a3b7392aa7d03b78")
-      .setType(Type.EXTERNAL)
-      .setCreatedAt(1L)
-      .setUpdatedAt(1L));
-    dbTester.commit();
-
-    addPlugin("csharp", PluginType.EXTERNAL, null);
-    register.start();
-
-    Map<String, PluginDto> pluginsByKey = selectAllPlugins();
-    assertThat(pluginsByKey).hasSize(3);
-    verify(pluginsByKey.get("java"), Type.BUNDLED, null, "bd451e47a1aa76e73da0359cef63dd63", true, 1L, now);
-    verify(pluginsByKey.get("java2"), Type.BUNDLED, null, "bd451e47a1aa76e73da0359cef63dd63", true, 1L, 1L);
-    verify(pluginsByKey.get("csharp"), Type.EXTERNAL, null, "a20d785dbacb8f41a3b7392aa7d03b78", false, 1L, 1L);
-  }
-
-  @Test
-  public void re_add_previously_removed_plugin() throws IOException {
-    dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto()
-      .setUuid("c")
-      .setKee("csharp")
-      .setBasePluginKey(null)
-      .setFileHash("a20d785dbacb8f41a3b7392aa7d03b78")
-      .setType(Type.EXTERNAL)
-      .setRemoved(true)
-      .setCreatedAt(1L)
-      .setUpdatedAt(1L));
-    dbTester.commit();
-
-    addPlugin("csharp", PluginType.EXTERNAL, null);
-    register.start();
-
-    Map<String, PluginDto> pluginsByKey = selectAllPlugins();
-    assertThat(pluginsByKey).hasSize(1);
-    verify(pluginsByKey.get("csharp"), Type.EXTERNAL, null, "a20d785dbacb8f41a3b7392aa7d03b78", false, 1L, now);
-  }
-
-  /**
-   * Update existing plugins, only when checksum is different and don't remove uninstalled plugins
-   */
-  @Test
-  public void update_changed_plugins() throws IOException {
-    dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto()
-      .setUuid("a")
-      .setKee("java")
-      .setBasePluginKey(null)
-      .setFileHash("bd451e47a1aa76e73da0359cef63dd63")
-      .setType(Type.BUNDLED)
-      .setCreatedAt(1L)
-      .setUpdatedAt(1L));
-    dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto()
-      .setUuid("b")
-      .setKee("javacustom")
-      .setBasePluginKey("java")
-      .setFileHash("de9b2de3ddc0680904939686c0dba5be")
-      .setType(Type.BUNDLED)
-      .setCreatedAt(1L)
-      .setUpdatedAt(1L));
-    dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto()
-      .setUuid("c")
-      .setKee("csharp")
-      .setBasePluginKey(null)
-      .setFileHash("a20d785dbacb8f41a3b7392aa7d03b78")
-      .setType(Type.EXTERNAL)
-      .setCreatedAt(1L)
-      .setUpdatedAt(1L));
-    dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto()
-      .setUuid("d")
-      .setKee("new-measures")
-      .setBasePluginKey(null)
-      .setFileHash("6d24712cf701c41ce5eaa948e0bd6d22")
-      .setType(Type.EXTERNAL)
-      .setCreatedAt(1L)
-      .setUpdatedAt(1L));
-
-    dbTester.commit();
-
-    addPlugin("javacustom", PluginType.BUNDLED, "java2");
-    // csharp plugin type changed
-    addPlugin("csharp", PluginType.BUNDLED, null);
-
-    register.start();
-
-    Map<String, PluginDto> pluginsByKey = selectAllPlugins();
-    assertThat(pluginsByKey).hasSize(4);
-    verify(pluginsByKey.get("java"), Type.BUNDLED, null, "bd451e47a1aa76e73da0359cef63dd63", true, 1L, now);
-    verify(pluginsByKey.get("javacustom"), Type.BUNDLED, "java2", "bf8b3d4cb91efc5f9ef0bcd02f128ebf", false, 1L, now);
-    verify(pluginsByKey.get("csharp"), Type.BUNDLED, null, "a20d785dbacb8f41a3b7392aa7d03b78", false, 1L, now);
-    verify(pluginsByKey.get("new-measures"), Type.EXTERNAL, null, "6d24712cf701c41ce5eaa948e0bd6d22", true, 1L, now);
-  }
-
-  private ServerPlugin addPlugin(String key, @Nullable String basePlugin) throws IOException {
-    return addPlugin(key, PluginType.BUNDLED, basePlugin);
-  }
-
-  private ServerPlugin addPlugin(String key, PluginType type, @Nullable String basePlugin) throws IOException {
-    File file = createPluginFile(key);
-    PluginFilesAndMd5.FileAndMd5 jar = new PluginFilesAndMd5.FileAndMd5(file);
-    PluginInfo info = new PluginInfo(key)
-      .setBasePlugin(basePlugin)
-      .setJarFile(file);
-    ServerPlugin serverPlugin = new ServerPlugin(info, type, null, jar, null);
-    serverPluginRepository.addPlugin(serverPlugin);
-    return serverPlugin;
-  }
-
-  private File createPluginFile(String key) throws IOException {
-    File pluginJar = temp.newFile();
-    FileUtils.write(pluginJar, key, StandardCharsets.UTF_8);
-    return pluginJar;
-  }
-
-  private Map<String, PluginDto> selectAllPlugins() {
-    return dbTester.getDbClient().pluginDao().selectAll(dbTester.getSession()).stream()
-      .collect(uniqueIndex(PluginDto::getKee));
-  }
-
-  private void verify(PluginDto pluginDto, Type type, @Nullable String basePluginKey, String fileHash, boolean removed, @Nullable Long createdAt, long updatedAt) {
-    assertThat(pluginDto.getBasePluginKey()).isEqualTo(basePluginKey);
-    assertThat(pluginDto.getType()).isEqualTo(type);
-    assertThat(pluginDto.getFileHash()).isEqualTo(fileHash);
-    assertThat(pluginDto.isRemoved()).isEqualTo(removed);
-    assertThat(pluginDto.getCreatedAt()).isEqualTo(createdAt);
-    assertThat(pluginDto.getUpdatedAt()).isEqualTo(updatedAt);
-  }
-
-}
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/UpgradeSuggestionsCleanerTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/UpgradeSuggestionsCleanerTest.java
deleted file mode 100644 (file)
index f82e082..0000000
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.startup;
-
-import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.sonar.api.SonarEdition;
-import org.sonar.api.SonarRuntime;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
-import org.sonar.db.ce.CeTaskMessageDto;
-import org.sonar.db.ce.CeTaskMessageType;
-import org.sonar.db.user.UserDismissedMessageDto;
-import org.sonar.db.user.UserDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-@RunWith(DataProviderRunner.class)
-public class UpgradeSuggestionsCleanerTest {
-  private static final String TASK_UUID = "b8d564dd-4ceb-4dba-8a3d-5fafa2d72cdf";
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private final SonarRuntime sonarRuntime = mock(SonarRuntime.class);
-  private final UpgradeSuggestionsCleaner underTest = new UpgradeSuggestionsCleaner(dbTester.getDbClient(), sonarRuntime);
-
-  private UserDto user;
-
-  @Before
-  public void setup() {
-    user = dbTester.users().insertUser();
-  }
-
-  @DataProvider
-  public static Object[][] editionsWithCleanup() {
-    return new Object[][] {
-      {SonarEdition.DEVELOPER},
-      {SonarEdition.ENTERPRISE},
-      {SonarEdition.DATACENTER}
-    };
-  }
-
-  @DataProvider
-  public static Object[][] allEditions() {
-    return new Object[][] {
-      {SonarEdition.COMMUNITY},
-      {SonarEdition.DEVELOPER},
-      {SonarEdition.ENTERPRISE},
-      {SonarEdition.DATACENTER}
-    };
-  }
-
-  @Test
-  @UseDataProvider("editionsWithCleanup")
-  public void start_cleans_up_obsolete_upgrade_suggestions(SonarEdition edition) {
-    when(sonarRuntime.getEdition()).thenReturn(edition);
-    insertCeTaskMessage("ctm1", CeTaskMessageType.GENERIC, "msg1");
-    insertCeTaskMessage("ctm2", CeTaskMessageType.GENERIC, "msg2");
-    insertCeTaskMessage("ctm3", CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE, "upgrade-msg-1");
-    insertInUserDismissedMessages("u1", CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE);
-    insertInUserDismissedMessages("u2", CeTaskMessageType.GENERIC);
-
-    underTest.start();
-    underTest.stop();
-
-    assertThat(dbTester.getDbClient().ceTaskMessageDao().selectByTask(dbTester.getSession(), TASK_UUID))
-      .extracting(CeTaskMessageDto::getUuid)
-      .containsExactly("ctm1", "ctm2");
-    assertThat(dbTester.getDbClient().userDismissedMessagesDao().selectByUser(dbTester.getSession(), user))
-      .extracting(UserDismissedMessageDto::getUuid)
-      .containsExactly("u2");
-  }
-
-  @Test
-  public void start_does_nothing_in_community_edition() {
-    when(sonarRuntime.getEdition()).thenReturn(SonarEdition.COMMUNITY);
-    insertCeTaskMessage("ctm1", CeTaskMessageType.GENERIC, "msg1");
-    insertCeTaskMessage("ctm2", CeTaskMessageType.GENERIC, "msg2");
-    insertCeTaskMessage("ctm3", CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE, "upgrade-msg-1");
-    insertInUserDismissedMessages("u1", CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE);
-    insertInUserDismissedMessages("u2", CeTaskMessageType.GENERIC);
-
-    underTest.start();
-
-    assertThat(dbTester.getDbClient().ceTaskMessageDao().selectByTask(dbTester.getSession(), TASK_UUID))
-      .extracting(CeTaskMessageDto::getUuid)
-      .containsExactly("ctm1", "ctm2", "ctm3");
-    assertThat(dbTester.getDbClient().userDismissedMessagesDao().selectByUser(dbTester.getSession(), user))
-      .extracting(UserDismissedMessageDto::getUuid)
-      .containsExactlyInAnyOrder("u1", "u2");
-  }
-
-  @Test
-  @UseDataProvider("allEditions")
-  public void start_does_nothing_when_no_suggest_upgrade_messages(SonarEdition edition) {
-    when(sonarRuntime.getEdition()).thenReturn(edition);
-
-    underTest.start();
-
-    assertThat(dbTester.getDbClient().ceTaskMessageDao().selectByTask(dbTester.getSession(), TASK_UUID)).isEmpty();
-    assertThat(dbTester.getDbClient().userDismissedMessagesDao().selectByUser(dbTester.getSession(), user)).isEmpty();
-  }
-
-  private void insertCeTaskMessage(String uuid, CeTaskMessageType messageType, String msg) {
-    CeTaskMessageDto dto = new CeTaskMessageDto()
-      .setUuid(uuid)
-      .setMessage(msg)
-      .setType(messageType)
-      .setTaskUuid(TASK_UUID);
-    dbTester.getDbClient().ceTaskMessageDao().insert(dbTester.getSession(), dto);
-    dbTester.getSession().commit();
-  }
-
-  private void insertInUserDismissedMessages(String uuid, CeTaskMessageType messageType) {
-    UserDismissedMessageDto dto = new UserDismissedMessageDto()
-      .setUuid(uuid)
-      .setUserUuid(user.getUuid())
-      .setProjectUuid("PROJECT_1")
-      .setCeMessageType(messageType);
-    dbTester.getDbClient().userDismissedMessagesDao().insert(dbTester.getSession(), dto);
-    dbTester.getSession().commit();
-  }
-}
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/webhook/WebhookQGChangeEventListenerTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/webhook/WebhookQGChangeEventListenerTest.java
deleted file mode 100644 (file)
index ba51b1b..0000000
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.webhook;
-
-import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Random;
-import java.util.Set;
-import java.util.function.Supplier;
-import javax.annotation.Nullable;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.measures.Metric;
-import org.sonar.api.utils.System2;
-import org.sonar.core.util.UuidFactoryFast;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.AnalysisPropertyDto;
-import org.sonar.db.component.BranchDto;
-import org.sonar.db.component.BranchType;
-import org.sonar.db.component.SnapshotDto;
-import org.sonar.db.project.ProjectDto;
-import org.sonar.server.qualitygate.EvaluatedQualityGate;
-import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
-import org.sonar.server.qualitygate.changeevent.QGChangeEventListener;
-
-import static java.util.Arrays.stream;
-import static java.util.Collections.emptySet;
-import static java.util.stream.Stream.concat;
-import static java.util.stream.Stream.of;
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoInteractions;
-import static org.mockito.Mockito.when;
-import static org.sonar.core.util.stream.MoreCollectors.toArrayList;
-import static org.sonar.db.component.BranchType.BRANCH;
-
-@RunWith(DataProviderRunner.class)
-public class WebhookQGChangeEventListenerTest {
-
-  private static final Set<QGChangeEventListener.ChangedIssue> CHANGED_ISSUES_ARE_IGNORED = emptySet();
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private DbClient dbClient = dbTester.getDbClient();
-
-  private EvaluatedQualityGate newQualityGate = mock(EvaluatedQualityGate.class);
-  private WebHooks webHooks = mock(WebHooks.class);
-  private WebhookPayloadFactory webhookPayloadFactory = mock(WebhookPayloadFactory.class);
-  private DbClient spiedOnDbClient = Mockito.spy(dbClient);
-  private WebhookQGChangeEventListener underTest = new WebhookQGChangeEventListener(webHooks, webhookPayloadFactory, spiedOnDbClient);
-  private DbClient mockedDbClient = mock(DbClient.class);
-  private WebhookQGChangeEventListener mockedUnderTest = new WebhookQGChangeEventListener(webHooks, webhookPayloadFactory, mockedDbClient);
-
-  @Test
-  @UseDataProvider("allCombinationsOfStatuses")
-  public void onIssueChanges_has_no_effect_if_no_webhook_is_configured(Metric.Level previousStatus, Metric.Level newStatus) {
-    Configuration configuration1 = mock(Configuration.class);
-    when(newQualityGate.getStatus()).thenReturn(newStatus);
-    QGChangeEvent qualityGateEvent = newQGChangeEvent(configuration1, previousStatus, newQualityGate);
-    mockWebhookDisabled(qualityGateEvent.getProject());
-
-    mockedUnderTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED);
-
-    verify(webHooks).isEnabled(qualityGateEvent.getProject());
-    verifyNoInteractions(webhookPayloadFactory, mockedDbClient);
-  }
-
-  @DataProvider
-  public static Object[][] allCombinationsOfStatuses() {
-    Metric.Level[] levelsAndNull = concat(of((Metric.Level) null), stream(Metric.Level.values()))
-      .toArray(Metric.Level[]::new);
-    Object[][] res = new Object[levelsAndNull.length * levelsAndNull.length][2];
-    int i = 0;
-    for (Metric.Level previousStatus : levelsAndNull) {
-      for (Metric.Level newStatus : levelsAndNull) {
-        res[i][0] = previousStatus;
-        res[i][1] = newStatus;
-        i++;
-      }
-    }
-    return res;
-  }
-
-  @Test
-  public void onIssueChanges_has_no_effect_if_event_has_neither_previousQGStatus_nor_qualityGate() {
-    Configuration configuration = mock(Configuration.class);
-    QGChangeEvent qualityGateEvent = newQGChangeEvent(configuration, null, null);
-    mockWebhookEnabled(qualityGateEvent.getProject());
-
-    underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED);
-
-    verifyNoInteractions(webhookPayloadFactory, mockedDbClient);
-  }
-
-  @Test
-  public void onIssueChanges_has_no_effect_if_event_has_same_status_in_previous_and_new_QG() {
-    Configuration configuration = mock(Configuration.class);
-    Metric.Level previousStatus = randomLevel();
-    when(newQualityGate.getStatus()).thenReturn(previousStatus);
-    QGChangeEvent qualityGateEvent = newQGChangeEvent(configuration, previousStatus, newQualityGate);
-    mockWebhookEnabled(qualityGateEvent.getProject());
-
-    underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED);
-
-    verifyNoInteractions(webhookPayloadFactory, mockedDbClient);
-  }
-
-  @Test
-  @UseDataProvider("newQGorNot")
-  public void onIssueChanges_calls_webhook_for_changeEvent_with_webhook_enabled(@Nullable EvaluatedQualityGate newQualityGate) {
-    ProjectAndBranch projectBranch = insertBranch(BRANCH, "foo");
-    SnapshotDto analysis = insertAnalysisTask(projectBranch);
-    Configuration configuration = mock(Configuration.class);
-    mockPayloadSupplierConsumedByWebhooks();
-    Map<String, String> properties = new HashMap<>();
-    properties.put("sonar.analysis.test1", randomAlphanumeric(50));
-    properties.put("sonar.analysis.test2", randomAlphanumeric(5000));
-    insertPropertiesFor(analysis.getUuid(), properties);
-    QGChangeEvent qualityGateEvent = newQGChangeEvent(projectBranch, analysis, configuration, newQualityGate);
-    mockWebhookEnabled(qualityGateEvent.getProject());
-
-    underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED);
-
-    ProjectAnalysis projectAnalysis = verifyWebhookCalledAndExtractPayloadFactoryArgument(projectBranch, analysis, qualityGateEvent.getProject());
-    assertThat(projectAnalysis).isEqualTo(
-      new ProjectAnalysis(
-        new Project(projectBranch.project.getUuid(), projectBranch.project.getKey(), projectBranch.project.getName()),
-        null,
-        new Analysis(analysis.getUuid(), analysis.getCreatedAt(), analysis.getRevision()),
-        new Branch(false, "foo", Branch.Type.BRANCH),
-        newQualityGate,
-        null,
-        properties));
-  }
-
-  @Test
-  @UseDataProvider("newQGorNot")
-  public void onIssueChanges_calls_webhook_on_main_branch(@Nullable EvaluatedQualityGate newQualityGate) {
-    ProjectAndBranch mainBranch = insertMainBranch();
-    SnapshotDto analysis = insertAnalysisTask(mainBranch);
-    Configuration configuration = mock(Configuration.class);
-    QGChangeEvent qualityGateEvent = newQGChangeEvent(mainBranch, analysis, configuration, newQualityGate);
-    mockWebhookEnabled(qualityGateEvent.getProject());
-
-    underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED);
-
-    verifyWebhookCalled(mainBranch, analysis, qualityGateEvent.getProject());
-  }
-
-  @Test
-  public void onIssueChanges_calls_webhook_on_branch() {
-    onIssueChangesCallsWebhookOnBranch(BRANCH);
-  }
-
-  @Test
-  public void onIssueChanges_calls_webhook_on_pr() {
-    onIssueChangesCallsWebhookOnBranch(BranchType.PULL_REQUEST);
-  }
-
-  public void onIssueChangesCallsWebhookOnBranch(BranchType branchType) {
-    ProjectAndBranch nonMainBranch = insertBranch(branchType, "foo");
-    SnapshotDto analysis = insertAnalysisTask(nonMainBranch);
-    Configuration configuration = mock(Configuration.class);
-    QGChangeEvent qualityGateEvent = newQGChangeEvent(nonMainBranch, analysis, configuration, null);
-    mockWebhookEnabled(qualityGateEvent.getProject());
-
-    underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED);
-
-    verifyWebhookCalled(nonMainBranch, analysis, qualityGateEvent.getProject());
-  }
-
-  @DataProvider
-  public static Object[][] newQGorNot() {
-    EvaluatedQualityGate newQualityGate = mock(EvaluatedQualityGate.class);
-    return new Object[][] {
-      {null},
-      {newQualityGate}
-    };
-  }
-
-  private void mockWebhookEnabled(ProjectDto... projects) {
-    for (ProjectDto dto : projects) {
-      when(webHooks.isEnabled(dto)).thenReturn(true);
-    }
-  }
-
-  private void mockWebhookDisabled(ProjectDto... projects) {
-    for (ProjectDto dto : projects) {
-      when(webHooks.isEnabled(dto)).thenReturn(false);
-    }
-  }
-
-  private void mockPayloadSupplierConsumedByWebhooks() {
-    Mockito.doAnswer(invocationOnMock -> {
-      Supplier<WebhookPayload> supplier = (Supplier<WebhookPayload>) invocationOnMock.getArguments()[1];
-      supplier.get();
-      return null;
-    }).when(webHooks)
-      .sendProjectAnalysisUpdate(any(), any());
-  }
-
-  private void insertPropertiesFor(String snapshotUuid, Map<String, String> properties) {
-    List<AnalysisPropertyDto> analysisProperties = properties.entrySet().stream()
-      .map(entry -> new AnalysisPropertyDto()
-        .setUuid(UuidFactoryFast.getInstance().create())
-        .setAnalysisUuid(snapshotUuid)
-        .setKey(entry.getKey())
-        .setValue(entry.getValue()))
-      .collect(toArrayList(properties.size()));
-    dbTester.getDbClient().analysisPropertiesDao().insert(dbTester.getSession(), analysisProperties);
-    dbTester.getSession().commit();
-  }
-
-  private SnapshotDto insertAnalysisTask(ProjectAndBranch projectAndBranch) {
-    return dbTester.components().insertSnapshot(projectAndBranch.getBranch());
-  }
-
-  private ProjectAnalysis verifyWebhookCalledAndExtractPayloadFactoryArgument(ProjectAndBranch projectAndBranch, SnapshotDto analysis, ProjectDto project) {
-    verifyWebhookCalled(projectAndBranch, analysis, project);
-
-    return extractPayloadFactoryArguments(1).iterator().next();
-  }
-
-  private void verifyWebhookCalled(ProjectAndBranch projectAndBranch, SnapshotDto analysis, ProjectDto project) {
-    verify(webHooks).isEnabled(project);
-    verify(webHooks).sendProjectAnalysisUpdate(
-      eq(new WebHooks.Analysis(projectAndBranch.uuid(), analysis.getUuid(), null)),
-      any());
-  }
-
-  private List<ProjectAnalysis> extractPayloadFactoryArguments(int time) {
-    ArgumentCaptor<ProjectAnalysis> projectAnalysisCaptor = ArgumentCaptor.forClass(ProjectAnalysis.class);
-    verify(webhookPayloadFactory, Mockito.times(time)).create(projectAnalysisCaptor.capture());
-    return projectAnalysisCaptor.getAllValues();
-  }
-
-  public ProjectAndBranch insertMainBranch() {
-    ProjectDto project = dbTester.components().insertPrivateProjectDto();
-    BranchDto branch = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), project.getUuid()).get();
-    dbTester.commit();
-    return new ProjectAndBranch(project, branch);
-  }
-
-  public ProjectAndBranch insertBranch(BranchType type, String branchKey) {
-    ProjectDto project = dbTester.components().insertPrivateProjectDto();
-    BranchDto branch = dbTester.components().insertProjectBranch(project, b -> b.setKey(branchKey).setBranchType(type));
-    return new ProjectAndBranch(project, branch);
-  }
-
-  public ProjectAndBranch insertBranch(ProjectDto project, BranchType type, String branchKey) {
-    BranchDto branch = dbTester.components().insertProjectBranch(project, b -> b.setKey(branchKey).setBranchType(type));
-    return new ProjectAndBranch(project, branch);
-  }
-
-  private static class ProjectAndBranch {
-    private final ProjectDto project;
-    private final BranchDto branch;
-
-    private ProjectAndBranch(ProjectDto project, BranchDto branch) {
-      this.project = project;
-      this.branch = branch;
-    }
-
-    public ProjectDto getProject() {
-      return project;
-    }
-
-    public BranchDto getBranch() {
-      return branch;
-    }
-
-    public String uuid() {
-      return project.getUuid();
-    }
-
-  }
-
-  private static QGChangeEvent newQGChangeEvent(Configuration configuration, @Nullable Metric.Level previousQQStatus, @Nullable EvaluatedQualityGate evaluatedQualityGate) {
-    return new QGChangeEvent(new ProjectDto(), new BranchDto(), new SnapshotDto(), configuration, previousQQStatus, () -> Optional.ofNullable(evaluatedQualityGate));
-  }
-
-  private static QGChangeEvent newQGChangeEvent(ProjectAndBranch branch, SnapshotDto analysis, Configuration configuration, @Nullable EvaluatedQualityGate evaluatedQualityGate) {
-    Metric.Level previousStatus = randomLevel();
-    if (evaluatedQualityGate != null) {
-      Metric.Level otherLevel = stream(Metric.Level.values())
-        .filter(s -> s != previousStatus)
-        .toArray(Metric.Level[]::new)[new Random().nextInt(Metric.Level.values().length - 1)];
-      when(evaluatedQualityGate.getStatus()).thenReturn(otherLevel);
-    }
-    return new QGChangeEvent(branch.project, branch.branch, analysis, configuration, previousStatus, () -> Optional.ofNullable(evaluatedQualityGate));
-  }
-
-  private static Metric.Level randomLevel() {
-    return Metric.Level.values()[new Random().nextInt(Metric.Level.values().length)];
-  }
-
-}
diff --git a/server/sonar-webserver-es/src/it/java/org/sonar/server/permission/index/PermissionIndexerDaoIT.java b/server/sonar-webserver-es/src/it/java/org/sonar/server/permission/index/PermissionIndexerDaoIT.java
new file mode 100644 (file)
index 0000000..021856d
--- /dev/null
@@ -0,0 +1,271 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.permission.index;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import org.assertj.core.api.Assertions;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.Uuids;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.permission.GroupPermissionDto;
+import org.sonar.db.user.GroupDto;
+import org.sonar.db.user.UserDbTester;
+import org.sonar.db.user.UserDto;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+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.api.web.UserRole.ADMIN;
+import static org.sonar.api.web.UserRole.USER;
+
+public class PermissionIndexerDaoIT {
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private final DbClient dbClient = dbTester.getDbClient();
+  private final DbSession dbSession = dbTester.getSession();
+  private final ComponentDbTester componentDbTester = new ComponentDbTester(dbTester);
+  private final UserDbTester userDbTester = new UserDbTester(dbTester);
+
+  private ComponentDto publicProject;
+  private ComponentDto privateProject1;
+  private ComponentDto privateProject2;
+  private ComponentDto view1;
+  private ComponentDto view2;
+  private ComponentDto application;
+  private UserDto user1;
+  private UserDto user2;
+  private GroupDto group;
+
+  private final PermissionIndexerDao underTest = new PermissionIndexerDao();
+
+  @Before
+  public void setUp() {
+    publicProject = componentDbTester.insertPublicProject();
+    privateProject1 = componentDbTester.insertPrivateProject();
+    privateProject2 = componentDbTester.insertPrivateProject();
+    view1 = componentDbTester.insertPublicPortfolio();
+    view2 = componentDbTester.insertPublicPortfolio();
+    application = componentDbTester.insertPublicApplication();
+    user1 = userDbTester.insertUser();
+    user2 = userDbTester.insertUser();
+    group = userDbTester.insertGroup();
+  }
+
+  @Test
+  public void select_all() {
+    insertTestDataForProjectsAndViews();
+
+    Collection<IndexPermissions> dtos = underTest.selectAll(dbClient, dbSession);
+    Assertions.assertThat(dtos).hasSize(6);
+
+    IndexPermissions publicProjectAuthorization = getByProjectUuid(publicProject.uuid(), dtos);
+    isPublic(publicProjectAuthorization, PROJECT);
+
+    IndexPermissions view1Authorization = getByProjectUuid(view1.uuid(), dtos);
+    isPublic(view1Authorization, VIEW);
+
+    IndexPermissions applicationAuthorization = getByProjectUuid(application.uuid(), dtos);
+    isPublic(applicationAuthorization, APP);
+
+    IndexPermissions privateProject1Authorization = getByProjectUuid(privateProject1.uuid(), dtos);
+    assertThat(privateProject1Authorization.getGroupUuids()).containsOnly(group.getUuid());
+    assertThat(privateProject1Authorization.isAllowAnyone()).isFalse();
+    assertThat(privateProject1Authorization.getUserUuids()).containsOnly(user1.getUuid(), user2.getUuid());
+    assertThat(privateProject1Authorization.getQualifier()).isEqualTo(PROJECT);
+
+    IndexPermissions privateProject2Authorization = getByProjectUuid(privateProject2.uuid(), dtos);
+    assertThat(privateProject2Authorization.getGroupUuids()).isEmpty();
+    assertThat(privateProject2Authorization.isAllowAnyone()).isFalse();
+    assertThat(privateProject2Authorization.getUserUuids()).containsOnly(user1.getUuid());
+    assertThat(privateProject2Authorization.getQualifier()).isEqualTo(PROJECT);
+
+    IndexPermissions view2Authorization = getByProjectUuid(view2.uuid(), dtos);
+    isPublic(view2Authorization, VIEW);
+  }
+
+  @Test
+  public void selectByUuids() {
+    insertTestDataForProjectsAndViews();
+
+    Map<String, IndexPermissions> dtos = underTest
+      .selectByUuids(dbClient, dbSession, asList(publicProject.uuid(), privateProject1.uuid(), privateProject2.uuid(), view1.uuid(), view2.uuid(), application.uuid()))
+      .stream()
+      .collect(MoreCollectors.uniqueIndex(IndexPermissions::getProjectUuid, Function.identity()));
+    Assertions.assertThat(dtos).hasSize(6);
+
+    IndexPermissions publicProjectAuthorization = dtos.get(publicProject.uuid());
+    isPublic(publicProjectAuthorization, PROJECT);
+
+    IndexPermissions view1Authorization = dtos.get(view1.uuid());
+    isPublic(view1Authorization, VIEW);
+
+    IndexPermissions applicationAuthorization = dtos.get(application.uuid());
+    isPublic(applicationAuthorization, APP);
+
+    IndexPermissions privateProject1Authorization = dtos.get(privateProject1.uuid());
+    assertThat(privateProject1Authorization.getGroupUuids()).containsOnly(group.getUuid());
+    assertThat(privateProject1Authorization.isAllowAnyone()).isFalse();
+    assertThat(privateProject1Authorization.getUserUuids()).containsOnly(user1.getUuid(), user2.getUuid());
+    assertThat(privateProject1Authorization.getQualifier()).isEqualTo(PROJECT);
+
+    IndexPermissions privateProject2Authorization = dtos.get(privateProject2.uuid());
+    assertThat(privateProject2Authorization.getGroupUuids()).isEmpty();
+    assertThat(privateProject2Authorization.isAllowAnyone()).isFalse();
+    assertThat(privateProject2Authorization.getUserUuids()).containsOnly(user1.getUuid());
+    assertThat(privateProject2Authorization.getQualifier()).isEqualTo(PROJECT);
+
+    IndexPermissions view2Authorization = dtos.get(view2.uuid());
+    isPublic(view2Authorization, VIEW);
+  }
+
+  @Test
+  public void selectByUuids_returns_empty_list_when_project_does_not_exist() {
+    insertTestDataForProjectsAndViews();
+
+    List<IndexPermissions> dtos = underTest.selectByUuids(dbClient, dbSession, singletonList("missing"));
+    Assertions.assertThat(dtos).isEmpty();
+  }
+
+  @Test
+  public void select_by_projects_with_high_number_of_projects() {
+    List<String> projectUuids = new ArrayList<>();
+    for (int i = 0; i < 3500; i++) {
+      ComponentDto project = dbTester.components().insertPrivateProject(Integer.toString(i));
+      projectUuids.add(project.uuid());
+      GroupPermissionDto dto = new GroupPermissionDto()
+        .setUuid(Uuids.createFast())
+        .setGroupUuid(group.getUuid())
+        .setGroupName(group.getName())
+        .setRole(USER)
+        .setComponentUuid(project.uuid())
+        .setComponentName(project.name());
+      dbClient.groupPermissionDao().insert(dbSession, dto, project, null);
+    }
+    dbSession.commit();
+
+    assertThat(underTest.selectByUuids(dbClient, dbSession, projectUuids))
+      .hasSize(3500)
+      .extracting(IndexPermissions::getProjectUuid)
+      .containsAll(projectUuids);
+  }
+
+  @Test
+  public void return_private_project_without_any_permission_when_no_permission_in_DB() {
+    List<IndexPermissions> dtos = underTest.selectByUuids(dbClient, dbSession, singletonList(privateProject1.uuid()));
+
+    // no permissions
+    Assertions.assertThat(dtos).hasSize(1);
+    IndexPermissions dto = dtos.get(0);
+    assertThat(dto.getGroupUuids()).isEmpty();
+    assertThat(dto.getUserUuids()).isEmpty();
+    assertThat(dto.isAllowAnyone()).isFalse();
+    assertThat(dto.getProjectUuid()).isEqualTo(privateProject1.uuid());
+    assertThat(dto.getQualifier()).isEqualTo(privateProject1.qualifier());
+  }
+
+  @Test
+  public void return_public_project_with_only_AllowAnyone_true_when_no_permission_in_DB() {
+    List<IndexPermissions> dtos = underTest.selectByUuids(dbClient, dbSession, singletonList(publicProject.uuid()));
+
+    Assertions.assertThat(dtos).hasSize(1);
+    IndexPermissions dto = dtos.get(0);
+    assertThat(dto.getGroupUuids()).isEmpty();
+    assertThat(dto.getUserUuids()).isEmpty();
+    assertThat(dto.isAllowAnyone()).isTrue();
+    assertThat(dto.getProjectUuid()).isEqualTo(publicProject.uuid());
+    assertThat(dto.getQualifier()).isEqualTo(publicProject.qualifier());
+  }
+
+  @Test
+  public void return_private_project_with_AllowAnyone_false_and_user_id_when_user_is_granted_USER_permission_directly() {
+    dbTester.users().insertProjectPermissionOnUser(user1, USER, privateProject1);
+    List<IndexPermissions> dtos = underTest.selectByUuids(dbClient, dbSession, singletonList(privateProject1.uuid()));
+
+    Assertions.assertThat(dtos).hasSize(1);
+    IndexPermissions dto = dtos.get(0);
+    assertThat(dto.getGroupUuids()).isEmpty();
+    assertThat(dto.getUserUuids()).containsOnly(user1.getUuid());
+    assertThat(dto.isAllowAnyone()).isFalse();
+    assertThat(dto.getProjectUuid()).isEqualTo(privateProject1.uuid());
+    assertThat(dto.getQualifier()).isEqualTo(privateProject1.qualifier());
+  }
+
+  @Test
+  public void return_private_project_with_AllowAnyone_false_and_group_id_but_not_user_id_when_user_is_granted_USER_permission_through_group() {
+    dbTester.users().insertMember(group, user1);
+    dbTester.users().insertProjectPermissionOnGroup(group, USER, privateProject1);
+    List<IndexPermissions> dtos = underTest.selectByUuids(dbClient, dbSession, singletonList(privateProject1.uuid()));
+
+    Assertions.assertThat(dtos).hasSize(1);
+    IndexPermissions dto = dtos.get(0);
+    assertThat(dto.getGroupUuids()).containsOnly(group.getUuid());
+    assertThat(dto.getUserUuids()).isEmpty();
+    assertThat(dto.isAllowAnyone()).isFalse();
+    assertThat(dto.getProjectUuid()).isEqualTo(privateProject1.uuid());
+    assertThat(dto.getQualifier()).isEqualTo(privateProject1.qualifier());
+  }
+
+  private void isPublic(IndexPermissions view1Authorization, String qualifier) {
+    assertThat(view1Authorization.getGroupUuids()).isEmpty();
+    assertThat(view1Authorization.isAllowAnyone()).isTrue();
+    assertThat(view1Authorization.getUserUuids()).isEmpty();
+    assertThat(view1Authorization.getQualifier()).isEqualTo(qualifier);
+  }
+
+  private static IndexPermissions getByProjectUuid(String projectUuid, Collection<IndexPermissions> dtos) {
+    return dtos.stream().filter(dto -> dto.getProjectUuid().equals(projectUuid)).findFirst().orElseThrow(IllegalArgumentException::new);
+  }
+
+  private void insertTestDataForProjectsAndViews() {
+    // user1 has USER access on both private projects
+    userDbTester.insertProjectPermissionOnUser(user1, ADMIN, publicProject);
+    userDbTester.insertProjectPermissionOnUser(user1, USER, privateProject1);
+    userDbTester.insertProjectPermissionOnUser(user1, USER, privateProject2);
+    userDbTester.insertProjectPermissionOnUser(user1, ADMIN, view1);
+    userDbTester.insertProjectPermissionOnUser(user1, ADMIN, application);
+
+    // user2 has USER access on privateProject1 only
+    userDbTester.insertProjectPermissionOnUser(user2, USER, privateProject1);
+    userDbTester.insertProjectPermissionOnUser(user2, ADMIN, privateProject2);
+
+    // group1 has USER access on privateProject1 only
+    userDbTester.insertProjectPermissionOnGroup(group, USER, privateProject1);
+    userDbTester.insertProjectPermissionOnGroup(group, ADMIN, privateProject1);
+    userDbTester.insertProjectPermissionOnGroup(group, ADMIN, view1);
+    userDbTester.insertProjectPermissionOnGroup(group, ADMIN, application);
+  }
+}
diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/permission/index/PermissionIndexerDaoTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/permission/index/PermissionIndexerDaoTest.java
deleted file mode 100644 (file)
index d847c5e..0000000
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.permission.index;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-import org.assertj.core.api.Assertions;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.core.util.Uuids;
-import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.permission.GroupPermissionDto;
-import org.sonar.db.user.GroupDto;
-import org.sonar.db.user.UserDbTester;
-import org.sonar.db.user.UserDto;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.assertThat;
-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.api.web.UserRole.ADMIN;
-import static org.sonar.api.web.UserRole.USER;
-
-public class PermissionIndexerDaoTest {
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private final DbClient dbClient = dbTester.getDbClient();
-  private final DbSession dbSession = dbTester.getSession();
-  private final ComponentDbTester componentDbTester = new ComponentDbTester(dbTester);
-  private final UserDbTester userDbTester = new UserDbTester(dbTester);
-
-  private ComponentDto publicProject;
-  private ComponentDto privateProject1;
-  private ComponentDto privateProject2;
-  private ComponentDto view1;
-  private ComponentDto view2;
-  private ComponentDto application;
-  private UserDto user1;
-  private UserDto user2;
-  private GroupDto group;
-
-  private final PermissionIndexerDao underTest = new PermissionIndexerDao();
-
-  @Before
-  public void setUp() {
-    publicProject = componentDbTester.insertPublicProject();
-    privateProject1 = componentDbTester.insertPrivateProject();
-    privateProject2 = componentDbTester.insertPrivateProject();
-    view1 = componentDbTester.insertPublicPortfolio();
-    view2 = componentDbTester.insertPublicPortfolio();
-    application = componentDbTester.insertPublicApplication();
-    user1 = userDbTester.insertUser();
-    user2 = userDbTester.insertUser();
-    group = userDbTester.insertGroup();
-  }
-
-  @Test
-  public void select_all() {
-    insertTestDataForProjectsAndViews();
-
-    Collection<IndexPermissions> dtos = underTest.selectAll(dbClient, dbSession);
-    Assertions.assertThat(dtos).hasSize(6);
-
-    IndexPermissions publicProjectAuthorization = getByProjectUuid(publicProject.uuid(), dtos);
-    isPublic(publicProjectAuthorization, PROJECT);
-
-    IndexPermissions view1Authorization = getByProjectUuid(view1.uuid(), dtos);
-    isPublic(view1Authorization, VIEW);
-
-    IndexPermissions applicationAuthorization = getByProjectUuid(application.uuid(), dtos);
-    isPublic(applicationAuthorization, APP);
-
-    IndexPermissions privateProject1Authorization = getByProjectUuid(privateProject1.uuid(), dtos);
-    assertThat(privateProject1Authorization.getGroupUuids()).containsOnly(group.getUuid());
-    assertThat(privateProject1Authorization.isAllowAnyone()).isFalse();
-    assertThat(privateProject1Authorization.getUserUuids()).containsOnly(user1.getUuid(), user2.getUuid());
-    assertThat(privateProject1Authorization.getQualifier()).isEqualTo(PROJECT);
-
-    IndexPermissions privateProject2Authorization = getByProjectUuid(privateProject2.uuid(), dtos);
-    assertThat(privateProject2Authorization.getGroupUuids()).isEmpty();
-    assertThat(privateProject2Authorization.isAllowAnyone()).isFalse();
-    assertThat(privateProject2Authorization.getUserUuids()).containsOnly(user1.getUuid());
-    assertThat(privateProject2Authorization.getQualifier()).isEqualTo(PROJECT);
-
-    IndexPermissions view2Authorization = getByProjectUuid(view2.uuid(), dtos);
-    isPublic(view2Authorization, VIEW);
-  }
-
-  @Test
-  public void selectByUuids() {
-    insertTestDataForProjectsAndViews();
-
-    Map<String, IndexPermissions> dtos = underTest
-      .selectByUuids(dbClient, dbSession, asList(publicProject.uuid(), privateProject1.uuid(), privateProject2.uuid(), view1.uuid(), view2.uuid(), application.uuid()))
-      .stream()
-      .collect(MoreCollectors.uniqueIndex(IndexPermissions::getProjectUuid, Function.identity()));
-    Assertions.assertThat(dtos).hasSize(6);
-
-    IndexPermissions publicProjectAuthorization = dtos.get(publicProject.uuid());
-    isPublic(publicProjectAuthorization, PROJECT);
-
-    IndexPermissions view1Authorization = dtos.get(view1.uuid());
-    isPublic(view1Authorization, VIEW);
-
-    IndexPermissions applicationAuthorization = dtos.get(application.uuid());
-    isPublic(applicationAuthorization, APP);
-
-    IndexPermissions privateProject1Authorization = dtos.get(privateProject1.uuid());
-    assertThat(privateProject1Authorization.getGroupUuids()).containsOnly(group.getUuid());
-    assertThat(privateProject1Authorization.isAllowAnyone()).isFalse();
-    assertThat(privateProject1Authorization.getUserUuids()).containsOnly(user1.getUuid(), user2.getUuid());
-    assertThat(privateProject1Authorization.getQualifier()).isEqualTo(PROJECT);
-
-    IndexPermissions privateProject2Authorization = dtos.get(privateProject2.uuid());
-    assertThat(privateProject2Authorization.getGroupUuids()).isEmpty();
-    assertThat(privateProject2Authorization.isAllowAnyone()).isFalse();
-    assertThat(privateProject2Authorization.getUserUuids()).containsOnly(user1.getUuid());
-    assertThat(privateProject2Authorization.getQualifier()).isEqualTo(PROJECT);
-
-    IndexPermissions view2Authorization = dtos.get(view2.uuid());
-    isPublic(view2Authorization, VIEW);
-  }
-
-  @Test
-  public void selectByUuids_returns_empty_list_when_project_does_not_exist() {
-    insertTestDataForProjectsAndViews();
-
-    List<IndexPermissions> dtos = underTest.selectByUuids(dbClient, dbSession, singletonList("missing"));
-    Assertions.assertThat(dtos).isEmpty();
-  }
-
-  @Test
-  public void select_by_projects_with_high_number_of_projects() {
-    List<String> projectUuids = new ArrayList<>();
-    for (int i = 0; i < 3500; i++) {
-      ComponentDto project = dbTester.components().insertPrivateProject(Integer.toString(i));
-      projectUuids.add(project.uuid());
-      GroupPermissionDto dto = new GroupPermissionDto()
-        .setUuid(Uuids.createFast())
-        .setGroupUuid(group.getUuid())
-        .setGroupName(group.getName())
-        .setRole(USER)
-        .setComponentUuid(project.uuid())
-        .setComponentName(project.name());
-      dbClient.groupPermissionDao().insert(dbSession, dto, project, null);
-    }
-    dbSession.commit();
-
-    assertThat(underTest.selectByUuids(dbClient, dbSession, projectUuids))
-      .hasSize(3500)
-      .extracting(IndexPermissions::getProjectUuid)
-      .containsAll(projectUuids);
-  }
-
-  @Test
-  public void return_private_project_without_any_permission_when_no_permission_in_DB() {
-    List<IndexPermissions> dtos = underTest.selectByUuids(dbClient, dbSession, singletonList(privateProject1.uuid()));
-
-    // no permissions
-    Assertions.assertThat(dtos).hasSize(1);
-    IndexPermissions dto = dtos.get(0);
-    assertThat(dto.getGroupUuids()).isEmpty();
-    assertThat(dto.getUserUuids()).isEmpty();
-    assertThat(dto.isAllowAnyone()).isFalse();
-    assertThat(dto.getProjectUuid()).isEqualTo(privateProject1.uuid());
-    assertThat(dto.getQualifier()).isEqualTo(privateProject1.qualifier());
-  }
-
-  @Test
-  public void return_public_project_with_only_AllowAnyone_true_when_no_permission_in_DB() {
-    List<IndexPermissions> dtos = underTest.selectByUuids(dbClient, dbSession, singletonList(publicProject.uuid()));
-
-    Assertions.assertThat(dtos).hasSize(1);
-    IndexPermissions dto = dtos.get(0);
-    assertThat(dto.getGroupUuids()).isEmpty();
-    assertThat(dto.getUserUuids()).isEmpty();
-    assertThat(dto.isAllowAnyone()).isTrue();
-    assertThat(dto.getProjectUuid()).isEqualTo(publicProject.uuid());
-    assertThat(dto.getQualifier()).isEqualTo(publicProject.qualifier());
-  }
-
-  @Test
-  public void return_private_project_with_AllowAnyone_false_and_user_id_when_user_is_granted_USER_permission_directly() {
-    dbTester.users().insertProjectPermissionOnUser(user1, USER, privateProject1);
-    List<IndexPermissions> dtos = underTest.selectByUuids(dbClient, dbSession, singletonList(privateProject1.uuid()));
-
-    Assertions.assertThat(dtos).hasSize(1);
-    IndexPermissions dto = dtos.get(0);
-    assertThat(dto.getGroupUuids()).isEmpty();
-    assertThat(dto.getUserUuids()).containsOnly(user1.getUuid());
-    assertThat(dto.isAllowAnyone()).isFalse();
-    assertThat(dto.getProjectUuid()).isEqualTo(privateProject1.uuid());
-    assertThat(dto.getQualifier()).isEqualTo(privateProject1.qualifier());
-  }
-
-  @Test
-  public void return_private_project_with_AllowAnyone_false_and_group_id_but_not_user_id_when_user_is_granted_USER_permission_through_group() {
-    dbTester.users().insertMember(group, user1);
-    dbTester.users().insertProjectPermissionOnGroup(group, USER, privateProject1);
-    List<IndexPermissions> dtos = underTest.selectByUuids(dbClient, dbSession, singletonList(privateProject1.uuid()));
-
-    Assertions.assertThat(dtos).hasSize(1);
-    IndexPermissions dto = dtos.get(0);
-    assertThat(dto.getGroupUuids()).containsOnly(group.getUuid());
-    assertThat(dto.getUserUuids()).isEmpty();
-    assertThat(dto.isAllowAnyone()).isFalse();
-    assertThat(dto.getProjectUuid()).isEqualTo(privateProject1.uuid());
-    assertThat(dto.getQualifier()).isEqualTo(privateProject1.qualifier());
-  }
-
-  private void isPublic(IndexPermissions view1Authorization, String qualifier) {
-    assertThat(view1Authorization.getGroupUuids()).isEmpty();
-    assertThat(view1Authorization.isAllowAnyone()).isTrue();
-    assertThat(view1Authorization.getUserUuids()).isEmpty();
-    assertThat(view1Authorization.getQualifier()).isEqualTo(qualifier);
-  }
-
-  private static IndexPermissions getByProjectUuid(String projectUuid, Collection<IndexPermissions> dtos) {
-    return dtos.stream().filter(dto -> dto.getProjectUuid().equals(projectUuid)).findFirst().orElseThrow(IllegalArgumentException::new);
-  }
-
-  private void insertTestDataForProjectsAndViews() {
-    // user1 has USER access on both private projects
-    userDbTester.insertProjectPermissionOnUser(user1, ADMIN, publicProject);
-    userDbTester.insertProjectPermissionOnUser(user1, USER, privateProject1);
-    userDbTester.insertProjectPermissionOnUser(user1, USER, privateProject2);
-    userDbTester.insertProjectPermissionOnUser(user1, ADMIN, view1);
-    userDbTester.insertProjectPermissionOnUser(user1, ADMIN, application);
-
-    // user2 has USER access on privateProject1 only
-    userDbTester.insertProjectPermissionOnUser(user2, USER, privateProject1);
-    userDbTester.insertProjectPermissionOnUser(user2, ADMIN, privateProject2);
-
-    // group1 has USER access on privateProject1 only
-    userDbTester.insertProjectPermissionOnGroup(group, USER, privateProject1);
-    userDbTester.insertProjectPermissionOnGroup(group, ADMIN, privateProject1);
-    userDbTester.insertProjectPermissionOnGroup(group, ADMIN, view1);
-    userDbTester.insertProjectPermissionOnGroup(group, ADMIN, application);
-  }
-}
diff --git a/server/sonar-webserver-pushapi/src/it/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerAndExecutorIT.java b/server/sonar-webserver-pushapi/src/it/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerAndExecutorIT.java
new file mode 100644 (file)
index 0000000..0600d51
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.pushapi.scheduler.purge;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.pushevent.PushEventDto;
+import org.sonar.server.util.GlobalLockManagerImpl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.server.pushapi.scheduler.purge.PushEventsPurgeScheduler.ENQUEUE_DELAY_IN_SECONDS;
+import static org.sonar.server.pushapi.scheduler.purge.PushEventsPurgeScheduler.INITIAL_DELAY_IN_SECONDS;
+
+public class PushEventsPurgeSchedulerAndExecutorIT {
+
+  private static final String PUSH_EVENT_UUID = "push_event_uuid";
+  private static final long INITIAL_AND_ENQUE_DELAY_MS = 1L;
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private PushEventsPurgeScheduler pushEventsPurgeScheduler;
+
+  @Before
+  public void setup() {
+    Configuration configuration = mock(Configuration.class);
+    when(configuration.getLong(INITIAL_DELAY_IN_SECONDS)).thenReturn(Optional.of(INITIAL_AND_ENQUE_DELAY_MS));
+    when(configuration.getLong(ENQUEUE_DELAY_IN_SECONDS)).thenReturn(Optional.of(INITIAL_AND_ENQUE_DELAY_MS));
+
+    GlobalLockManagerImpl lockManager = mock(GlobalLockManagerImpl.class);
+    when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
+
+    DbClient dbClient = dbTester.getDbClient();
+    pushEventsPurgeScheduler = new PushEventsPurgeScheduler(
+      dbClient,
+      configuration,
+      lockManager,
+      new PushEventsPurgeExecutorServiceImpl(),
+      System2.INSTANCE
+    );
+  }
+
+  @Test
+  public void pushEventsPurgeScheduler_shouldCleanUpPeriodically() throws InterruptedException {
+    insertOldPushEvent();
+    assertThat(pushEventIsCleanedUp()).isFalse();
+
+    pushEventsPurgeScheduler.start();
+
+    await()
+      .atMost(3, TimeUnit.SECONDS)
+      .pollDelay(500, TimeUnit.MILLISECONDS)
+      .until(this::pushEventIsCleanedUp);
+  }
+
+  private void insertOldPushEvent() {
+    PushEventDto pushEventDto = new PushEventDto();
+    pushEventDto.setUuid(PUSH_EVENT_UUID);
+    pushEventDto.setName("test_event");
+    pushEventDto.setProjectUuid("test_project_uuid");
+    pushEventDto.setPayload("payload".getBytes(StandardCharsets.UTF_8));
+    pushEventDto.setCreatedAt(1656633600);
+    dbTester.getDbClient().pushEventDao().insert(dbTester.getSession(), pushEventDto);
+    dbTester.commit();
+  }
+
+  private boolean pushEventIsCleanedUp() {
+    return dbTester.getDbClient().pushEventDao().selectByUuid(dbTester.getSession(), PUSH_EVENT_UUID) == null;
+  }
+
+}
diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerAndExecutorTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerAndExecutorTest.java
deleted file mode 100644 (file)
index e82fc3c..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.pushapi.scheduler.purge;
-
-import java.nio.charset.StandardCharsets;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.db.pushevent.PushEventDto;
-import org.sonar.server.util.GlobalLockManagerImpl;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.awaitility.Awaitility.await;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.server.pushapi.scheduler.purge.PushEventsPurgeScheduler.ENQUEUE_DELAY_IN_SECONDS;
-import static org.sonar.server.pushapi.scheduler.purge.PushEventsPurgeScheduler.INITIAL_DELAY_IN_SECONDS;
-
-public class PushEventsPurgeSchedulerAndExecutorTest {
-
-  private static final String PUSH_EVENT_UUID = "push_event_uuid";
-  private static final long INITIAL_AND_ENQUE_DELAY_MS = 1L;
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private PushEventsPurgeScheduler pushEventsPurgeScheduler;
-
-  @Before
-  public void setup() {
-    Configuration configuration = mock(Configuration.class);
-    when(configuration.getLong(INITIAL_DELAY_IN_SECONDS)).thenReturn(Optional.of(INITIAL_AND_ENQUE_DELAY_MS));
-    when(configuration.getLong(ENQUEUE_DELAY_IN_SECONDS)).thenReturn(Optional.of(INITIAL_AND_ENQUE_DELAY_MS));
-
-    GlobalLockManagerImpl lockManager = mock(GlobalLockManagerImpl.class);
-    when(lockManager.tryLock(any(), anyInt())).thenReturn(true);
-
-    DbClient dbClient = dbTester.getDbClient();
-    pushEventsPurgeScheduler = new PushEventsPurgeScheduler(
-      dbClient,
-      configuration,
-      lockManager,
-      new PushEventsPurgeExecutorServiceImpl(),
-      System2.INSTANCE
-    );
-  }
-
-  @Test
-  public void pushEventsPurgeScheduler_shouldCleanUpPeriodically() throws InterruptedException {
-    insertOldPushEvent();
-    assertThat(pushEventIsCleanedUp()).isFalse();
-
-    pushEventsPurgeScheduler.start();
-
-    await()
-      .atMost(3, TimeUnit.SECONDS)
-      .pollDelay(500, TimeUnit.MILLISECONDS)
-      .until(this::pushEventIsCleanedUp);
-  }
-
-  private void insertOldPushEvent() {
-    PushEventDto pushEventDto = new PushEventDto();
-    pushEventDto.setUuid(PUSH_EVENT_UUID);
-    pushEventDto.setName("test_event");
-    pushEventDto.setProjectUuid("test_project_uuid");
-    pushEventDto.setPayload("payload".getBytes(StandardCharsets.UTF_8));
-    pushEventDto.setCreatedAt(1656633600);
-    dbTester.getDbClient().pushEventDao().insert(dbTester.getSession(), pushEventDto);
-    dbTester.commit();
-  }
-
-  private boolean pushEventIsCleanedUp() {
-    return dbTester.getDbClient().pushEventDao().selectByUuid(dbTester.getSession(), PUSH_EVENT_UUID) == null;
-  }
-
-}
diff --git a/server/sonar-webserver/src/it/java/org/sonar/server/platform/web/SonarLintConnectionFilterIT.java b/server/sonar-webserver/src/it/java/org/sonar/server/platform/web/SonarLintConnectionFilterIT.java
new file mode 100644 (file)
index 0000000..45f4be0
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.platform.web;
+
+import java.io.IOException;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.impl.utils.TestSystem2;
+import org.sonar.db.DbTester;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.user.ServerUserSession;
+import org.sonar.server.user.ThreadLocalUserSession;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class SonarLintConnectionFilterIT {
+  private static final String LOGIN = "user1";
+  private final TestSystem2 system2 = new TestSystem2();
+
+  @Rule
+  public DbTester dbTester = DbTester.create(system2);
+
+  @Test
+  public void update() throws IOException, ServletException {
+    system2.setNow(10_000_000L);
+    addUser(LOGIN, 1_000_000L);
+
+    runFilter(LOGIN, "SonarLint for IntelliJ");
+    assertThat(getLastUpdate(LOGIN)).isEqualTo(10_000_000L);
+  }
+
+  @Test
+  public void update_first_time() throws IOException, ServletException {
+    system2.setNow(10_000_000L);
+    addUser(LOGIN, null);
+
+    runFilter(LOGIN, "SonarLint for IntelliJ");
+    assertThat(getLastUpdate(LOGIN)).isEqualTo(10_000_000L);
+  }
+
+  @Test
+  public void only_applies_to_api() {
+    SonarLintConnectionFilter underTest = new SonarLintConnectionFilter(dbTester.getDbClient(), mock(ThreadLocalUserSession.class), system2);
+    assertThat(underTest.doGetPattern().matches("/api/test")).isTrue();
+    assertThat(underTest.doGetPattern().matches("/test")).isFalse();
+
+  }
+
+  @Test
+  public void do_nothing_if_no_sonarlint_agent() throws IOException, ServletException {
+    system2.setNow(10_000L);
+    addUser(LOGIN, 1_000L);
+
+    runFilter(LOGIN, "unknown");
+    runFilter(LOGIN, null);
+    assertThat(getLastUpdate(LOGIN)).isEqualTo(1_000L);
+  }
+
+  @Test
+  public void do_nothing_if_not_logged_in() throws IOException, ServletException {
+    system2.setNow(10_000_000L);
+    addUser("invalid", 1_000_000L);
+
+    runFilter(LOGIN, "SonarLint for IntelliJ");
+    assertThat(getLastUpdate("invalid")).isEqualTo(1_000_000L);
+  }
+
+  @Test
+  public void dont_fail_if_no_user_set() throws IOException, ServletException {
+    SonarLintConnectionFilter underTest = new SonarLintConnectionFilter(dbTester.getDbClient(), new ThreadLocalUserSession(), system2);
+    HttpServletRequest request = mock(HttpServletRequest.class);
+    when(request.getHeader("User-Agent")).thenReturn("sonarlint");
+    FilterChain chain = mock(FilterChain.class);
+    underTest.doFilter(request, mock(ServletResponse.class), chain);
+    verify(chain).doFilter(any(), any());
+  }
+
+  @Test
+  public void only_update_if_not_updated_within_1h() throws IOException, ServletException {
+    system2.setNow(2_000_000L);
+    addUser(LOGIN, 1_000_000L);
+
+    runFilter(LOGIN, "SonarLint for IntelliJ");
+    assertThat(getLastUpdate(LOGIN)).isEqualTo(1_000_000L);
+  }
+
+  private void addUser(String login, @Nullable Long lastUpdate) {
+    dbTester.users().insertUser(u -> u.setLogin(login).setLastSonarlintConnectionDate(lastUpdate));
+  }
+
+  @CheckForNull
+  private Long getLastUpdate(String login) {
+    return dbTester.getDbClient().userDao().selectByLogin(dbTester.getSession(), login).getLastSonarlintConnectionDate();
+  }
+
+  private void runFilter(String loggedInUser, @Nullable String agent) throws IOException, ServletException {
+    UserDto user = dbTester.getDbClient().userDao().selectByLogin(dbTester.getSession(), loggedInUser);
+    ThreadLocalUserSession session = new ThreadLocalUserSession();
+    session.set(new ServerUserSession(dbTester.getDbClient(), user));
+    SonarLintConnectionFilter underTest = new SonarLintConnectionFilter(dbTester.getDbClient(), session, system2);
+    HttpServletRequest request = mock(HttpServletRequest.class);
+    when(request.getHeader("User-Agent")).thenReturn(agent);
+    FilterChain chain = mock(FilterChain.class);
+    underTest.doFilter(request, mock(ServletResponse.class), chain);
+    verify(chain).doFilter(any(), any());
+  }
+}
diff --git a/server/sonar-webserver/src/test/java/org/sonar/server/platform/web/SonarLintConnectionFilterTest.java b/server/sonar-webserver/src/test/java/org/sonar/server/platform/web/SonarLintConnectionFilterTest.java
deleted file mode 100644 (file)
index fd08c97..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.platform.web;
-
-import java.io.IOException;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.impl.utils.TestSystem2;
-import org.sonar.db.DbTester;
-import org.sonar.db.user.UserDto;
-import org.sonar.server.user.ServerUserSession;
-import org.sonar.server.user.ThreadLocalUserSession;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class SonarLintConnectionFilterTest {
-  private static final String LOGIN = "user1";
-  private final TestSystem2 system2 = new TestSystem2();
-
-  @Rule
-  public DbTester dbTester = DbTester.create(system2);
-
-  @Test
-  public void update() throws IOException, ServletException {
-    system2.setNow(10_000_000L);
-    addUser(LOGIN, 1_000_000L);
-
-    runFilter(LOGIN, "SonarLint for IntelliJ");
-    assertThat(getLastUpdate(LOGIN)).isEqualTo(10_000_000L);
-  }
-
-  @Test
-  public void update_first_time() throws IOException, ServletException {
-    system2.setNow(10_000_000L);
-    addUser(LOGIN, null);
-
-    runFilter(LOGIN, "SonarLint for IntelliJ");
-    assertThat(getLastUpdate(LOGIN)).isEqualTo(10_000_000L);
-  }
-
-  @Test
-  public void only_applies_to_api() {
-    SonarLintConnectionFilter underTest = new SonarLintConnectionFilter(dbTester.getDbClient(), mock(ThreadLocalUserSession.class), system2);
-    assertThat(underTest.doGetPattern().matches("/api/test")).isTrue();
-    assertThat(underTest.doGetPattern().matches("/test")).isFalse();
-
-  }
-
-  @Test
-  public void do_nothing_if_no_sonarlint_agent() throws IOException, ServletException {
-    system2.setNow(10_000L);
-    addUser(LOGIN, 1_000L);
-
-    runFilter(LOGIN, "unknown");
-    runFilter(LOGIN, null);
-    assertThat(getLastUpdate(LOGIN)).isEqualTo(1_000L);
-  }
-
-  @Test
-  public void do_nothing_if_not_logged_in() throws IOException, ServletException {
-    system2.setNow(10_000_000L);
-    addUser("invalid", 1_000_000L);
-
-    runFilter(LOGIN, "SonarLint for IntelliJ");
-    assertThat(getLastUpdate("invalid")).isEqualTo(1_000_000L);
-  }
-
-  @Test
-  public void dont_fail_if_no_user_set() throws IOException, ServletException {
-    SonarLintConnectionFilter underTest = new SonarLintConnectionFilter(dbTester.getDbClient(), new ThreadLocalUserSession(), system2);
-    HttpServletRequest request = mock(HttpServletRequest.class);
-    when(request.getHeader("User-Agent")).thenReturn("sonarlint");
-    FilterChain chain = mock(FilterChain.class);
-    underTest.doFilter(request, mock(ServletResponse.class), chain);
-    verify(chain).doFilter(any(), any());
-  }
-
-  @Test
-  public void only_update_if_not_updated_within_1h() throws IOException, ServletException {
-    system2.setNow(2_000_000L);
-    addUser(LOGIN, 1_000_000L);
-
-    runFilter(LOGIN, "SonarLint for IntelliJ");
-    assertThat(getLastUpdate(LOGIN)).isEqualTo(1_000_000L);
-  }
-
-  private void addUser(String login, @Nullable Long lastUpdate) {
-    dbTester.users().insertUser(u -> u.setLogin(login).setLastSonarlintConnectionDate(lastUpdate));
-  }
-
-  @CheckForNull
-  private Long getLastUpdate(String login) {
-    return dbTester.getDbClient().userDao().selectByLogin(dbTester.getSession(), login).getLastSonarlintConnectionDate();
-  }
-
-  private void runFilter(String loggedInUser, @Nullable String agent) throws IOException, ServletException {
-    UserDto user = dbTester.getDbClient().userDao().selectByLogin(dbTester.getSession(), loggedInUser);
-    ThreadLocalUserSession session = new ThreadLocalUserSession();
-    session.set(new ServerUserSession(dbTester.getDbClient(), user));
-    SonarLintConnectionFilter underTest = new SonarLintConnectionFilter(dbTester.getDbClient(), session, system2);
-    HttpServletRequest request = mock(HttpServletRequest.class);
-    when(request.getHeader("User-Agent")).thenReturn(agent);
-    FilterChain chain = mock(FilterChain.class);
-    underTest.doFilter(request, mock(ServletResponse.class), chain);
-    verify(chain).doFilter(any(), any());
-  }
-}