]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18679 update UTs to ITs sonar-ce
authorLéo Geoffroy <leo.geoffroy@sonarsource.com>
Wed, 15 Mar 2023 09:39:13 +0000 (10:39 +0100)
committerPhilippe Perrin <philippe.perrin@sonarsource.com>
Fri, 17 Mar 2023 09:08:02 +0000 (10:08 +0100)
144 files changed:
server/sonar-ce-common/src/it/java/org/sonar/ce/queue/CeQueueImplIT.java [new file with mode: 0644]
server/sonar-ce-common/src/test/java/org/sonar/ce/queue/CeQueueImplTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactoryImplIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/component/ProjectPersisterIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/component/ReferenceBranchComponentUuidsIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/component/SiblingComponentsWithOpenIssuesIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/AdHocRuleCreatorIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/ClosedIssuesInputFactoryIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesLoaderIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/DefaultAssigneeIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/ProjectTrackerBaseLazyInputIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/ScmAccountToUserLoaderIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMergerIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/SourceBranchComponentUuidsIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/TargetBranchComponentUuidsIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/TrackerReferenceBranchInputFactoryIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/TrackerSourceBranchInputFactoryIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/TrackerTargetBranchInputFactoryIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/measure/MeasureRepositoryImplIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/period/NewCodeReferenceBranchComponentUuidsIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/source/DbLineHashVersionIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/source/PersistFileSourcesStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/CleanIssueChangesStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/EnableAnalysisStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ExtractReportStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/IndexAnalysisStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/LoadFileHashesAndStatusStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistAdHocRulesStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistDuplicationDataStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistLiveMeasuresStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistMeasuresStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistProjectLinksStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistPushEventsStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistScannerAnalysisCacheStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistScannerContextStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ReportPersistAnalysisStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ReportPersistComponentsStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/SendIssueNotificationsStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/UpdateNeedIssueSyncStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/UpdateQualityProfilesLastUsedDateStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ValidateProjectStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ViewsPersistAnalysisStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ViewsPersistComponentsStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/taskprocessor/AuditHousekeepingFrequencyHelperIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/taskprocessor/AuditPurgeStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/taskprocessor/IndexIssuesStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/analysis/ExportAnalysesStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/branches/ExportBranchesStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/file/ExportLineHashesStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/issue/ExportIssuesChangelogStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/issue/ExportIssuesStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/rule/ExportAdHocRulesStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/ExportEventsStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/ExportLinksStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/ExportLiveMeasuresStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/ExportMetricsStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/ExportNewCodePeriodsStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/ExportSettingsStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/LoadProjectStepIT.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/step/ExtractReportStepIT/zip-bomb.zip [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectexport/issue/rubbish_data.txt [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactoryImplTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ProjectPersisterTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ReferenceBranchComponentUuidsTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/SiblingComponentsWithOpenIssuesTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/AdHocRuleCreatorTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ClosedIssuesInputFactoryTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesLoaderTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/DefaultAssigneeTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ProjectTrackerBaseLazyInputTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ScmAccountToUserLoaderTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMergerTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/SourceBranchComponentUuidsTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TargetBranchComponentUuidsTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerReferenceBranchInputFactoryTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerSourceBranchInputFactoryTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerTargetBranchInputFactoryTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/measure/MeasureRepositoryImplTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/period/NewCodeReferenceBranchComponentUuidsTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/source/DbLineHashVersionTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/source/PersistFileSourcesStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/CleanIssueChangesStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/EnableAnalysisStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ExtractReportStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/IndexAnalysisStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadFileHashesAndStatusStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistAdHocRulesStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistDuplicationDataStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistLiveMeasuresStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistMeasuresStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistProjectLinksStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistPushEventsStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistScannerAnalysisCacheStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistScannerContextStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ReportPersistAnalysisStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ReportPersistComponentsStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/SendIssueNotificationsStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/UpdateNeedIssueSyncStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/UpdateQualityProfilesLastUsedDateStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ValidateProjectStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ViewsPersistAnalysisStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ViewsPersistComponentsStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/taskprocessor/AuditHousekeepingFrequencyHelperTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/taskprocessor/AuditPurgeStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/taskprocessor/IndexIssuesStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/analysis/ExportAnalysesStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/branches/ExportBranchesStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/file/ExportLineHashesStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/issue/ExportIssuesChangelogStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/issue/ExportIssuesStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/rule/ExportAdHocRulesStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/ExportEventsStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/ExportLinksStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/ExportLiveMeasuresStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/ExportMetricsStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/ExportNewCodePeriodsStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/ExportSettingsStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/LoadProjectStepTest.java [deleted file]
server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/step/ExtractReportStepTest/zip-bomb.zip [deleted file]
server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectexport/issue/rubbish_data.txt [deleted file]
server/sonar-ce-task/src/it/java/org/sonar/ce/task/log/CeTaskMessagesImplIT.java [new file with mode: 0644]
server/sonar-ce-task/src/test/java/org/sonar/ce/task/log/CeTaskMessagesImplTest.java [deleted file]
server/sonar-ce/src/it/java/org/sonar/ce/analysis/cache/cleaning/AnalysisCacheCleaningSchedulerImplIT.java [new file with mode: 0644]
server/sonar-ce/src/it/java/org/sonar/ce/container/ComputeEngineContainerImplIT.java [new file with mode: 0644]
server/sonar-ce/src/it/java/org/sonar/ce/monitoring/CeDatabaseMBeanImplIT.java [new file with mode: 0644]
server/sonar-ce/src/it/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationExecutionListenerIT.java [new file with mode: 0644]
server/sonar-ce/src/it/java/org/sonar/ce/queue/InternalCeQueueImplIT.java [new file with mode: 0644]
server/sonar-ce/src/it/java/org/sonar/ce/queue/NextPendingTaskPickerIT.java [new file with mode: 0644]
server/sonar-ce/src/it/java/org/sonar/ce/taskprocessor/CeWorkerImplIT.java [new file with mode: 0644]
server/sonar-ce/src/test/java/org/sonar/ce/analysis/cache/cleaning/AnalysisCacheCleaningSchedulerImplTest.java [deleted file]
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java [deleted file]
server/sonar-ce/src/test/java/org/sonar/ce/monitoring/CeDatabaseMBeanImplTest.java [deleted file]
server/sonar-ce/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationExecutionListenerTest.java [deleted file]
server/sonar-ce/src/test/java/org/sonar/ce/queue/InternalCeQueueImplTest.java [deleted file]
server/sonar-ce/src/test/java/org/sonar/ce/queue/NextPendingTaskPickerTest.java [deleted file]
server/sonar-ce/src/test/java/org/sonar/ce/taskprocessor/CeWorkerImplTest.java [deleted file]

diff --git a/server/sonar-ce-common/src/it/java/org/sonar/ce/queue/CeQueueImplIT.java b/server/sonar-ce-common/src/it/java/org/sonar/ce/queue/CeQueueImplIT.java
new file mode 100644 (file)
index 0000000..e2bf5ee
--- /dev/null
@@ -0,0 +1,651 @@
+/*
+ * 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.queue;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Random;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.impl.utils.TestSystem2;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.queue.CeTaskSubmit.Component;
+import org.sonar.ce.task.CeTask;
+import org.sonar.core.util.SequenceUuidFactory;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.core.util.UuidFactoryFast;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.ce.CeActivityDto;
+import org.sonar.db.ce.CeQueueDto;
+import org.sonar.db.ce.CeTaskTypes;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.user.UserDto;
+import org.sonar.db.user.UserTesting;
+import org.sonar.server.platform.NodeInformation;
+
+import static com.google.common.collect.ImmutableList.of;
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyMap;
+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.assertj.core.api.Assertions.catchThrowable;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.ce.queue.CeQueue.SubmitOption.UNIQUE_QUEUE_PER_MAIN_COMPONENT;
+
+public class CeQueueImplIT {
+
+  private static final String WORKER_UUID = "workerUuid";
+  private static final long NOW = 1_450_000_000_000L;
+  private static final String NODE_NAME = "nodeName1";
+
+  private System2 system2 = new TestSystem2().setNow(NOW);
+
+  @Rule
+  public DbTester db = DbTester.create(system2);
+
+  private DbSession session = db.getSession();
+
+  private UuidFactory uuidFactory = new SequenceUuidFactory();
+
+  private NodeInformation nodeInformation = mock(NodeInformation.class);
+
+  private CeQueue underTest = new CeQueueImpl(system2, db.getDbClient(), uuidFactory, nodeInformation);
+
+  @Test
+  public void submit_returns_task_populated_from_CeTaskSubmit_and_creates_CeQueue_row() {
+    String componentUuid = randomAlphabetic(3);
+    String mainComponentUuid = randomAlphabetic(4);
+    CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, new Component(componentUuid, mainComponentUuid), "submitter uuid");
+    UserDto userDto = db.getDbClient().userDao().selectByUuid(db.getSession(), taskSubmit.getSubmitterUuid());
+
+    CeTask task = underTest.submit(taskSubmit);
+
+    verifyCeTask(taskSubmit, task, null, userDto);
+    verifyCeQueueDtoForTaskSubmit(taskSubmit);
+  }
+
+  @Test
+  public void submit_populates_component_name_and_key_of_CeTask_if_component_exists() {
+    ComponentDto componentDto = insertComponent(ComponentTesting.newPrivateProjectDto("PROJECT_1"));
+    CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, Component.fromDto(componentDto), null);
+
+    CeTask task = underTest.submit(taskSubmit);
+
+    verifyCeTask(taskSubmit, task, componentDto, null);
+  }
+
+  @Test
+  public void submit_returns_task_without_component_info_when_submit_has_none() {
+    CeTaskSubmit taskSubmit = createTaskSubmit("not cpt related");
+
+    CeTask task = underTest.submit(taskSubmit);
+
+    verifyCeTask(taskSubmit, task, null, null);
+  }
+
+  @Test
+  public void submit_populates_submitter_login_of_CeTask_if_submitter_exists() {
+    UserDto userDto = insertUser(UserTesting.newUserDto());
+    CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, null, userDto.getUuid());
+
+    CeTask task = underTest.submit(taskSubmit);
+
+    verifyCeTask(taskSubmit, task, null, userDto);
+  }
+
+  @Test
+  public void submit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_without_component_when_there_is_a_pending_task_without_component() {
+    CeTaskSubmit taskSubmit = createTaskSubmit("no_component");
+    CeQueueDto dto = insertPendingInQueue(null);
+
+    Optional<CeTask> task = underTest.submit(taskSubmit, UNIQUE_QUEUE_PER_MAIN_COMPONENT);
+
+    assertThat(task).isNotEmpty();
+    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
+      .extracting(CeQueueDto::getUuid)
+      .containsOnly(dto.getUuid(), task.get().getUuid());
+  }
+
+  @Test
+  public void submit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_when_there_is_a_pending_task_for_another_main_component() {
+    String mainComponentUuid = randomAlphabetic(5);
+    String otherMainComponentUuid = randomAlphabetic(6);
+    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
+    CeQueueDto dto = insertPendingInQueue(newComponent(otherMainComponentUuid));
+
+    Optional<CeTask> task = underTest.submit(taskSubmit, UNIQUE_QUEUE_PER_MAIN_COMPONENT);
+
+    assertThat(task).isNotEmpty();
+    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
+      .extracting(CeQueueDto::getUuid)
+      .containsOnly(dto.getUuid(), task.get().getUuid());
+  }
+
+  @Test
+  public void submit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_does_not_create_task_when_there_is_one_pending_task_for_same_main_component() {
+    String mainComponentUuid = randomAlphabetic(5);
+    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
+    CeQueueDto dto = insertPendingInQueue(newComponent(mainComponentUuid));
+
+    Optional<CeTask> task = underTest.submit(taskSubmit, UNIQUE_QUEUE_PER_MAIN_COMPONENT);
+
+    assertThat(task).isEmpty();
+    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
+      .extracting(CeQueueDto::getUuid)
+      .containsOnly(dto.getUuid());
+  }
+
+  @Test
+  public void submit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_does_not_create_task_when_there_is_many_pending_task_for_same_main_component() {
+    String mainComponentUuid = randomAlphabetic(5);
+    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
+    String[] uuids = IntStream.range(0, 2 + new Random().nextInt(5))
+      .mapToObj(i -> insertPendingInQueue(newComponent(mainComponentUuid)))
+      .map(CeQueueDto::getUuid)
+      .toArray(String[]::new);
+
+    Optional<CeTask> task = underTest.submit(taskSubmit, UNIQUE_QUEUE_PER_MAIN_COMPONENT);
+
+    assertThat(task).isEmpty();
+    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
+      .extracting(CeQueueDto::getUuid)
+      .containsOnly(uuids);
+  }
+
+  @Test
+  public void submit_without_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_when_there_is_one_pending_task_for_same_main_component() {
+    String mainComponentUuid = randomAlphabetic(5);
+    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
+    CeQueueDto dto = insertPendingInQueue(newComponent(mainComponentUuid));
+
+    CeTask task = underTest.submit(taskSubmit);
+
+    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
+      .extracting(CeQueueDto::getUuid)
+      .containsOnly(dto.getUuid(), task.getUuid());
+  }
+
+  @Test
+  public void submit_without_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_when_there_is_many_pending_task_for_same_main_component() {
+    String mainComponentUuid = randomAlphabetic(5);
+    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
+    String[] uuids = IntStream.range(0, 2 + new Random().nextInt(5))
+      .mapToObj(i -> insertPendingInQueue(newComponent(mainComponentUuid)))
+      .map(CeQueueDto::getUuid)
+      .toArray(String[]::new);
+
+    CeTask task = underTest.submit(taskSubmit);
+
+    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
+      .extracting(CeQueueDto::getUuid)
+      .hasSize(uuids.length + 1)
+      .contains(uuids)
+      .contains(task.getUuid());
+  }
+
+  @Test
+  public void massSubmit_returns_tasks_for_each_CeTaskSubmit_populated_from_CeTaskSubmit_and_creates_CeQueue_row_for_each() {
+    String mainComponentUuid = randomAlphabetic(10);
+    CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, newComponent(mainComponentUuid), "submitter uuid");
+    CeTaskSubmit taskSubmit2 = createTaskSubmit("some type");
+    UserDto userDto1 = db.getDbClient().userDao().selectByUuid(db.getSession(), taskSubmit1.getSubmitterUuid());
+
+    List<CeTask> tasks = underTest.massSubmit(asList(taskSubmit1, taskSubmit2));
+
+    assertThat(tasks).hasSize(2);
+    verifyCeTask(taskSubmit1, tasks.get(0), null, userDto1);
+    verifyCeTask(taskSubmit2, tasks.get(1), null, null);
+    verifyCeQueueDtoForTaskSubmit(taskSubmit1);
+    verifyCeQueueDtoForTaskSubmit(taskSubmit2);
+  }
+
+  @Test
+  public void massSubmit_populates_component_name_and_key_of_CeTask_if_project_exists() {
+    ComponentDto componentDto1 = insertComponent(ComponentTesting.newPrivateProjectDto("PROJECT_1"));
+    CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, Component.fromDto(componentDto1), null);
+    CeTaskSubmit taskSubmit2 = createTaskSubmit("something", newComponent(randomAlphabetic(12)), null);
+
+    List<CeTask> tasks = underTest.massSubmit(asList(taskSubmit1, taskSubmit2));
+
+    assertThat(tasks).hasSize(2);
+    verifyCeTask(taskSubmit1, tasks.get(0), componentDto1, null);
+    verifyCeTask(taskSubmit2, tasks.get(1), null, null);
+  }
+
+  @Test
+  public void massSubmit_populates_component_name_and_key_of_CeTask_if_project_and_branch_exists() {
+    ComponentDto project = insertComponent(ComponentTesting.newPrivateProjectDto("PROJECT_1"));
+    ComponentDto branch1 = db.components().insertProjectBranch(project);
+    ComponentDto branch2 = db.components().insertProjectBranch(project);
+    CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, Component.fromDto(branch1), null);
+    CeTaskSubmit taskSubmit2 = createTaskSubmit("something", Component.fromDto(branch2), null);
+
+    List<CeTask> tasks = underTest.massSubmit(asList(taskSubmit1, taskSubmit2));
+
+    assertThat(tasks).hasSize(2);
+    verifyCeTask(taskSubmit1, tasks.get(0), branch1, project, null);
+    verifyCeTask(taskSubmit2, tasks.get(1), branch2, project, null);
+  }
+
+  @Test
+  public void massSubmit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_without_component_when_there_is_a_pending_task_without_component() {
+    CeTaskSubmit taskSubmit = createTaskSubmit("no_component");
+    CeQueueDto dto = insertPendingInQueue(null);
+
+    List<CeTask> tasks = underTest.massSubmit(of(taskSubmit), UNIQUE_QUEUE_PER_MAIN_COMPONENT);
+
+    assertThat(tasks).hasSize(1);
+    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
+      .extracting(CeQueueDto::getUuid)
+      .containsOnly(dto.getUuid(), tasks.iterator().next().getUuid());
+  }
+
+  @Test
+  public void massSubmit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_when_there_is_a_pending_task_for_another_main_component() {
+    String mainComponentUuid = randomAlphabetic(5);
+    String otherMainComponentUuid = randomAlphabetic(6);
+    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
+    CeQueueDto dto = insertPendingInQueue(newComponent(otherMainComponentUuid));
+
+    List<CeTask> tasks = underTest.massSubmit(of(taskSubmit), UNIQUE_QUEUE_PER_MAIN_COMPONENT);
+
+    assertThat(tasks).hasSize(1);
+    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
+      .extracting(CeQueueDto::getUuid)
+      .containsOnly(dto.getUuid(), tasks.iterator().next().getUuid());
+  }
+
+  @Test
+  public void massSubmit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_does_not_create_task_when_there_is_one_pending_task_for_same_main_component() {
+    String mainComponentUuid = randomAlphabetic(5);
+    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
+    CeQueueDto dto = insertPendingInQueue(newComponent(mainComponentUuid));
+
+    List<CeTask> tasks = underTest.massSubmit(of(taskSubmit), UNIQUE_QUEUE_PER_MAIN_COMPONENT);
+
+    assertThat(tasks).isEmpty();
+    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
+      .extracting(CeQueueDto::getUuid)
+      .containsOnly(dto.getUuid());
+  }
+
+  @Test
+  public void massSubmit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_does_not_create_task_when_there_is_many_pending_task_for_same_main_component() {
+    String mainComponentUuid = randomAlphabetic(5);
+    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
+    String[] uuids = IntStream.range(0, 2 + new Random().nextInt(5))
+      .mapToObj(i -> insertPendingInQueue(newComponent(mainComponentUuid)))
+      .map(CeQueueDto::getUuid)
+      .toArray(String[]::new);
+
+    List<CeTask> tasks = underTest.massSubmit(of(taskSubmit), UNIQUE_QUEUE_PER_MAIN_COMPONENT);
+
+    assertThat(tasks).isEmpty();
+    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
+      .extracting(CeQueueDto::getUuid)
+      .containsOnly(uuids);
+  }
+
+  @Test
+  public void massSubmit_without_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_when_there_is_one_pending_task_for_other_main_component() {
+    String mainComponentUuid = randomAlphabetic(5);
+    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
+    CeQueueDto dto = insertPendingInQueue(newComponent(mainComponentUuid));
+
+    List<CeTask> tasks = underTest.massSubmit(of(taskSubmit));
+
+    assertThat(tasks).hasSize(1);
+    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
+      .extracting(CeQueueDto::getUuid)
+      .containsOnly(dto.getUuid(), tasks.iterator().next().getUuid());
+  }
+
+  @Test
+  public void massSubmit_without_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_when_there_is_many_pending_task_for_other_main_component() {
+    String mainComponentUuid = randomAlphabetic(5);
+    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
+    String[] uuids = IntStream.range(0, 2 + new Random().nextInt(5))
+      .mapToObj(i -> insertPendingInQueue(newComponent(mainComponentUuid)))
+      .map(CeQueueDto::getUuid)
+      .toArray(String[]::new);
+
+    List<CeTask> tasks = underTest.massSubmit(of(taskSubmit));
+
+    assertThat(tasks).hasSize(1);
+    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
+      .extracting(CeQueueDto::getUuid)
+      .hasSize(uuids.length + 1)
+      .contains(uuids)
+      .contains(tasks.iterator().next().getUuid());
+  }
+
+  @Test
+  public void massSubmit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_tasks_depending_on_whether_there_is_pending_task_for_same_main_component() {
+    String mainComponentUuid1 = randomAlphabetic(5);
+    String mainComponentUuid2 = randomAlphabetic(6);
+    String mainComponentUuid3 = randomAlphabetic(7);
+    String mainComponentUuid4 = randomAlphabetic(8);
+    String mainComponentUuid5 = randomAlphabetic(9);
+    CeTaskSubmit taskSubmit1 = createTaskSubmit("with_one_pending", newComponent(mainComponentUuid1), null);
+    CeQueueDto dto1 = insertPendingInQueue(newComponent(mainComponentUuid1));
+    Component componentForMainComponentUuid2 = newComponent(mainComponentUuid2);
+    CeTaskSubmit taskSubmit2 = createTaskSubmit("no_pending", componentForMainComponentUuid2, null);
+    CeTaskSubmit taskSubmit3 = createTaskSubmit("with_many_pending", newComponent(mainComponentUuid3), null);
+    String[] uuids3 = IntStream.range(0, 2 + new Random().nextInt(5))
+      .mapToObj(i -> insertPendingInQueue(newComponent(mainComponentUuid3)))
+      .map(CeQueueDto::getUuid)
+      .toArray(String[]::new);
+    Component componentForMainComponentUuid4 = newComponent(mainComponentUuid4);
+    CeTaskSubmit taskSubmit4 = createTaskSubmit("no_pending_2", componentForMainComponentUuid4, null);
+    CeTaskSubmit taskSubmit5 = createTaskSubmit("with_pending_2", newComponent(mainComponentUuid5), null);
+    CeQueueDto dto5 = insertPendingInQueue(newComponent(mainComponentUuid5));
+
+    List<CeTask> tasks = underTest.massSubmit(of(taskSubmit1, taskSubmit2, taskSubmit3, taskSubmit4, taskSubmit5), UNIQUE_QUEUE_PER_MAIN_COMPONENT);
+
+    assertThat(tasks)
+      .hasSize(2)
+      .extracting(task -> task.getComponent().get().getUuid(), task -> task.getMainComponent().get().getUuid())
+      .containsOnly(tuple(componentForMainComponentUuid2.getUuid(), componentForMainComponentUuid2.getMainComponentUuid()),
+        tuple(componentForMainComponentUuid4.getUuid(), componentForMainComponentUuid4.getMainComponentUuid()));
+    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
+      .extracting(CeQueueDto::getUuid)
+      .hasSize(1 + uuids3.length + 1 + tasks.size())
+      .contains(dto1.getUuid())
+      .contains(uuids3)
+      .contains(dto5.getUuid())
+      .containsAll(tasks.stream().map(CeTask::getUuid).collect(Collectors.toList()));
+  }
+
+  @Test
+  public void cancel_pending() {
+    CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
+    CeQueueDto queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), task.getUuid()).get();
+
+    underTest.cancel(db.getSession(), queueDto);
+
+    Optional<CeActivityDto> activity = findCeActivityDtoInDb(task);
+    assertThat(activity).isPresent();
+    assertThat(activity.get().getStatus()).isEqualTo(CeActivityDto.Status.CANCELED);
+  }
+
+  @Test
+  public void cancel_pending_whenNodeNameProvided_setItInCeActivity() {
+    when(nodeInformation.getNodeName()).thenReturn(Optional.of(NODE_NAME));
+    CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
+    CeQueueDto queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), task.getUuid()).get();
+
+    underTest.cancel(db.getSession(), queueDto);
+
+    Optional<CeActivityDto> activity = findCeActivityDtoInDb(task);
+    assertThat(activity).isPresent();
+    assertThat(activity.get().getNodeName()).isEqualTo(NODE_NAME);
+  }
+
+  @Test
+  public void cancel_pending_whenNodeNameNOtProvided_setNulInCeActivity() {
+    when(nodeInformation.getNodeName()).thenReturn(Optional.empty());
+    CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
+    CeQueueDto queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), task.getUuid()).get();
+
+    underTest.cancel(db.getSession(), queueDto);
+
+    Optional<CeActivityDto> activity = findCeActivityDtoInDb(task);
+    assertThat(activity).isPresent();
+    assertThat(activity.get().getNodeName()).isNull();
+  }
+
+  @Test
+  public void fail_to_cancel_if_in_progress() {
+    CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(11)));
+    CeQueueDto ceQueueDto = db.getDbClient().ceQueueDao().tryToPeek(session, task.getUuid(), WORKER_UUID).get();
+
+    assertThatThrownBy(() -> underTest.cancel(db.getSession(), ceQueueDto))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessageStartingWith("Task is in progress and can't be canceled");
+  }
+
+  @Test
+  public void cancelAll_pendings_but_not_in_progress() {
+    CeTask inProgressTask = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
+    CeTask pendingTask1 = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
+    CeTask pendingTask2 = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
+
+    db.getDbClient().ceQueueDao().tryToPeek(session, inProgressTask.getUuid(), WORKER_UUID);
+
+    int canceledCount = underTest.cancelAll();
+    assertThat(canceledCount).isEqualTo(2);
+
+    Optional<CeActivityDto> ceActivityInProgress = findCeActivityDtoInDb(pendingTask1);
+    assertThat(ceActivityInProgress.get().getStatus()).isEqualTo(CeActivityDto.Status.CANCELED);
+    Optional<CeActivityDto> ceActivityPending1 = findCeActivityDtoInDb(pendingTask2);
+    assertThat(ceActivityPending1.get().getStatus()).isEqualTo(CeActivityDto.Status.CANCELED);
+    Optional<CeActivityDto> ceActivityPending2 = findCeActivityDtoInDb(inProgressTask);
+    assertThat(ceActivityPending2).isNotPresent();
+  }
+
+  @Test
+  public void pauseWorkers_marks_workers_as_paused_if_zero_tasks_in_progress() {
+    submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
+    // task is pending
+
+    assertThat(underTest.getWorkersPauseStatus()).isEqualTo(CeQueue.WorkersPauseStatus.RESUMED);
+
+    underTest.pauseWorkers();
+    assertThat(underTest.getWorkersPauseStatus()).isEqualTo(CeQueue.WorkersPauseStatus.PAUSED);
+  }
+
+  @Test
+  public void pauseWorkers_marks_workers_as_pausing_if_some_tasks_in_progress() {
+    CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
+    db.getDbClient().ceQueueDao().tryToPeek(session, task.getUuid(), WORKER_UUID);
+    // task is in-progress
+
+    assertThat(underTest.getWorkersPauseStatus()).isEqualTo(CeQueue.WorkersPauseStatus.RESUMED);
+
+    underTest.pauseWorkers();
+    assertThat(underTest.getWorkersPauseStatus()).isEqualTo(CeQueue.WorkersPauseStatus.PAUSING);
+  }
+
+  @Test
+  public void resumeWorkers_does_nothing_if_not_paused() {
+    assertThat(underTest.getWorkersPauseStatus()).isEqualTo(CeQueue.WorkersPauseStatus.RESUMED);
+
+    underTest.resumeWorkers();
+
+    assertThat(underTest.getWorkersPauseStatus()).isEqualTo(CeQueue.WorkersPauseStatus.RESUMED);
+  }
+
+  @Test
+  public void resumeWorkers_resumes_pausing_workers() {
+    CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
+    db.getDbClient().ceQueueDao().tryToPeek(session, task.getUuid(), WORKER_UUID);
+    // task is in-progress
+
+    underTest.pauseWorkers();
+    assertThat(underTest.getWorkersPauseStatus()).isEqualTo(CeQueue.WorkersPauseStatus.PAUSING);
+
+    underTest.resumeWorkers();
+    assertThat(underTest.getWorkersPauseStatus()).isEqualTo(CeQueue.WorkersPauseStatus.RESUMED);
+  }
+
+  @Test
+  public void resumeWorkers_resumes_paused_workers() {
+    underTest.pauseWorkers();
+    assertThat(underTest.getWorkersPauseStatus()).isEqualTo(CeQueue.WorkersPauseStatus.PAUSED);
+
+    underTest.resumeWorkers();
+    assertThat(underTest.getWorkersPauseStatus()).isEqualTo(CeQueue.WorkersPauseStatus.RESUMED);
+  }
+
+  @Test
+  public void fail_in_progress_task() {
+    CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
+    CeQueueDto queueDto = db.getDbClient().ceQueueDao().tryToPeek(db.getSession(), task.getUuid(), WORKER_UUID).get();
+
+    underTest.fail(db.getSession(), queueDto, "TIMEOUT", "Failed on timeout");
+
+    Optional<CeActivityDto> activity = findCeActivityDtoInDb(task);
+    assertThat(activity).isPresent();
+    assertThat(activity.get().getStatus()).isEqualTo(CeActivityDto.Status.FAILED);
+    assertThat(activity.get().getErrorType()).isEqualTo("TIMEOUT");
+    assertThat(activity.get().getErrorMessage()).isEqualTo("Failed on timeout");
+    assertThat(activity.get().getExecutedAt()).isEqualTo(NOW);
+    assertThat(activity.get().getWorkerUuid()).isEqualTo(WORKER_UUID);
+    assertThat(activity.get().getNodeName()).isNull();
+  }
+
+  @Test
+  public void fail_in_progress_task_whenNodeNameProvided_setsItInCeActivityDto() {
+    when(nodeInformation.getNodeName()).thenReturn(Optional.of(NODE_NAME));
+    CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
+    CeQueueDto queueDto = db.getDbClient().ceQueueDao().tryToPeek(db.getSession(), task.getUuid(), WORKER_UUID).get();
+
+    underTest.fail(db.getSession(), queueDto, "TIMEOUT", "Failed on timeout");
+
+    Optional<CeActivityDto> activity = findCeActivityDtoInDb(task);
+    assertThat(activity).isPresent();
+    assertThat(activity.get().getNodeName()).isEqualTo(NODE_NAME);
+  }
+
+  private Optional<CeActivityDto> findCeActivityDtoInDb(CeTask task) {
+    return db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), task.getUuid());
+  }
+
+  @Test
+  public void fail_throws_exception_if_task_is_pending() {
+    CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
+    CeQueueDto queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), task.getUuid()).get();
+
+    Throwable thrown = catchThrowable(() -> underTest.fail(db.getSession(), queueDto, "TIMEOUT", "Failed on timeout"));
+
+    assertThat(thrown)
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Task is not in-progress and can't be marked as failed [uuid=" + task.getUuid() + "]");
+  }
+
+  private void verifyCeTask(CeTaskSubmit taskSubmit, CeTask task, @Nullable ComponentDto componentDto, UserDto userDto) {
+    verifyCeTask(taskSubmit, task, componentDto, componentDto, userDto);
+  }
+
+  private void verifyCeTask(CeTaskSubmit taskSubmit, CeTask task, @Nullable ComponentDto componentDto, @Nullable ComponentDto mainComponentDto, @Nullable UserDto userDto) {
+    assertThat(task.getUuid()).isEqualTo(taskSubmit.getUuid());
+    if (componentDto != null) {
+      CeTask.Component component = task.getComponent().get();
+      assertThat(component.getUuid()).isEqualTo(componentDto.uuid());
+      assertThat(component.getKey()).contains(componentDto.getKey());
+      assertThat(component.getName()).contains(componentDto.name());
+    } else if (taskSubmit.getComponent().isPresent()) {
+      assertThat(task.getComponent()).contains(new CeTask.Component(taskSubmit.getComponent().get().getUuid(), null, null));
+    } else {
+      assertThat(task.getComponent()).isEmpty();
+    }
+    if (mainComponentDto != null) {
+      CeTask.Component component = task.getMainComponent().get();
+      assertThat(component.getUuid()).isEqualTo(mainComponentDto.uuid());
+      assertThat(component.getKey()).contains(mainComponentDto.getKey());
+      assertThat(component.getName()).contains(mainComponentDto.name());
+    } else if (taskSubmit.getComponent().isPresent()) {
+      assertThat(task.getMainComponent()).contains(new CeTask.Component(taskSubmit.getComponent().get().getMainComponentUuid(), null, null));
+    } else {
+      assertThat(task.getMainComponent()).isEmpty();
+    }
+    assertThat(task.getType()).isEqualTo(taskSubmit.getType());
+    if (taskSubmit.getSubmitterUuid() != null) {
+      if (userDto == null) {
+        assertThat(task.getSubmitter().uuid()).isEqualTo(taskSubmit.getSubmitterUuid());
+        assertThat(task.getSubmitter().login()).isNull();
+      } else {
+        assertThat(task.getSubmitter().uuid()).isEqualTo(userDto.getUuid()).isEqualTo(taskSubmit.getSubmitterUuid());
+        assertThat(task.getSubmitter().login()).isEqualTo(userDto.getLogin());
+      }
+    }
+  }
+
+  private void verifyCeQueueDtoForTaskSubmit(CeTaskSubmit taskSubmit) {
+    Optional<CeQueueDto> queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), taskSubmit.getUuid());
+    assertThat(queueDto).isPresent();
+    assertThat(queueDto.get().getTaskType()).isEqualTo(taskSubmit.getType());
+    Optional<Component> component = taskSubmit.getComponent();
+    if (component.isPresent()) {
+      assertThat(queueDto.get().getComponentUuid()).isEqualTo(component.get().getUuid());
+      assertThat(queueDto.get().getMainComponentUuid()).isEqualTo(component.get().getMainComponentUuid());
+    } else {
+      assertThat(queueDto.get().getComponentUuid()).isNull();
+      assertThat(queueDto.get().getComponentUuid()).isNull();
+    }
+    assertThat(queueDto.get().getSubmitterUuid()).isEqualTo(taskSubmit.getSubmitterUuid());
+    assertThat(queueDto.get().getCreatedAt()).isEqualTo(1_450_000_000_000L);
+  }
+
+  private CeTask submit(String reportType, Component component) {
+    return underTest.submit(createTaskSubmit(reportType, component, null));
+  }
+
+  private CeTaskSubmit createTaskSubmit(String type) {
+    return createTaskSubmit(type, null, null);
+  }
+
+  private CeTaskSubmit createTaskSubmit(String type, @Nullable Component component, @Nullable String submitterUuid) {
+    return underTest.prepareSubmit()
+      .setType(type)
+      .setComponent(component)
+      .setSubmitterUuid(submitterUuid)
+      .setCharacteristics(emptyMap())
+      .build();
+  }
+
+  private ComponentDto insertComponent(ComponentDto componentDto) {
+    return db.components().insertComponent(componentDto);
+  }
+
+  private UserDto insertUser(UserDto userDto) {
+    db.getDbClient().userDao().insert(session, userDto);
+    session.commit();
+    return userDto;
+  }
+
+  private CeQueueDto insertPendingInQueue(@Nullable Component component) {
+    CeQueueDto dto = new CeQueueDto()
+      .setUuid(UuidFactoryFast.getInstance().create())
+      .setTaskType("some type")
+      .setStatus(CeQueueDto.Status.PENDING);
+    if (component != null) {
+      dto.setComponentUuid(component.getUuid())
+        .setMainComponentUuid(component.getMainComponentUuid());
+    }
+    db.getDbClient().ceQueueDao().insert(db.getSession(), dto);
+    db.commit();
+    return dto;
+  }
+
+  private static int newComponentIdGenerator = new Random().nextInt(8_999_333);
+
+  private static Component newComponent(String mainComponentUuid) {
+    return new Component("uuid_" + newComponentIdGenerator++, mainComponentUuid);
+  }
+}
diff --git a/server/sonar-ce-common/src/test/java/org/sonar/ce/queue/CeQueueImplTest.java b/server/sonar-ce-common/src/test/java/org/sonar/ce/queue/CeQueueImplTest.java
deleted file mode 100644 (file)
index d17937e..0000000
+++ /dev/null
@@ -1,651 +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.queue;
-
-import java.util.List;
-import java.util.Optional;
-import java.util.Random;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import javax.annotation.Nullable;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.impl.utils.TestSystem2;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.queue.CeTaskSubmit.Component;
-import org.sonar.ce.task.CeTask;
-import org.sonar.core.util.SequenceUuidFactory;
-import org.sonar.core.util.UuidFactory;
-import org.sonar.core.util.UuidFactoryFast;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.ce.CeActivityDto;
-import org.sonar.db.ce.CeQueueDto;
-import org.sonar.db.ce.CeTaskTypes;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.user.UserDto;
-import org.sonar.db.user.UserTesting;
-import org.sonar.server.platform.NodeInformation;
-
-import static com.google.common.collect.ImmutableList.of;
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptyMap;
-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.assertj.core.api.Assertions.catchThrowable;
-import static org.assertj.core.api.Assertions.tuple;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.ce.queue.CeQueue.SubmitOption.UNIQUE_QUEUE_PER_MAIN_COMPONENT;
-
-public class CeQueueImplTest {
-
-  private static final String WORKER_UUID = "workerUuid";
-  private static final long NOW = 1_450_000_000_000L;
-  private static final String NODE_NAME = "nodeName1";
-
-  private System2 system2 = new TestSystem2().setNow(NOW);
-
-  @Rule
-  public DbTester db = DbTester.create(system2);
-
-  private DbSession session = db.getSession();
-
-  private UuidFactory uuidFactory = new SequenceUuidFactory();
-
-  private NodeInformation nodeInformation = mock(NodeInformation.class);
-
-  private CeQueue underTest = new CeQueueImpl(system2, db.getDbClient(), uuidFactory, nodeInformation);
-
-  @Test
-  public void submit_returns_task_populated_from_CeTaskSubmit_and_creates_CeQueue_row() {
-    String componentUuid = randomAlphabetic(3);
-    String mainComponentUuid = randomAlphabetic(4);
-    CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, new Component(componentUuid, mainComponentUuid), "submitter uuid");
-    UserDto userDto = db.getDbClient().userDao().selectByUuid(db.getSession(), taskSubmit.getSubmitterUuid());
-
-    CeTask task = underTest.submit(taskSubmit);
-
-    verifyCeTask(taskSubmit, task, null, userDto);
-    verifyCeQueueDtoForTaskSubmit(taskSubmit);
-  }
-
-  @Test
-  public void submit_populates_component_name_and_key_of_CeTask_if_component_exists() {
-    ComponentDto componentDto = insertComponent(ComponentTesting.newPrivateProjectDto("PROJECT_1"));
-    CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, Component.fromDto(componentDto), null);
-
-    CeTask task = underTest.submit(taskSubmit);
-
-    verifyCeTask(taskSubmit, task, componentDto, null);
-  }
-
-  @Test
-  public void submit_returns_task_without_component_info_when_submit_has_none() {
-    CeTaskSubmit taskSubmit = createTaskSubmit("not cpt related");
-
-    CeTask task = underTest.submit(taskSubmit);
-
-    verifyCeTask(taskSubmit, task, null, null);
-  }
-
-  @Test
-  public void submit_populates_submitter_login_of_CeTask_if_submitter_exists() {
-    UserDto userDto = insertUser(UserTesting.newUserDto());
-    CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, null, userDto.getUuid());
-
-    CeTask task = underTest.submit(taskSubmit);
-
-    verifyCeTask(taskSubmit, task, null, userDto);
-  }
-
-  @Test
-  public void submit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_without_component_when_there_is_a_pending_task_without_component() {
-    CeTaskSubmit taskSubmit = createTaskSubmit("no_component");
-    CeQueueDto dto = insertPendingInQueue(null);
-
-    Optional<CeTask> task = underTest.submit(taskSubmit, UNIQUE_QUEUE_PER_MAIN_COMPONENT);
-
-    assertThat(task).isNotEmpty();
-    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
-      .extracting(CeQueueDto::getUuid)
-      .containsOnly(dto.getUuid(), task.get().getUuid());
-  }
-
-  @Test
-  public void submit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_when_there_is_a_pending_task_for_another_main_component() {
-    String mainComponentUuid = randomAlphabetic(5);
-    String otherMainComponentUuid = randomAlphabetic(6);
-    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
-    CeQueueDto dto = insertPendingInQueue(newComponent(otherMainComponentUuid));
-
-    Optional<CeTask> task = underTest.submit(taskSubmit, UNIQUE_QUEUE_PER_MAIN_COMPONENT);
-
-    assertThat(task).isNotEmpty();
-    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
-      .extracting(CeQueueDto::getUuid)
-      .containsOnly(dto.getUuid(), task.get().getUuid());
-  }
-
-  @Test
-  public void submit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_does_not_create_task_when_there_is_one_pending_task_for_same_main_component() {
-    String mainComponentUuid = randomAlphabetic(5);
-    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
-    CeQueueDto dto = insertPendingInQueue(newComponent(mainComponentUuid));
-
-    Optional<CeTask> task = underTest.submit(taskSubmit, UNIQUE_QUEUE_PER_MAIN_COMPONENT);
-
-    assertThat(task).isEmpty();
-    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
-      .extracting(CeQueueDto::getUuid)
-      .containsOnly(dto.getUuid());
-  }
-
-  @Test
-  public void submit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_does_not_create_task_when_there_is_many_pending_task_for_same_main_component() {
-    String mainComponentUuid = randomAlphabetic(5);
-    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
-    String[] uuids = IntStream.range(0, 2 + new Random().nextInt(5))
-      .mapToObj(i -> insertPendingInQueue(newComponent(mainComponentUuid)))
-      .map(CeQueueDto::getUuid)
-      .toArray(String[]::new);
-
-    Optional<CeTask> task = underTest.submit(taskSubmit, UNIQUE_QUEUE_PER_MAIN_COMPONENT);
-
-    assertThat(task).isEmpty();
-    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
-      .extracting(CeQueueDto::getUuid)
-      .containsOnly(uuids);
-  }
-
-  @Test
-  public void submit_without_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_when_there_is_one_pending_task_for_same_main_component() {
-    String mainComponentUuid = randomAlphabetic(5);
-    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
-    CeQueueDto dto = insertPendingInQueue(newComponent(mainComponentUuid));
-
-    CeTask task = underTest.submit(taskSubmit);
-
-    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
-      .extracting(CeQueueDto::getUuid)
-      .containsOnly(dto.getUuid(), task.getUuid());
-  }
-
-  @Test
-  public void submit_without_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_when_there_is_many_pending_task_for_same_main_component() {
-    String mainComponentUuid = randomAlphabetic(5);
-    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
-    String[] uuids = IntStream.range(0, 2 + new Random().nextInt(5))
-      .mapToObj(i -> insertPendingInQueue(newComponent(mainComponentUuid)))
-      .map(CeQueueDto::getUuid)
-      .toArray(String[]::new);
-
-    CeTask task = underTest.submit(taskSubmit);
-
-    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
-      .extracting(CeQueueDto::getUuid)
-      .hasSize(uuids.length + 1)
-      .contains(uuids)
-      .contains(task.getUuid());
-  }
-
-  @Test
-  public void massSubmit_returns_tasks_for_each_CeTaskSubmit_populated_from_CeTaskSubmit_and_creates_CeQueue_row_for_each() {
-    String mainComponentUuid = randomAlphabetic(10);
-    CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, newComponent(mainComponentUuid), "submitter uuid");
-    CeTaskSubmit taskSubmit2 = createTaskSubmit("some type");
-    UserDto userDto1 = db.getDbClient().userDao().selectByUuid(db.getSession(), taskSubmit1.getSubmitterUuid());
-
-    List<CeTask> tasks = underTest.massSubmit(asList(taskSubmit1, taskSubmit2));
-
-    assertThat(tasks).hasSize(2);
-    verifyCeTask(taskSubmit1, tasks.get(0), null, userDto1);
-    verifyCeTask(taskSubmit2, tasks.get(1), null, null);
-    verifyCeQueueDtoForTaskSubmit(taskSubmit1);
-    verifyCeQueueDtoForTaskSubmit(taskSubmit2);
-  }
-
-  @Test
-  public void massSubmit_populates_component_name_and_key_of_CeTask_if_project_exists() {
-    ComponentDto componentDto1 = insertComponent(ComponentTesting.newPrivateProjectDto("PROJECT_1"));
-    CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, Component.fromDto(componentDto1), null);
-    CeTaskSubmit taskSubmit2 = createTaskSubmit("something", newComponent(randomAlphabetic(12)), null);
-
-    List<CeTask> tasks = underTest.massSubmit(asList(taskSubmit1, taskSubmit2));
-
-    assertThat(tasks).hasSize(2);
-    verifyCeTask(taskSubmit1, tasks.get(0), componentDto1, null);
-    verifyCeTask(taskSubmit2, tasks.get(1), null, null);
-  }
-
-  @Test
-  public void massSubmit_populates_component_name_and_key_of_CeTask_if_project_and_branch_exists() {
-    ComponentDto project = insertComponent(ComponentTesting.newPrivateProjectDto("PROJECT_1"));
-    ComponentDto branch1 = db.components().insertProjectBranch(project);
-    ComponentDto branch2 = db.components().insertProjectBranch(project);
-    CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, Component.fromDto(branch1), null);
-    CeTaskSubmit taskSubmit2 = createTaskSubmit("something", Component.fromDto(branch2), null);
-
-    List<CeTask> tasks = underTest.massSubmit(asList(taskSubmit1, taskSubmit2));
-
-    assertThat(tasks).hasSize(2);
-    verifyCeTask(taskSubmit1, tasks.get(0), branch1, project, null);
-    verifyCeTask(taskSubmit2, tasks.get(1), branch2, project, null);
-  }
-
-  @Test
-  public void massSubmit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_without_component_when_there_is_a_pending_task_without_component() {
-    CeTaskSubmit taskSubmit = createTaskSubmit("no_component");
-    CeQueueDto dto = insertPendingInQueue(null);
-
-    List<CeTask> tasks = underTest.massSubmit(of(taskSubmit), UNIQUE_QUEUE_PER_MAIN_COMPONENT);
-
-    assertThat(tasks).hasSize(1);
-    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
-      .extracting(CeQueueDto::getUuid)
-      .containsOnly(dto.getUuid(), tasks.iterator().next().getUuid());
-  }
-
-  @Test
-  public void massSubmit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_when_there_is_a_pending_task_for_another_main_component() {
-    String mainComponentUuid = randomAlphabetic(5);
-    String otherMainComponentUuid = randomAlphabetic(6);
-    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
-    CeQueueDto dto = insertPendingInQueue(newComponent(otherMainComponentUuid));
-
-    List<CeTask> tasks = underTest.massSubmit(of(taskSubmit), UNIQUE_QUEUE_PER_MAIN_COMPONENT);
-
-    assertThat(tasks).hasSize(1);
-    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
-      .extracting(CeQueueDto::getUuid)
-      .containsOnly(dto.getUuid(), tasks.iterator().next().getUuid());
-  }
-
-  @Test
-  public void massSubmit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_does_not_create_task_when_there_is_one_pending_task_for_same_main_component() {
-    String mainComponentUuid = randomAlphabetic(5);
-    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
-    CeQueueDto dto = insertPendingInQueue(newComponent(mainComponentUuid));
-
-    List<CeTask> tasks = underTest.massSubmit(of(taskSubmit), UNIQUE_QUEUE_PER_MAIN_COMPONENT);
-
-    assertThat(tasks).isEmpty();
-    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
-      .extracting(CeQueueDto::getUuid)
-      .containsOnly(dto.getUuid());
-  }
-
-  @Test
-  public void massSubmit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_does_not_create_task_when_there_is_many_pending_task_for_same_main_component() {
-    String mainComponentUuid = randomAlphabetic(5);
-    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
-    String[] uuids = IntStream.range(0, 2 + new Random().nextInt(5))
-      .mapToObj(i -> insertPendingInQueue(newComponent(mainComponentUuid)))
-      .map(CeQueueDto::getUuid)
-      .toArray(String[]::new);
-
-    List<CeTask> tasks = underTest.massSubmit(of(taskSubmit), UNIQUE_QUEUE_PER_MAIN_COMPONENT);
-
-    assertThat(tasks).isEmpty();
-    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
-      .extracting(CeQueueDto::getUuid)
-      .containsOnly(uuids);
-  }
-
-  @Test
-  public void massSubmit_without_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_when_there_is_one_pending_task_for_other_main_component() {
-    String mainComponentUuid = randomAlphabetic(5);
-    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
-    CeQueueDto dto = insertPendingInQueue(newComponent(mainComponentUuid));
-
-    List<CeTask> tasks = underTest.massSubmit(of(taskSubmit));
-
-    assertThat(tasks).hasSize(1);
-    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
-      .extracting(CeQueueDto::getUuid)
-      .containsOnly(dto.getUuid(), tasks.iterator().next().getUuid());
-  }
-
-  @Test
-  public void massSubmit_without_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_task_when_there_is_many_pending_task_for_other_main_component() {
-    String mainComponentUuid = randomAlphabetic(5);
-    CeTaskSubmit taskSubmit = createTaskSubmit("with_component", newComponent(mainComponentUuid), null);
-    String[] uuids = IntStream.range(0, 2 + new Random().nextInt(5))
-      .mapToObj(i -> insertPendingInQueue(newComponent(mainComponentUuid)))
-      .map(CeQueueDto::getUuid)
-      .toArray(String[]::new);
-
-    List<CeTask> tasks = underTest.massSubmit(of(taskSubmit));
-
-    assertThat(tasks).hasSize(1);
-    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
-      .extracting(CeQueueDto::getUuid)
-      .hasSize(uuids.length + 1)
-      .contains(uuids)
-      .contains(tasks.iterator().next().getUuid());
-  }
-
-  @Test
-  public void massSubmit_with_UNIQUE_QUEUE_PER_MAIN_COMPONENT_creates_tasks_depending_on_whether_there_is_pending_task_for_same_main_component() {
-    String mainComponentUuid1 = randomAlphabetic(5);
-    String mainComponentUuid2 = randomAlphabetic(6);
-    String mainComponentUuid3 = randomAlphabetic(7);
-    String mainComponentUuid4 = randomAlphabetic(8);
-    String mainComponentUuid5 = randomAlphabetic(9);
-    CeTaskSubmit taskSubmit1 = createTaskSubmit("with_one_pending", newComponent(mainComponentUuid1), null);
-    CeQueueDto dto1 = insertPendingInQueue(newComponent(mainComponentUuid1));
-    Component componentForMainComponentUuid2 = newComponent(mainComponentUuid2);
-    CeTaskSubmit taskSubmit2 = createTaskSubmit("no_pending", componentForMainComponentUuid2, null);
-    CeTaskSubmit taskSubmit3 = createTaskSubmit("with_many_pending", newComponent(mainComponentUuid3), null);
-    String[] uuids3 = IntStream.range(0, 2 + new Random().nextInt(5))
-      .mapToObj(i -> insertPendingInQueue(newComponent(mainComponentUuid3)))
-      .map(CeQueueDto::getUuid)
-      .toArray(String[]::new);
-    Component componentForMainComponentUuid4 = newComponent(mainComponentUuid4);
-    CeTaskSubmit taskSubmit4 = createTaskSubmit("no_pending_2", componentForMainComponentUuid4, null);
-    CeTaskSubmit taskSubmit5 = createTaskSubmit("with_pending_2", newComponent(mainComponentUuid5), null);
-    CeQueueDto dto5 = insertPendingInQueue(newComponent(mainComponentUuid5));
-
-    List<CeTask> tasks = underTest.massSubmit(of(taskSubmit1, taskSubmit2, taskSubmit3, taskSubmit4, taskSubmit5), UNIQUE_QUEUE_PER_MAIN_COMPONENT);
-
-    assertThat(tasks)
-      .hasSize(2)
-      .extracting(task -> task.getComponent().get().getUuid(), task -> task.getMainComponent().get().getUuid())
-      .containsOnly(tuple(componentForMainComponentUuid2.getUuid(), componentForMainComponentUuid2.getMainComponentUuid()),
-        tuple(componentForMainComponentUuid4.getUuid(), componentForMainComponentUuid4.getMainComponentUuid()));
-    assertThat(db.getDbClient().ceQueueDao().selectAllInAscOrder(db.getSession()))
-      .extracting(CeQueueDto::getUuid)
-      .hasSize(1 + uuids3.length + 1 + tasks.size())
-      .contains(dto1.getUuid())
-      .contains(uuids3)
-      .contains(dto5.getUuid())
-      .containsAll(tasks.stream().map(CeTask::getUuid).collect(Collectors.toList()));
-  }
-
-  @Test
-  public void cancel_pending() {
-    CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
-    CeQueueDto queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), task.getUuid()).get();
-
-    underTest.cancel(db.getSession(), queueDto);
-
-    Optional<CeActivityDto> activity = findCeActivityDtoInDb(task);
-    assertThat(activity).isPresent();
-    assertThat(activity.get().getStatus()).isEqualTo(CeActivityDto.Status.CANCELED);
-  }
-
-  @Test
-  public void cancel_pending_whenNodeNameProvided_setItInCeActivity() {
-    when(nodeInformation.getNodeName()).thenReturn(Optional.of(NODE_NAME));
-    CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
-    CeQueueDto queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), task.getUuid()).get();
-
-    underTest.cancel(db.getSession(), queueDto);
-
-    Optional<CeActivityDto> activity = findCeActivityDtoInDb(task);
-    assertThat(activity).isPresent();
-    assertThat(activity.get().getNodeName()).isEqualTo(NODE_NAME);
-  }
-
-  @Test
-  public void cancel_pending_whenNodeNameNOtProvided_setNulInCeActivity() {
-    when(nodeInformation.getNodeName()).thenReturn(Optional.empty());
-    CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
-    CeQueueDto queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), task.getUuid()).get();
-
-    underTest.cancel(db.getSession(), queueDto);
-
-    Optional<CeActivityDto> activity = findCeActivityDtoInDb(task);
-    assertThat(activity).isPresent();
-    assertThat(activity.get().getNodeName()).isNull();
-  }
-
-  @Test
-  public void fail_to_cancel_if_in_progress() {
-    CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(11)));
-    CeQueueDto ceQueueDto = db.getDbClient().ceQueueDao().tryToPeek(session, task.getUuid(), WORKER_UUID).get();
-
-    assertThatThrownBy(() -> underTest.cancel(db.getSession(), ceQueueDto))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessageStartingWith("Task is in progress and can't be canceled");
-  }
-
-  @Test
-  public void cancelAll_pendings_but_not_in_progress() {
-    CeTask inProgressTask = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
-    CeTask pendingTask1 = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
-    CeTask pendingTask2 = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
-
-    db.getDbClient().ceQueueDao().tryToPeek(session, inProgressTask.getUuid(), WORKER_UUID);
-
-    int canceledCount = underTest.cancelAll();
-    assertThat(canceledCount).isEqualTo(2);
-
-    Optional<CeActivityDto> ceActivityInProgress = findCeActivityDtoInDb(pendingTask1);
-    assertThat(ceActivityInProgress.get().getStatus()).isEqualTo(CeActivityDto.Status.CANCELED);
-    Optional<CeActivityDto> ceActivityPending1 = findCeActivityDtoInDb(pendingTask2);
-    assertThat(ceActivityPending1.get().getStatus()).isEqualTo(CeActivityDto.Status.CANCELED);
-    Optional<CeActivityDto> ceActivityPending2 = findCeActivityDtoInDb(inProgressTask);
-    assertThat(ceActivityPending2).isNotPresent();
-  }
-
-  @Test
-  public void pauseWorkers_marks_workers_as_paused_if_zero_tasks_in_progress() {
-    submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
-    // task is pending
-
-    assertThat(underTest.getWorkersPauseStatus()).isEqualTo(CeQueue.WorkersPauseStatus.RESUMED);
-
-    underTest.pauseWorkers();
-    assertThat(underTest.getWorkersPauseStatus()).isEqualTo(CeQueue.WorkersPauseStatus.PAUSED);
-  }
-
-  @Test
-  public void pauseWorkers_marks_workers_as_pausing_if_some_tasks_in_progress() {
-    CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
-    db.getDbClient().ceQueueDao().tryToPeek(session, task.getUuid(), WORKER_UUID);
-    // task is in-progress
-
-    assertThat(underTest.getWorkersPauseStatus()).isEqualTo(CeQueue.WorkersPauseStatus.RESUMED);
-
-    underTest.pauseWorkers();
-    assertThat(underTest.getWorkersPauseStatus()).isEqualTo(CeQueue.WorkersPauseStatus.PAUSING);
-  }
-
-  @Test
-  public void resumeWorkers_does_nothing_if_not_paused() {
-    assertThat(underTest.getWorkersPauseStatus()).isEqualTo(CeQueue.WorkersPauseStatus.RESUMED);
-
-    underTest.resumeWorkers();
-
-    assertThat(underTest.getWorkersPauseStatus()).isEqualTo(CeQueue.WorkersPauseStatus.RESUMED);
-  }
-
-  @Test
-  public void resumeWorkers_resumes_pausing_workers() {
-    CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
-    db.getDbClient().ceQueueDao().tryToPeek(session, task.getUuid(), WORKER_UUID);
-    // task is in-progress
-
-    underTest.pauseWorkers();
-    assertThat(underTest.getWorkersPauseStatus()).isEqualTo(CeQueue.WorkersPauseStatus.PAUSING);
-
-    underTest.resumeWorkers();
-    assertThat(underTest.getWorkersPauseStatus()).isEqualTo(CeQueue.WorkersPauseStatus.RESUMED);
-  }
-
-  @Test
-  public void resumeWorkers_resumes_paused_workers() {
-    underTest.pauseWorkers();
-    assertThat(underTest.getWorkersPauseStatus()).isEqualTo(CeQueue.WorkersPauseStatus.PAUSED);
-
-    underTest.resumeWorkers();
-    assertThat(underTest.getWorkersPauseStatus()).isEqualTo(CeQueue.WorkersPauseStatus.RESUMED);
-  }
-
-  @Test
-  public void fail_in_progress_task() {
-    CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
-    CeQueueDto queueDto = db.getDbClient().ceQueueDao().tryToPeek(db.getSession(), task.getUuid(), WORKER_UUID).get();
-
-    underTest.fail(db.getSession(), queueDto, "TIMEOUT", "Failed on timeout");
-
-    Optional<CeActivityDto> activity = findCeActivityDtoInDb(task);
-    assertThat(activity).isPresent();
-    assertThat(activity.get().getStatus()).isEqualTo(CeActivityDto.Status.FAILED);
-    assertThat(activity.get().getErrorType()).isEqualTo("TIMEOUT");
-    assertThat(activity.get().getErrorMessage()).isEqualTo("Failed on timeout");
-    assertThat(activity.get().getExecutedAt()).isEqualTo(NOW);
-    assertThat(activity.get().getWorkerUuid()).isEqualTo(WORKER_UUID);
-    assertThat(activity.get().getNodeName()).isNull();
-  }
-
-  @Test
-  public void fail_in_progress_task_whenNodeNameProvided_setsItInCeActivityDto() {
-    when(nodeInformation.getNodeName()).thenReturn(Optional.of(NODE_NAME));
-    CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
-    CeQueueDto queueDto = db.getDbClient().ceQueueDao().tryToPeek(db.getSession(), task.getUuid(), WORKER_UUID).get();
-
-    underTest.fail(db.getSession(), queueDto, "TIMEOUT", "Failed on timeout");
-
-    Optional<CeActivityDto> activity = findCeActivityDtoInDb(task);
-    assertThat(activity).isPresent();
-    assertThat(activity.get().getNodeName()).isEqualTo(NODE_NAME);
-  }
-
-  private Optional<CeActivityDto> findCeActivityDtoInDb(CeTask task) {
-    return db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), task.getUuid());
-  }
-
-  @Test
-  public void fail_throws_exception_if_task_is_pending() {
-    CeTask task = submit(CeTaskTypes.REPORT, newComponent(randomAlphabetic(12)));
-    CeQueueDto queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), task.getUuid()).get();
-
-    Throwable thrown = catchThrowable(() -> underTest.fail(db.getSession(), queueDto, "TIMEOUT", "Failed on timeout"));
-
-    assertThat(thrown)
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Task is not in-progress and can't be marked as failed [uuid=" + task.getUuid() + "]");
-  }
-
-  private void verifyCeTask(CeTaskSubmit taskSubmit, CeTask task, @Nullable ComponentDto componentDto, UserDto userDto) {
-    verifyCeTask(taskSubmit, task, componentDto, componentDto, userDto);
-  }
-
-  private void verifyCeTask(CeTaskSubmit taskSubmit, CeTask task, @Nullable ComponentDto componentDto, @Nullable ComponentDto mainComponentDto, @Nullable UserDto userDto) {
-    assertThat(task.getUuid()).isEqualTo(taskSubmit.getUuid());
-    if (componentDto != null) {
-      CeTask.Component component = task.getComponent().get();
-      assertThat(component.getUuid()).isEqualTo(componentDto.uuid());
-      assertThat(component.getKey()).contains(componentDto.getKey());
-      assertThat(component.getName()).contains(componentDto.name());
-    } else if (taskSubmit.getComponent().isPresent()) {
-      assertThat(task.getComponent()).contains(new CeTask.Component(taskSubmit.getComponent().get().getUuid(), null, null));
-    } else {
-      assertThat(task.getComponent()).isEmpty();
-    }
-    if (mainComponentDto != null) {
-      CeTask.Component component = task.getMainComponent().get();
-      assertThat(component.getUuid()).isEqualTo(mainComponentDto.uuid());
-      assertThat(component.getKey()).contains(mainComponentDto.getKey());
-      assertThat(component.getName()).contains(mainComponentDto.name());
-    } else if (taskSubmit.getComponent().isPresent()) {
-      assertThat(task.getMainComponent()).contains(new CeTask.Component(taskSubmit.getComponent().get().getMainComponentUuid(), null, null));
-    } else {
-      assertThat(task.getMainComponent()).isEmpty();
-    }
-    assertThat(task.getType()).isEqualTo(taskSubmit.getType());
-    if (taskSubmit.getSubmitterUuid() != null) {
-      if (userDto == null) {
-        assertThat(task.getSubmitter().uuid()).isEqualTo(taskSubmit.getSubmitterUuid());
-        assertThat(task.getSubmitter().login()).isNull();
-      } else {
-        assertThat(task.getSubmitter().uuid()).isEqualTo(userDto.getUuid()).isEqualTo(taskSubmit.getSubmitterUuid());
-        assertThat(task.getSubmitter().login()).isEqualTo(userDto.getLogin());
-      }
-    }
-  }
-
-  private void verifyCeQueueDtoForTaskSubmit(CeTaskSubmit taskSubmit) {
-    Optional<CeQueueDto> queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), taskSubmit.getUuid());
-    assertThat(queueDto).isPresent();
-    assertThat(queueDto.get().getTaskType()).isEqualTo(taskSubmit.getType());
-    Optional<Component> component = taskSubmit.getComponent();
-    if (component.isPresent()) {
-      assertThat(queueDto.get().getComponentUuid()).isEqualTo(component.get().getUuid());
-      assertThat(queueDto.get().getMainComponentUuid()).isEqualTo(component.get().getMainComponentUuid());
-    } else {
-      assertThat(queueDto.get().getComponentUuid()).isNull();
-      assertThat(queueDto.get().getComponentUuid()).isNull();
-    }
-    assertThat(queueDto.get().getSubmitterUuid()).isEqualTo(taskSubmit.getSubmitterUuid());
-    assertThat(queueDto.get().getCreatedAt()).isEqualTo(1_450_000_000_000L);
-  }
-
-  private CeTask submit(String reportType, Component component) {
-    return underTest.submit(createTaskSubmit(reportType, component, null));
-  }
-
-  private CeTaskSubmit createTaskSubmit(String type) {
-    return createTaskSubmit(type, null, null);
-  }
-
-  private CeTaskSubmit createTaskSubmit(String type, @Nullable Component component, @Nullable String submitterUuid) {
-    return underTest.prepareSubmit()
-      .setType(type)
-      .setComponent(component)
-      .setSubmitterUuid(submitterUuid)
-      .setCharacteristics(emptyMap())
-      .build();
-  }
-
-  private ComponentDto insertComponent(ComponentDto componentDto) {
-    return db.components().insertComponent(componentDto);
-  }
-
-  private UserDto insertUser(UserDto userDto) {
-    db.getDbClient().userDao().insert(session, userDto);
-    session.commit();
-    return userDto;
-  }
-
-  private CeQueueDto insertPendingInQueue(@Nullable Component component) {
-    CeQueueDto dto = new CeQueueDto()
-      .setUuid(UuidFactoryFast.getInstance().create())
-      .setTaskType("some type")
-      .setStatus(CeQueueDto.Status.PENDING);
-    if (component != null) {
-      dto.setComponentUuid(component.getUuid())
-        .setMainComponentUuid(component.getMainComponentUuid());
-    }
-    db.getDbClient().ceQueueDao().insert(db.getSession(), dto);
-    db.commit();
-    return dto;
-  }
-
-  private static int newComponentIdGenerator = new Random().nextInt(8_999_333);
-
-  private static Component newComponent(String mainComponentUuid) {
-    return new Component("uuid_" + newComponentIdGenerator++, mainComponentUuid);
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactoryImplIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactoryImplIT.java
new file mode 100644 (file)
index 0000000..710f388
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * 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 org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.projectanalysis.analysis.Branch;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.ComponentDto;
+
+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.BranchDto.DEFAULT_MAIN_BRANCH_NAME;
+
+public class ComponentUuidFactoryImplIT {
+  private final Branch mainBranch = new DefaultBranchImpl(DEFAULT_MAIN_BRANCH_NAME);
+  private final Branch mockedBranch = mock(Branch.class);
+
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+
+  @Test
+  public void getOrCreateForKey_when_existingComponentsInDbForMainBranch_should_load() {
+    ComponentDto project = db.components().insertPrivateProject();
+
+    ComponentUuidFactory underTest = new ComponentUuidFactoryImpl(db.getDbClient(), db.getSession(), project.getKey(), mainBranch);
+
+    assertThat(underTest.getOrCreateForKey(project.getKey())).isEqualTo(project.uuid());
+  }
+
+  @Test
+  public void getOrCreateForKey_when_existingComponentsInDbForNonMainBranch_should_load() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("b1"));
+    when(mockedBranch.getType()).thenReturn(BranchType.BRANCH);
+    when(mockedBranch.isMain()).thenReturn(false);
+    when(mockedBranch.getName()).thenReturn("b1");
+
+    ComponentUuidFactory underTest = new ComponentUuidFactoryImpl(db.getDbClient(), db.getSession(), project.getKey(), mockedBranch);
+
+    assertThat(underTest.getOrCreateForKey(project.getKey())).isEqualTo(branch.uuid());
+  }
+
+  @Test
+  public void getOrCreateForKey_when_existingComponentsInDbForPr_should_load() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto pr = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.PULL_REQUEST).setKey("pr1"));
+    when(mockedBranch.getType()).thenReturn(BranchType.PULL_REQUEST);
+    when(mockedBranch.isMain()).thenReturn(false);
+    when(mockedBranch.getPullRequestKey()).thenReturn("pr1");
+
+    ComponentUuidFactory underTest = new ComponentUuidFactoryImpl(db.getDbClient(), db.getSession(), project.getKey(), mockedBranch);
+
+    assertThat(underTest.getOrCreateForKey(project.getKey())).isEqualTo(pr.uuid());
+  }
+
+  @Test
+  public void getOrCreateForKey_when_componentsNotInDb_should_generate() {
+    ComponentUuidFactory underTest = new ComponentUuidFactoryImpl(db.getDbClient(), db.getSession(), "theProjectKey", mainBranch);
+
+    String generatedKey = underTest.getOrCreateForKey("foo");
+    assertThat(generatedKey).isNotEmpty();
+
+    // uuid is kept in memory for further calls with same key
+    assertThat(underTest.getOrCreateForKey("foo")).isEqualTo(generatedKey);
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/component/ProjectPersisterIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/component/ProjectPersisterIT.java
new file mode 100644 (file)
index 0000000..a2ff4f3
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * 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 org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.impl.utils.TestSystem2;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.db.DbTester;
+import org.sonar.db.project.ProjectDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT;
+import static org.sonar.ce.task.projectanalysis.component.Component.Type.VIEW;
+import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
+
+public class ProjectPersisterIT {
+  private final static Component ROOT = builder(PROJECT, 1)
+    .setUuid("PROJECT_UUID")
+    .setKey("PROJECT_KEY")
+    .setDescription("PROJECT_DESC")
+    .setName("PROJECT_NAME")
+    .build();
+
+  @Rule
+  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+  public TestSystem2 system2 = new TestSystem2();
+
+  private ProjectPersister underTest = new ProjectPersister(dbTester.getDbClient(), treeRootHolder, system2);
+
+  @Before
+  public void prepare() {
+    treeRootHolder.setRoot(ROOT);
+    system2.setNow(1000L);
+  }
+
+  @Test
+  public void skip_portfolios() {
+    Component root = ViewsComponent.builder(VIEW, 1).build();
+    TreeRootHolder treeRootHolder = mock(TreeRootHolder.class);
+    when(treeRootHolder.getRoot()).thenReturn(root);
+    new ProjectPersister(dbTester.getDbClient(), treeRootHolder, system2).persist(dbTester.getSession());
+    verify(treeRootHolder).getRoot();
+    verifyNoMoreInteractions(treeRootHolder);
+
+  }
+
+  @Test
+  public void update_description() {
+    ProjectDto p1 = dbTester.components().insertPublicProjectDto(
+      c -> c.setUuid("PROJECT_UUID").setKey(ROOT.getKey()).setName(ROOT.getName()).setDescription("OLD_DESC"));
+
+    assertProject("OLD_DESC", ROOT.getName(), p1.getUpdatedAt());
+    underTest.persist(dbTester.getSession());
+    assertProject(ROOT.getDescription(), ROOT.getName(), 1000L);
+  }
+
+  @Test
+  public void update_name() {
+    ProjectDto p1 = dbTester.components().insertPublicProjectDto(
+      c -> c.setUuid("PROJECT_UUID").setKey(ROOT.getKey()).setName("OLD_NAME").setDescription(ROOT.getDescription()));
+
+    assertProject(ROOT.getDescription(), "OLD_NAME", p1.getUpdatedAt());
+    underTest.persist(dbTester.getSession());
+    assertProject(ROOT.getDescription(), ROOT.getName(), 1000L);
+  }
+
+  @Test
+  public void dont_update() {
+    ProjectDto p1 = dbTester.components().insertPublicProjectDto(
+      c -> c.setUuid("PROJECT_UUID").setKey(ROOT.getKey()).setName(ROOT.getName()).setDescription(ROOT.getDescription()));
+
+    assertProject(ROOT.getDescription(), ROOT.getName(), p1.getUpdatedAt());
+    underTest.persist(dbTester.getSession());
+    assertProject(ROOT.getDescription(), ROOT.getName(), p1.getUpdatedAt());
+  }
+
+  private void assertProject(String description, String name, long updated) {
+    assertThat(dbTester.getDbClient().projectDao().selectProjectByKey(dbTester.getSession(), ROOT.getKey()).get())
+      .extracting(ProjectDto::getName, ProjectDto::getDescription, ProjectDto::getUpdatedAt)
+      .containsExactly(name, description, updated);
+
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/component/ReferenceBranchComponentUuidsIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/component/ReferenceBranchComponentUuidsIT.java
new file mode 100644 (file)
index 0000000..147cfc8
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * 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 org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.ce.task.projectanalysis.analysis.Branch;
+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.server.project.Project;
+
+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.SnapshotTesting.newAnalysis;
+
+public class ReferenceBranchComponentUuidsIT {
+  @Rule
+  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
+
+  @Rule
+  public DbTester db = DbTester.create();
+
+  private ReferenceBranchComponentUuids underTest;
+  private Branch branch = mock(Branch.class);
+
+  private ComponentDto branch1;
+  private ComponentDto branch1File;
+  private ComponentDto pr1File;
+  private ComponentDto pr2File;
+  private Project project;
+  private ComponentDto pr1;
+  private ComponentDto pr2;
+  private ComponentDto branch2;
+  private ComponentDto branch2File;
+
+  @Before
+  public void setUp() {
+    underTest = new ReferenceBranchComponentUuids(analysisMetadataHolder, db.getDbClient());
+    project = mock(Project.class);
+    analysisMetadataHolder.setProject(project);
+    analysisMetadataHolder.setBranch(branch);
+
+    ComponentDto projectDto = db.components().insertPublicProject();
+    when(project.getUuid()).thenReturn(projectDto.uuid());
+    branch1 = db.components().insertProjectBranch(projectDto, b -> b.setKey("branch1"));
+    branch2 = db.components().insertProjectBranch(projectDto, b -> b.setKey("branch2"));
+    pr1 = db.components().insertProjectBranch(projectDto, b -> b.setKey("pr1").setBranchType(BranchType.PULL_REQUEST).setMergeBranchUuid(branch1.uuid()));
+    pr2 = db.components().insertProjectBranch(projectDto, b -> b.setKey("pr2").setBranchType(BranchType.PULL_REQUEST).setMergeBranchUuid(branch1.uuid()));
+    branch1File = ComponentTesting.newFileDto(branch1, null, "file").setUuid("branch1File");
+    branch2File = ComponentTesting.newFileDto(branch2, null, "file").setUuid("branch2File");
+    pr1File = ComponentTesting.newFileDto(pr1, null, "file").setUuid("file1");
+    pr2File = ComponentTesting.newFileDto(pr2, null, "file").setUuid("file2");
+    db.components().insertComponents(branch1File, pr1File, pr2File, branch2File);
+  }
+
+  @Test
+  public void should_support_db_key_when_looking_for_reference_component() {
+    when(branch.getReferenceBranchUuid()).thenReturn(branch1.uuid());
+    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
+    when(branch.getTargetBranchName()).thenReturn("notAnalyzedBranch");
+    db.components().insertSnapshot(newAnalysis(branch1));
+    assertThat(underTest.getComponentUuid(pr1File.getKey())).isEqualTo(branch1File.uuid());
+    assertThat(underTest.hasReferenceBranchAnalysis()).isTrue();
+    assertThat(underTest.getReferenceBranchName()).isEqualTo("branch1");
+  }
+
+  @Test
+  public void should_support_key_when_looking_for_reference_component() {
+    when(branch.getReferenceBranchUuid()).thenReturn(branch1.uuid());
+    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
+    when(branch.getTargetBranchName()).thenReturn("notAnalyzedBranch");
+    db.components().insertSnapshot(newAnalysis(branch1));
+    assertThat(underTest.getComponentUuid(pr1File.getKey())).isEqualTo(branch1File.uuid());
+  }
+
+  @Test
+  public void return_null_if_file_doesnt_exist() {
+    when(branch.getReferenceBranchUuid()).thenReturn(branch1.uuid());
+    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
+    when(branch.getTargetBranchName()).thenReturn("notAnalyzedBranch");
+    db.components().insertSnapshot(newAnalysis(branch1));
+    assertThat(underTest.getComponentUuid("doesnt exist")).isNull();
+  }
+
+  @Test
+  public void skip_init_if_no_reference_branch_analysis() {
+    when(branch.getReferenceBranchUuid()).thenReturn(branch1.uuid());
+    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
+    when(branch.getTargetBranchName()).thenReturn("notAnalyzedBranch");
+    assertThat(underTest.getComponentUuid(pr1File.getKey())).isNull();
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/component/SiblingComponentsWithOpenIssuesIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/component/SiblingComponentsWithOpenIssuesIT.java
new file mode 100644 (file)
index 0000000..b35f634
--- /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.component;
+
+import java.util.Collections;
+import javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.ce.task.projectanalysis.analysis.Branch;
+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.rule.RuleDto;
+import org.sonar.server.project.Project;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class SiblingComponentsWithOpenIssuesIT {
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+
+  @Rule
+  public AnalysisMetadataHolderRule metadataHolder = new AnalysisMetadataHolderRule();
+
+  @Rule
+  public DbTester db = DbTester.create();
+
+  private SiblingComponentsWithOpenIssues underTest;
+
+  private ComponentDto branch1;
+  private ComponentDto fileWithNoIssuesOnBranch1;
+  private ComponentDto fileWithOneOpenIssueOnBranch1Pr1;
+  private ComponentDto fileWithOneResolvedIssueOnBranch1Pr1;
+  private ComponentDto fileWithOneOpenTwoResolvedIssuesOnBranch1Pr1;
+  private ComponentDto fileXWithOneResolvedIssueOnBranch1Pr1;
+  private ComponentDto fileXWithOneResolvedIssueOnBranch1Pr2;
+
+  private ComponentDto branch2;
+  private ComponentDto fileWithOneOpenIssueOnBranch2Pr1;
+  private ComponentDto fileWithOneResolvedIssueOnBranch2Pr1;
+  private ComponentDto branch1pr1;
+
+  @Before
+  public void setUp() {
+    ComponentDto project = db.components().insertPublicProject();
+    metadataHolder.setProject(new Project(project.uuid(), project.getKey(), project.name(), project.description(), Collections.emptyList()));
+
+    branch1 = db.components().insertProjectBranch(project, b -> b.setKey("branch1"), b -> b.setBranchType(BranchType.BRANCH));
+    branch1pr1 = db.components().insertProjectBranch(project,
+      b -> b.setKey("branch1pr1"),
+      b -> b.setBranchType(BranchType.PULL_REQUEST),
+      b -> b.setMergeBranchUuid(branch1.uuid()));
+    ComponentDto branch1pr2 = db.components().insertProjectBranch(project,
+      b -> b.setKey("branch1pr2"),
+      b -> b.setBranchType(BranchType.PULL_REQUEST),
+      b -> b.setMergeBranchUuid(branch1.uuid()));
+
+    fileWithNoIssuesOnBranch1 = db.components().insertComponent(ComponentTesting.newFileDto(branch1, null));
+
+    RuleDto rule = db.rules().insert();
+
+    fileWithOneOpenIssueOnBranch1Pr1 = db.components().insertComponent(ComponentTesting.newFileDto(branch1pr1, null));
+    db.issues().insert(rule, branch1pr1, fileWithOneOpenIssueOnBranch1Pr1);
+
+    fileWithOneResolvedIssueOnBranch1Pr1 = db.components().insertComponent(ComponentTesting.newFileDto(branch1pr1, null));
+    db.issues().insert(rule, branch1pr1, fileWithOneResolvedIssueOnBranch1Pr1, i -> i.setStatus("RESOLVED"));
+
+    fileWithOneOpenTwoResolvedIssuesOnBranch1Pr1 = db.components().insertComponent(ComponentTesting.newFileDto(branch1pr1, null));
+    db.issues().insert(rule, branch1pr1, fileWithOneOpenTwoResolvedIssuesOnBranch1Pr1);
+    db.issues().insert(rule, branch1pr1, fileWithOneOpenTwoResolvedIssuesOnBranch1Pr1, i -> i.setStatus("RESOLVED"));
+
+    String fileKey = "file-x";
+    fileXWithOneResolvedIssueOnBranch1Pr1 = db.components().insertComponent(ComponentTesting.newFileDto(branch1pr1, null)
+      .setKey(fileKey));
+    db.issues().insert(rule, branch1pr1, fileXWithOneResolvedIssueOnBranch1Pr1, i -> i.setStatus("RESOLVED"));
+
+    fileXWithOneResolvedIssueOnBranch1Pr2 = db.components().insertComponent(ComponentTesting.newFileDto(branch1pr2, null)
+      .setKey(fileKey));
+    db.issues().insert(rule, branch1pr2, fileXWithOneResolvedIssueOnBranch1Pr2, i -> i.setStatus("RESOLVED"));
+
+    branch2 = db.components().insertProjectBranch(project, b -> b.setKey("branch2"), b -> b.setBranchType(BranchType.BRANCH));
+    ComponentDto branch2pr1 = db.components().insertProjectBranch(project,
+      b -> b.setKey("branch2pr1"),
+      b -> b.setBranchType(BranchType.PULL_REQUEST),
+      b -> b.setMergeBranchUuid(branch2.uuid()));
+
+    fileWithOneOpenIssueOnBranch2Pr1 = db.components().insertComponent(ComponentTesting.newFileDto(branch2pr1, null));
+    db.issues().insert(rule, branch2pr1, fileWithOneOpenIssueOnBranch2Pr1);
+
+    fileWithOneResolvedIssueOnBranch2Pr1 = db.components().insertComponent(ComponentTesting.newFileDto(branch2pr1, null));
+    db.issues().insert(rule, branch2pr1, fileWithOneResolvedIssueOnBranch2Pr1, i -> i.setStatus("RESOLVED"));
+    setRoot(branch1);
+    underTest = new SiblingComponentsWithOpenIssues(treeRootHolder, metadataHolder, db.getDbClient());
+  }
+
+  @Test
+  public void should_find_sibling_components_with_open_issues_for_branch1() {
+    setRoot(branch1);
+    setBranch(BranchType.BRANCH);
+
+    assertThat(underTest.getUuids(fileWithNoIssuesOnBranch1.getKey())).isEmpty();
+    assertThat(underTest.getUuids(fileWithOneOpenIssueOnBranch1Pr1.getKey())).containsOnly(fileWithOneOpenIssueOnBranch1Pr1.uuid());
+    assertThat(underTest.getUuids(fileWithOneResolvedIssueOnBranch1Pr1.getKey())).containsOnly(fileWithOneResolvedIssueOnBranch1Pr1.uuid());
+    assertThat(underTest.getUuids(fileWithOneOpenTwoResolvedIssuesOnBranch1Pr1.getKey())).containsOnly(fileWithOneOpenTwoResolvedIssuesOnBranch1Pr1.uuid());
+
+    assertThat(fileXWithOneResolvedIssueOnBranch1Pr1.getKey()).isEqualTo(fileXWithOneResolvedIssueOnBranch1Pr2.getKey());
+    assertThat(underTest.getUuids(fileXWithOneResolvedIssueOnBranch1Pr1.getKey())).containsOnly(
+      fileXWithOneResolvedIssueOnBranch1Pr1.uuid(),
+      fileXWithOneResolvedIssueOnBranch1Pr2.uuid());
+  }
+
+  @Test
+  public void should_find_sibling_components_with_open_issues_for_pr1() {
+    setRoot(branch1pr1);
+    setBranch(BranchType.PULL_REQUEST, branch1.uuid());
+
+    assertThat(underTest.getUuids(fileWithNoIssuesOnBranch1.getKey())).isEmpty();
+    assertThat(underTest.getUuids(fileWithOneOpenIssueOnBranch1Pr1.getKey())).isEmpty();
+    assertThat(underTest.getUuids(fileWithOneResolvedIssueOnBranch1Pr1.getKey())).isEmpty();
+    assertThat(underTest.getUuids(fileWithOneOpenTwoResolvedIssuesOnBranch1Pr1.getKey())).isEmpty();
+
+    assertThat(underTest.getUuids(fileXWithOneResolvedIssueOnBranch1Pr1.getKey())).containsOnly(fileXWithOneResolvedIssueOnBranch1Pr2.uuid());
+  }
+
+  @Test
+  public void should_find_sibling_components_with_open_issues_for_branch2() {
+    setRoot(branch2);
+    setBranch(BranchType.BRANCH);
+
+    underTest = new SiblingComponentsWithOpenIssues(treeRootHolder, metadataHolder, db.getDbClient());
+
+    assertThat(underTest.getUuids(fileWithOneResolvedIssueOnBranch1Pr1.getKey())).isEmpty();
+    assertThat(underTest.getUuids(fileWithOneResolvedIssueOnBranch2Pr1.getKey())).containsOnly(fileWithOneResolvedIssueOnBranch2Pr1.uuid());
+    assertThat(underTest.getUuids(fileWithOneOpenIssueOnBranch2Pr1.getKey())).containsOnly(fileWithOneOpenIssueOnBranch2Pr1.uuid());
+  }
+
+  @Test
+  public void should_find_sibling_components_with_open_issues_from_pullrequest() {
+    ComponentDto project = db.components().insertPublicProject();
+    setRoot(project);
+    setBranch(BranchType.BRANCH);
+
+    ComponentDto pullRequest = db.components().insertProjectBranch(project,
+      b -> b.setBranchType(BranchType.PULL_REQUEST),
+      b -> b.setMergeBranchUuid(project.uuid()));
+
+    RuleDto rule = db.rules().insert();
+
+    ComponentDto fileWithResolvedIssueOnPullrequest = db.components().insertComponent(ComponentTesting.newFileDto(pullRequest, null));
+    db.issues().insert(rule, pullRequest, fileWithResolvedIssueOnPullrequest, i -> i.setStatus("RESOLVED"));
+    underTest = new SiblingComponentsWithOpenIssues(treeRootHolder, metadataHolder, db.getDbClient());
+
+    assertThat(underTest.getUuids(fileWithResolvedIssueOnPullrequest.getKey())).hasSize(1);
+  }
+
+  @Test
+  public void should_not_find_sibling_components_on_derived_branch() {
+    ComponentDto project = db.components().insertPublicProject();
+    setRoot(project);
+    setBranch(BranchType.BRANCH);
+
+    ComponentDto derivedBranch = db.components().insertProjectBranch(project,
+      b -> b.setBranchType(BranchType.BRANCH),
+      b -> b.setMergeBranchUuid(project.uuid()));
+
+    RuleDto rule = db.rules().insert();
+
+    ComponentDto fileWithResolvedIssueOnDerivedBranch = db.components().insertComponent(ComponentTesting.newFileDto(derivedBranch, null));
+    db.issues().insert(rule, derivedBranch, fileWithResolvedIssueOnDerivedBranch, i -> i.setStatus("RESOLVED"));
+
+    underTest = new SiblingComponentsWithOpenIssues(treeRootHolder, metadataHolder, db.getDbClient());
+
+    assertThat(underTest.getUuids(fileWithResolvedIssueOnDerivedBranch.getKey())).isEmpty();
+  }
+
+  private void setRoot(ComponentDto componentDto) {
+    Component root = mock(Component.class);
+    when(root.getUuid()).thenReturn(componentDto.uuid());
+    treeRootHolder.setRoot(root);
+  }
+
+  private void setBranch(BranchType currentBranchType) {
+    setBranch(currentBranchType, null);
+  }
+
+  private void setBranch(BranchType currentBranchType, @Nullable String mergeBranchUuid) {
+    Branch branch = mock(Branch.class);
+    when(branch.getType()).thenReturn(currentBranchType);
+    when(branch.getReferenceBranchUuid()).thenReturn(mergeBranchUuid);
+    metadataHolder.setBranch(branch);
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/AdHocRuleCreatorIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/AdHocRuleCreatorIT.java
new file mode 100644 (file)
index 0000000..df05ba8
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * 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.issue;
+
+import org.junit.Test;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleType;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.SequenceUuidFactory;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.scanner.protocol.Constants;
+import org.sonar.scanner.protocol.output.ScannerReport;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.rule.index.RuleIndexer;
+
+import static org.apache.commons.lang.StringUtils.repeat;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AdHocRuleCreatorIT {
+
+  @org.junit.Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+  @org.junit.Rule
+  public EsTester es = EsTester.create();
+
+  private RuleIndexer indexer = new RuleIndexer(es.client(), db.getDbClient());
+  private AdHocRuleCreator underTest = new AdHocRuleCreator(db.getDbClient(), System2.INSTANCE, indexer, new SequenceUuidFactory());
+  private DbSession dbSession = db.getSession();
+
+  @Test
+  public void create_ad_hoc_rule_from_issue() {
+    NewAdHocRule addHocRule = new NewAdHocRule(ScannerReport.ExternalIssue.newBuilder().setEngineId("eslint").setRuleId("no-cond-assign").build());
+
+    RuleDto rule = underTest.persistAndIndex(dbSession, addHocRule);
+
+    assertThat(rule).isNotNull();
+    assertThat(rule.isExternal()).isTrue();
+    assertThat(rule.isAdHoc()).isTrue();
+    assertThat(rule.getUuid()).isNotBlank();
+    assertThat(rule.getKey()).isEqualTo(RuleKey.of("external_eslint", "no-cond-assign"));
+    assertThat(rule.getName()).isEqualTo("eslint:no-cond-assign");
+    assertThat(rule.getRuleDescriptionSectionDtos()).isEmpty();
+    assertThat(rule.getSeverity()).isNull();
+    assertThat(rule.getType()).isZero();
+    assertThat(rule.getAdHocName()).isNull();
+    assertThat(rule.getAdHocDescription()).isNull();
+    assertThat(rule.getAdHocSeverity()).isNull();
+    assertThat(rule.getAdHocType()).isNull();
+  }
+
+  @Test
+  public void create_ad_hoc_rule_from_scanner_report() {
+    NewAdHocRule addHocRule = new NewAdHocRule(ScannerReport.AdHocRule.newBuilder()
+      .setEngineId("eslint")
+      .setRuleId("no-cond-assign")
+      .setName("No condition assigned")
+      .setDescription("A description")
+      .setSeverity(Constants.Severity.BLOCKER)
+      .setType(ScannerReport.IssueType.BUG)
+      .build());
+
+    RuleDto rule = underTest.persistAndIndex(dbSession, addHocRule);
+
+    assertThat(rule).isNotNull();
+    assertThat(rule.isExternal()).isTrue();
+    assertThat(rule.isAdHoc()).isTrue();
+    assertThat(rule.getUuid()).isNotBlank();
+    assertThat(rule.getKey()).isEqualTo(RuleKey.of("external_eslint", "no-cond-assign"));
+    assertThat(rule.getName()).isEqualTo("eslint:no-cond-assign");
+    assertThat(rule.getRuleDescriptionSectionDtos()).isEmpty();
+    assertThat(rule.getSeverity()).isNull();
+    assertThat(rule.getType()).isZero();
+    assertThat(rule.getAdHocName()).isEqualTo("No condition assigned");
+    assertThat(rule.getAdHocDescription()).isEqualTo("A description");
+    assertThat(rule.getAdHocSeverity()).isEqualTo(Severity.BLOCKER);
+    assertThat(rule.getAdHocType()).isEqualTo(RuleType.BUG.getDbConstant());
+  }
+
+  @Test
+  public void truncate_metadata_name_and_desc_if_longer_than_max_value() {
+    NewAdHocRule addHocRule = new NewAdHocRule(ScannerReport.AdHocRule.newBuilder()
+      .setEngineId("eslint")
+      .setRuleId("no-cond-assign")
+      .setName(repeat("a", 201))
+      .setDescription(repeat("a", 16_777_216))
+      .setSeverity(Constants.Severity.BLOCKER)
+      .setType(ScannerReport.IssueType.BUG)
+      .build());
+
+    RuleDto rule = underTest.persistAndIndex(dbSession, addHocRule);
+
+    assertThat(rule.getAdHocName()).isEqualTo(repeat("a", 200));
+    assertThat(rule.getAdHocDescription()).isEqualTo(repeat("a", 16_777_215));
+  }
+
+  @Test
+  public void update_metadata_only() {
+    NewAdHocRule addHocRule = new NewAdHocRule(ScannerReport.AdHocRule.newBuilder()
+      .setEngineId("eslint")
+      .setRuleId("no-cond-assign")
+      .setName("No condition assigned")
+      .setDescription("A description")
+      .setSeverity(Constants.Severity.BLOCKER)
+      .setType(ScannerReport.IssueType.BUG)
+      .build());
+    RuleDto rule = underTest.persistAndIndex(dbSession, addHocRule);
+    long creationDate = rule.getCreatedAt();
+    NewAdHocRule addHocRuleUpdated = new NewAdHocRule(ScannerReport.AdHocRule.newBuilder()
+      .setEngineId("eslint")
+      .setRuleId("no-cond-assign")
+      .setName("No condition assigned updated")
+      .setDescription("A description updated")
+      .setSeverity(Constants.Severity.CRITICAL)
+      .setType(ScannerReport.IssueType.CODE_SMELL)
+      .build());
+
+    RuleDto ruleUpdated = underTest.persistAndIndex(dbSession, addHocRuleUpdated);
+
+    assertThat(ruleUpdated).isNotNull();
+    assertThat(ruleUpdated.isExternal()).isTrue();
+    assertThat(ruleUpdated.isAdHoc()).isTrue();
+    assertThat(ruleUpdated.getUuid()).isNotBlank();
+    assertThat(ruleUpdated.getKey()).isEqualTo(RuleKey.of("external_eslint", "no-cond-assign"));
+    assertThat(ruleUpdated.getName()).isEqualTo("eslint:no-cond-assign");
+    assertThat(ruleUpdated.getRuleDescriptionSectionDtos()).isEmpty();
+    assertThat(ruleUpdated.getSeverity()).isNull();
+    assertThat(ruleUpdated.getType()).isZero();
+    assertThat(ruleUpdated.getAdHocName()).isEqualTo("No condition assigned updated");
+    assertThat(ruleUpdated.getAdHocDescription()).isEqualTo("A description updated");
+    assertThat(ruleUpdated.getAdHocSeverity()).isEqualTo(Severity.CRITICAL);
+    assertThat(ruleUpdated.getAdHocType()).isEqualTo(RuleType.CODE_SMELL.getDbConstant());
+    assertThat(ruleUpdated.getCreatedAt()).isEqualTo(creationDate);
+    assertThat(ruleUpdated.getUpdatedAt()).isGreaterThan(creationDate);
+  }
+
+  @Test
+  public void does_not_update_rule_when_no_change() {
+    RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("external_eslint").setIsExternal(true).setIsAdHoc(true));
+
+    RuleDto ruleUpdated = underTest.persistAndIndex(dbSession, new NewAdHocRule(ScannerReport.AdHocRule.newBuilder()
+      .setEngineId("eslint")
+      .setRuleId(rule.getKey().rule())
+      .setName(rule.getAdHocName())
+      .setDescription(rule.getAdHocDescription())
+      .setSeverity(Constants.Severity.valueOf(rule.getAdHocSeverity()))
+      .setType(ScannerReport.IssueType.forNumber(rule.getAdHocType()))
+      .build()));
+
+    assertThat(ruleUpdated).isNotNull();
+    assertThat(ruleUpdated.isExternal()).isTrue();
+    assertThat(ruleUpdated.isAdHoc()).isTrue();
+    assertThat(ruleUpdated.getKey()).isEqualTo(rule.getKey());
+    assertThat(ruleUpdated.getName()).isEqualTo(rule.getName());
+    assertThat(ruleUpdated.getRuleDescriptionSectionDtos()).usingRecursiveFieldByFieldElementComparator().isEqualTo(rule.getRuleDescriptionSectionDtos());
+    assertThat(ruleUpdated.getSeverity()).isEqualTo(rule.getSeverity());
+    assertThat(ruleUpdated.getType()).isEqualTo(rule.getType());
+    assertThat(ruleUpdated.getCreatedAt()).isEqualTo(rule.getCreatedAt());
+    assertThat(ruleUpdated.getUpdatedAt()).isEqualTo(rule.getUpdatedAt());
+
+    assertThat(ruleUpdated.getAdHocName()).isEqualTo(rule.getAdHocName());
+    assertThat(ruleUpdated.getAdHocDescription()).isEqualTo(rule.getAdHocDescription());
+    assertThat(ruleUpdated.getAdHocSeverity()).isEqualTo(rule.getAdHocSeverity());
+    assertThat(ruleUpdated.getAdHocType()).isEqualTo(rule.getAdHocType());
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/ClosedIssuesInputFactoryIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/ClosedIssuesInputFactoryIT.java
new file mode 100644 (file)
index 0000000..8d226df
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * 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.issue;
+
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.Optional;
+import org.junit.Test;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.ReportComponent;
+import org.sonar.ce.task.projectanalysis.filemove.MovedFilesRepository;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.tracking.Input;
+import org.sonar.db.DbClient;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+public class ClosedIssuesInputFactoryIT {
+  private ComponentIssuesLoader issuesLoader = mock(ComponentIssuesLoader.class);
+  private DbClient dbClient = mock(DbClient.class);
+  private MovedFilesRepository movedFilesRepository = mock(MovedFilesRepository.class);
+  private ClosedIssuesInputFactory underTest = new ClosedIssuesInputFactory(issuesLoader, dbClient, movedFilesRepository);
+
+  @Test
+  public void underTest_returns_inputFactory_loading_closed_issues_only_when_getIssues_is_called() {
+    String componentUuid = randomAlphanumeric(12);
+    ReportComponent component = ReportComponent.builder(Component.Type.FILE, 1).setUuid(componentUuid).build();
+    when(movedFilesRepository.getOriginalFile(component)).thenReturn(Optional.empty());
+
+    Input<DefaultIssue> input = underTest.create(component);
+
+    verifyNoInteractions(dbClient, issuesLoader);
+
+    List<DefaultIssue> issues = ImmutableList.of(new DefaultIssue(), new DefaultIssue());
+    when(issuesLoader.loadClosedIssues(componentUuid)).thenReturn(issues);
+
+    assertThat(input.getIssues()).isSameAs(issues);
+  }
+
+  @Test
+  public void underTest_returns_inputFactory_loading_closed_issues_from_moved_component_when_present() {
+    String componentUuid = randomAlphanumeric(12);
+    String originalComponentUuid = randomAlphanumeric(12);
+    ReportComponent component = ReportComponent.builder(Component.Type.FILE, 1).setUuid(componentUuid).build();
+    when(movedFilesRepository.getOriginalFile(component))
+      .thenReturn(Optional.of(new MovedFilesRepository.OriginalFile(originalComponentUuid, randomAlphanumeric(2))));
+
+    Input<DefaultIssue> input = underTest.create(component);
+
+    verifyNoInteractions(dbClient, issuesLoader);
+
+    List<DefaultIssue> issues = ImmutableList.of();
+    when(issuesLoader.loadClosedIssues(originalComponentUuid)).thenReturn(issues);
+
+    assertThat(input.getIssues()).isSameAs(issues);
+  }
+
+  @Test
+  public void underTest_returns_inputFactory_which_caches_loaded_issues() {
+    String componentUuid = randomAlphanumeric(12);
+    ReportComponent component = ReportComponent.builder(Component.Type.FILE, 1).setUuid(componentUuid).build();
+    when(movedFilesRepository.getOriginalFile(component)).thenReturn(Optional.empty());
+
+    Input<DefaultIssue> input = underTest.create(component);
+
+    verifyNoInteractions(dbClient, issuesLoader);
+
+    List<DefaultIssue> issues = ImmutableList.of(new DefaultIssue());
+    when(issuesLoader.loadClosedIssues(componentUuid)).thenReturn(issues);
+
+    assertThat(input.getIssues()).isSameAs(issues);
+
+    reset(issuesLoader);
+
+    assertThat(input.getIssues()).isSameAs(issues);
+    verifyNoInteractions(issuesLoader);
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesLoaderIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesLoaderIT.java
new file mode 100644 (file)
index 0000000..364a984
--- /dev/null
@@ -0,0 +1,441 @@
+/*
+ * 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.issue;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Random;
+import java.util.function.Consumer;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.utils.System2;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.DefaultIssueComment;
+import org.sonar.core.issue.FieldDiffs;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.issue.IssueChangeDto;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.rule.RuleDto;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singleton;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.issue.Issue.STATUS_CLOSED;
+import static org.sonar.api.rules.RuleType.CODE_SMELL;
+import static org.sonar.api.utils.DateUtils.addDays;
+import static org.sonar.api.utils.DateUtils.parseDateTime;
+import static org.sonar.ce.task.projectanalysis.issue.ComponentIssuesLoader.NUMBER_STATUS_AND_BRANCH_CHANGES_TO_KEEP;
+
+@RunWith(DataProviderRunner.class)
+public class ComponentIssuesLoaderIT {
+  private static final Date NOW = parseDateTime("2018-08-17T13:44:53+0000");
+  private static final Date DATE_LIMIT_30_DAYS_BACK_MIDNIGHT = parseDateTime("2018-07-18T00:00:00+0000");
+
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+
+  private final DbClient dbClient = db.getDbClient();
+  private final System2 system2 = mock(System2.class);
+  private final IssueChangesToDeleteRepository issueChangesToDeleteRepository = new IssueChangesToDeleteRepository();
+
+  @Test
+  public void loadClosedIssues_returns_single_DefaultIssue_by_issue_based_on_first_row() {
+    ComponentDto project = db.components().insertPublicProject();
+    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
+    RuleDto rule = db.rules().insert(t -> t.setType(CODE_SMELL));
+    Date issueDate = addDays(NOW, -10);
+    IssueDto issue = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIssueCloseDate(issueDate).setType(CODE_SMELL));
+    db.issues().insertFieldDiffs(issue, newToClosedDiffsWithLine(issueDate, 10));
+    db.issues().insertFieldDiffs(issue, newToClosedDiffsWithLine(addDays(issueDate, 3), 20));
+    db.issues().insertFieldDiffs(issue, newToClosedDiffsWithLine(addDays(issueDate, 1), 30));
+    when(system2.now()).thenReturn(NOW.getTime());
+
+    ComponentIssuesLoader underTest = newComponentIssuesLoader(newEmptySettings());
+    List<DefaultIssue> defaultIssues = underTest.loadClosedIssues(file.uuid());
+
+    assertThat(defaultIssues).hasSize(1);
+    assertThat(defaultIssues.iterator().next().getLine()).isEqualTo(20);
+  }
+
+  @Test
+  public void loadClosedIssues_returns_single_DefaultIssue_with_null_line_if_first_row_has_no_line_diff() {
+    ComponentDto project = db.components().insertPublicProject();
+    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
+    RuleDto rule = db.rules().insert(t -> t.setType(CODE_SMELL));
+    Date issueDate = addDays(NOW, -10);
+    IssueDto issue = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIssueCloseDate(issueDate).setType(CODE_SMELL));
+    db.issues().insertFieldDiffs(issue, newToClosedDiffsWithLine(issueDate, 10));
+    db.issues().insertFieldDiffs(issue, newToClosedDiffsWithLine(addDays(issueDate, 2), null));
+    db.issues().insertFieldDiffs(issue, newToClosedDiffsWithLine(addDays(issueDate, 1), 30));
+    when(system2.now()).thenReturn(NOW.getTime());
+
+    ComponentIssuesLoader underTest = newComponentIssuesLoader(newEmptySettings());
+    List<DefaultIssue> defaultIssues = underTest.loadClosedIssues(file.uuid());
+
+    assertThat(defaultIssues).hasSize(1);
+    assertThat(defaultIssues.iterator().next().getLine()).isNull();
+  }
+
+  @Test
+  public void loadClosedIssues_returns_only_closed_issues_with_close_date() {
+    ComponentDto project = db.components().insertPublicProject();
+    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
+    RuleDto rule = db.rules().insert(t -> t.setType(CODE_SMELL));
+    Date issueDate = addDays(NOW, -10);
+    IssueDto closedIssue = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIssueCloseDate(issueDate).setType(CODE_SMELL));
+    db.issues().insertFieldDiffs(closedIssue, newToClosedDiffsWithLine(issueDate, 10));
+    IssueDto issueNoCloseDate = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED));
+    db.issues().insertFieldDiffs(issueNoCloseDate, newToClosedDiffsWithLine(issueDate, 10));
+    when(system2.now()).thenReturn(NOW.getTime());
+
+    ComponentIssuesLoader underTest = newComponentIssuesLoader(newEmptySettings());
+    List<DefaultIssue> defaultIssues = underTest.loadClosedIssues(file.uuid());
+
+    assertThat(defaultIssues)
+      .extracting(DefaultIssue::key)
+      .containsOnly(closedIssue.getKey());
+  }
+
+  @Test
+  public void loadClosedIssues_returns_only_closed_issues_which_close_date_is_from_day_30_days_ago() {
+    ComponentIssuesLoader underTest = newComponentIssuesLoader(newEmptySettings());
+    loadClosedIssues_returns_only_closed_issues_with_close_date_is_from_30_days_ago(underTest);
+  }
+
+  @Test
+  public void loadClosedIssues_returns_only_closed_issues_with_close_date_is_from_30_days_ago_if_property_is_empty() {
+    Configuration configuration = newConfiguration(null);
+    ComponentIssuesLoader underTest = newComponentIssuesLoader(configuration);
+
+    loadClosedIssues_returns_only_closed_issues_with_close_date_is_from_30_days_ago(underTest);
+  }
+
+  @Test
+  public void loadClosedIssues_returns_only_closed_with_close_date_is_from_30_days_ago_if_property_is_less_than_0() {
+    Configuration configuration = newConfiguration(String.valueOf(-(1 + new Random().nextInt(10))));
+    ComponentIssuesLoader underTest = newComponentIssuesLoader(configuration);
+
+    loadClosedIssues_returns_only_closed_issues_with_close_date_is_from_30_days_ago(underTest);
+  }
+
+  @Test
+  public void loadClosedIssues_returns_only_closed_with_close_date_is_from_30_days_ago_if_property_is_30() {
+    Configuration configuration = newConfiguration("30");
+    ComponentIssuesLoader underTest = newComponentIssuesLoader(configuration);
+
+    loadClosedIssues_returns_only_closed_issues_with_close_date_is_from_30_days_ago(underTest);
+  }
+
+  @Test
+  @UseDataProvider("notAnIntegerPropertyValues")
+  public void loadClosedIssues_returns_only_closed_with_close_date_is_from_30_days_ago_if_property_is_not_an_integer(String notAnInteger) {
+    Configuration configuration = newConfiguration(notAnInteger);
+    ComponentIssuesLoader underTest = newComponentIssuesLoader(configuration);
+
+    loadClosedIssues_returns_only_closed_issues_with_close_date_is_from_30_days_ago(underTest);
+  }
+
+  @DataProvider
+  public static Object[][] notAnIntegerPropertyValues() {
+    return new Object[][] {
+      {"foo"},
+      {"1,3"},
+      {"1.3"},
+      {"-3.14"}
+    };
+  }
+
+  private void loadClosedIssues_returns_only_closed_issues_with_close_date_is_from_30_days_ago(ComponentIssuesLoader underTest) {
+    ComponentDto project = db.components().insertPublicProject();
+    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
+    RuleDto rule = db.rules().insert(t -> t.setType(CODE_SMELL));
+    Date[] issueDates = new Date[] {
+      addDays(NOW, -10),
+      addDays(NOW, -31),
+      addDays(NOW, -30),
+      DATE_LIMIT_30_DAYS_BACK_MIDNIGHT,
+      addDays(NOW, -29),
+      addDays(NOW, -60),
+    };
+    IssueDto[] issues = Arrays.stream(issueDates)
+      .map(issueDate -> {
+        IssueDto closedIssue = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIssueCloseDate(issueDate).setType(CODE_SMELL));
+        db.issues().insertFieldDiffs(closedIssue, newToClosedDiffsWithLine(issueDate, 10));
+        return closedIssue;
+      })
+      .toArray(IssueDto[]::new);
+    when(system2.now()).thenReturn(NOW.getTime());
+
+    List<DefaultIssue> defaultIssues = underTest.loadClosedIssues(file.uuid());
+
+    assertThat(defaultIssues)
+      .extracting(DefaultIssue::key)
+      .containsOnly(issues[0].getKey(), issues[2].getKey(), issues[3].getKey(), issues[4].getKey());
+  }
+
+  @Test
+  public void loadClosedIssues_returns_empty_without_querying_DB_if_property_is_0() {
+    System2 system2 = mock(System2.class);
+    DbClient dbClient = mock(DbClient.class);
+    Configuration configuration = newConfiguration("0");
+    String componentUuid = randomAlphabetic(15);
+    ComponentIssuesLoader underTest = new ComponentIssuesLoader(dbClient, null, null, configuration, system2, issueChangesToDeleteRepository);
+
+    assertThat(underTest.loadClosedIssues(componentUuid)).isEmpty();
+
+    verifyNoInteractions(dbClient, system2);
+  }
+
+  @Test
+  public void loadLatestDiffChangesForReopeningOfClosedIssues_collects_issue_changes_to_delete() {
+    IssueDto issue = db.issues().insert();
+    for (long i = 0; i < NUMBER_STATUS_AND_BRANCH_CHANGES_TO_KEEP + 5; i++) {
+      db.issues().insertChange(issue, diffIssueChangeModifier(i, "status"));
+    }
+    // should not be deleted
+    db.issues().insertChange(issue, diffIssueChangeModifier(-1, "other"));
+
+    ComponentIssuesLoader underTest = new ComponentIssuesLoader(dbClient, null, null, newConfiguration("0"), null, issueChangesToDeleteRepository);
+
+    underTest.loadLatestDiffChangesForReopeningOfClosedIssues(singleton(new DefaultIssue().setKey(issue.getKey())));
+    assertThat(issueChangesToDeleteRepository.getUuids()).containsOnly("0", "1", "2", "3", "4");
+  }
+
+  @Test
+  public void loadLatestDiffChangesForReopeningOfClosedIssues_does_not_query_DB_if_issue_list_is_empty() {
+    DbClient dbClient = mock(DbClient.class);
+    ComponentIssuesLoader underTest = new ComponentIssuesLoader(dbClient, null, null, newConfiguration("0"), null, issueChangesToDeleteRepository);
+
+    underTest.loadLatestDiffChangesForReopeningOfClosedIssues(emptyList());
+
+    verifyNoInteractions(dbClient, system2);
+  }
+
+  @Test
+  @UseDataProvider("statusOrResolutionFieldName")
+  public void loadLatestDiffChangesForReopeningOfClosedIssues_add_diff_change_with_most_recent_status_or_resolution(String statusOrResolutionFieldName) {
+    IssueDto issue = db.issues().insert();
+    db.issues().insertChange(issue, t -> t.setChangeData(randomDiffWith(statusOrResolutionFieldName, "val1")).setIssueChangeCreationDate(5));
+    db.issues().insertChange(issue, t -> t.setChangeData(randomDiffWith(statusOrResolutionFieldName, "val2")).setIssueChangeCreationDate(20));
+    db.issues().insertChange(issue, t -> t.setChangeData(randomDiffWith(statusOrResolutionFieldName, "val3")).setIssueChangeCreationDate(13));
+    ComponentIssuesLoader underTest = new ComponentIssuesLoader(dbClient, null, null, newConfiguration("0"), null, issueChangesToDeleteRepository);
+    DefaultIssue defaultIssue = new DefaultIssue().setKey(issue.getKey());
+
+    underTest.loadLatestDiffChangesForReopeningOfClosedIssues(singleton(defaultIssue));
+
+    assertThat(defaultIssue.changes())
+      .hasSize(1);
+    assertThat(defaultIssue.changes())
+      .extracting(t -> t.get(statusOrResolutionFieldName))
+      .filteredOn(t -> hasValue(t, "val2"))
+      .hasSize(1);
+  }
+
+  @Test
+  public void loadLatestDiffChangesForReopeningOfClosedIssues_add_single_diff_change_when_most_recent_status_and_resolution_is_the_same_diff() {
+    IssueDto issue = db.issues().insert();
+    db.issues().insertChange(issue, t -> t.setChangeData(randomDiffWith("status", "valStatus1")).setIssueChangeCreationDate(5));
+    db.issues().insertChange(issue, t -> t.setChangeData(randomDiffWith("status", "valStatus2")).setIssueChangeCreationDate(19));
+    db.issues().insertChange(issue, t -> t.setChangeData(randomDiffWith("status", "valStatus3", "resolution", "valRes3")).setIssueChangeCreationDate(20));
+    db.issues().insertChange(issue, t -> t.setChangeData(randomDiffWith("resolution", "valRes4")).setIssueChangeCreationDate(13));
+    ComponentIssuesLoader underTest = new ComponentIssuesLoader(dbClient, null, null, newConfiguration("0"), null, issueChangesToDeleteRepository);
+    DefaultIssue defaultIssue = new DefaultIssue().setKey(issue.getKey());
+
+    underTest.loadLatestDiffChangesForReopeningOfClosedIssues(singleton(defaultIssue));
+
+    assertThat(defaultIssue.changes())
+      .hasSize(1);
+    assertThat(defaultIssue.changes())
+      .extracting(t -> t.get("status"))
+      .filteredOn(t -> hasValue(t, "valStatus3"))
+      .hasSize(1);
+    assertThat(defaultIssue.changes())
+      .extracting(t -> t.get("resolution"))
+      .filteredOn(t -> hasValue(t, "valRes3"))
+      .hasSize(1);
+  }
+
+  @Test
+  public void loadLatestDiffChangesForReopeningOfClosedIssues_adds_2_diff_changes_if_most_recent_status_and_resolution_are_not_the_same_diff() {
+    IssueDto issue = db.issues().insert();
+    db.issues().insertChange(issue, t -> t.setChangeData(randomDiffWith("status", "valStatus1")).setIssueChangeCreationDate(5));
+    db.issues().insertChange(issue, t -> t.setChangeData(randomDiffWith("status", "valStatus2", "resolution", "valRes2")).setIssueChangeCreationDate(19));
+    db.issues().insertChange(issue, t -> t.setChangeData(randomDiffWith("status", "valStatus3")).setIssueChangeCreationDate(20));
+    db.issues().insertChange(issue, t -> t.setChangeData(randomDiffWith("resolution", "valRes4")).setIssueChangeCreationDate(13));
+    ComponentIssuesLoader underTest = new ComponentIssuesLoader(dbClient, null /* not used in method */, null /* not used in method */,
+      newConfiguration("0"), null /* not used by method */, issueChangesToDeleteRepository);
+    DefaultIssue defaultIssue = new DefaultIssue().setKey(issue.getKey());
+
+    underTest.loadLatestDiffChangesForReopeningOfClosedIssues(singleton(defaultIssue));
+
+    assertThat(defaultIssue.changes())
+      .hasSize(2);
+    assertThat(defaultIssue.changes())
+      .extracting(t -> t.get("status"))
+      .filteredOn(t -> hasValue(t, "valStatus3"))
+      .hasSize(1);
+    assertThat(defaultIssue.changes())
+      .extracting(t -> t.get("resolution"))
+      .filteredOn(t -> hasValue(t, "valRes2"))
+      .hasSize(1);
+  }
+
+  @Test
+  public void loadChanges_should_filter_out_old_status_changes() {
+    IssueDto issue = db.issues().insert();
+    for (int i = 0; i < NUMBER_STATUS_AND_BRANCH_CHANGES_TO_KEEP + 1; i++) {
+      db.issues().insertChange(issue, diffIssueChangeModifier(i, "status"));
+    }
+    // these are kept
+    db.issues().insertChange(issue, diffIssueChangeModifier(NUMBER_STATUS_AND_BRANCH_CHANGES_TO_KEEP + 1, "other"));
+    db.issues().insertChange(issue, t -> t
+      .setChangeType(IssueChangeDto.TYPE_COMMENT)
+      .setKey("comment1"));
+
+    ComponentIssuesLoader underTest = new ComponentIssuesLoader(dbClient, null, null, newConfiguration("0"), null, issueChangesToDeleteRepository);
+    DefaultIssue defaultIssue = new DefaultIssue().setKey(issue.getKey());
+    underTest.loadChanges(db.getSession(), singleton(defaultIssue));
+
+    assertThat(defaultIssue.changes())
+      .extracting(d -> d.creationDate().getTime())
+      .containsOnly(LongStream.rangeClosed(1, NUMBER_STATUS_AND_BRANCH_CHANGES_TO_KEEP + 1).boxed().toArray(Long[]::new));
+    assertThat(defaultIssue.defaultIssueComments()).extracting(DefaultIssueComment::key).containsOnly("comment1");
+    assertThat(issueChangesToDeleteRepository.getUuids()).containsOnly("0");
+  }
+
+  @Test
+  public void loadChanges_should_filter_out_old_from_branch_changes() {
+    IssueDto issue = db.issues().insert();
+    for (int i = 0; i < NUMBER_STATUS_AND_BRANCH_CHANGES_TO_KEEP + 1; i++) {
+      db.issues().insertChange(issue, diffIssueChangeModifier(i, "from_branch"));
+    }
+
+    ComponentIssuesLoader underTest = new ComponentIssuesLoader(dbClient, null, null, newConfiguration("0"), null, issueChangesToDeleteRepository);
+    DefaultIssue defaultIssue = new DefaultIssue().setKey(issue.getKey());
+    underTest.loadChanges(db.getSession(), singleton(defaultIssue));
+    assertThat(defaultIssue.changes())
+      .extracting(d -> d.creationDate().getTime())
+      .containsOnly(LongStream.rangeClosed(1, NUMBER_STATUS_AND_BRANCH_CHANGES_TO_KEEP).boxed().toArray(Long[]::new));
+    assertThat(issueChangesToDeleteRepository.getUuids()).containsOnly("0");
+  }
+
+  private Consumer<IssueChangeDto> diffIssueChangeModifier(long created, String field) {
+    return issueChangeDto -> issueChangeDto
+      .setChangeData(new FieldDiffs().setDiff(field, "A", "B").toEncodedString())
+      .setIssueChangeCreationDate(created)
+      .setUuid(String.valueOf(created));
+  }
+
+  private static boolean hasValue(@Nullable FieldDiffs.Diff t, String value) {
+    if (t == null) {
+      return false;
+    }
+    return (t.oldValue() == null || value.equals(t.oldValue())) && (t.newValue() == null || value.equals(t.newValue()));
+  }
+
+  @DataProvider
+  public static Object[][] statusOrResolutionFieldName() {
+    return new Object[][] {
+      {"status"},
+      {"resolution"},
+    };
+  }
+
+  private static String randomDiffWith(String... fieldsAndValues) {
+    Random random = new Random();
+    List<Diff> diffs = new ArrayList<>();
+    for (int i = 0; i < fieldsAndValues.length; i++) {
+      int oldOrNew = random.nextInt(3);
+      String value = fieldsAndValues[i + 1];
+      diffs.add(new Diff(fieldsAndValues[i], oldOrNew <= 2 ? value : null, oldOrNew >= 2 ? value : null));
+      i++;
+    }
+    IntStream.range(0, random.nextInt(5))
+      .forEach(i -> diffs.add(new Diff(randomAlphabetic(10), random.nextBoolean() ? null : randomAlphabetic(11), random.nextBoolean() ? null : randomAlphabetic(12))));
+    Collections.shuffle(diffs);
+
+    FieldDiffs res = new FieldDiffs();
+    diffs.forEach(diff -> res.setDiff(diff.field, diff.oldValue, diff.newValue));
+    return res.toEncodedString();
+  }
+
+  private static final class Diff {
+    private final String field;
+    private final String oldValue;
+    private final String newValue;
+
+    private Diff(String field, @Nullable String oldValue, @Nullable String newValue) {
+      this.field = field;
+      this.oldValue = oldValue;
+      this.newValue = newValue;
+    }
+  }
+
+  private static FieldDiffs newToClosedDiffsWithLine(Date creationDate, @Nullable Integer oldLineValue) {
+    FieldDiffs fieldDiffs = new FieldDiffs().setCreationDate(addDays(creationDate, -5))
+      .setDiff("status", randomNonCloseStatus(), STATUS_CLOSED);
+    if (oldLineValue != null) {
+      fieldDiffs.setDiff("line", oldLineValue, "");
+    }
+    return fieldDiffs;
+  }
+
+  private static String randomNonCloseStatus() {
+    String[] nonCloseStatuses = Issue.STATUSES.stream()
+      .filter(t -> !STATUS_CLOSED.equals(t))
+      .toArray(String[]::new);
+    return nonCloseStatuses[new Random().nextInt(nonCloseStatuses.length)];
+  }
+
+  private ComponentIssuesLoader newComponentIssuesLoader(Configuration configuration) {
+    return new ComponentIssuesLoader(dbClient, null /* not used in loadClosedIssues */, null /* not used in loadClosedIssues */,
+      configuration, system2, issueChangesToDeleteRepository);
+  }
+
+  private static Configuration newEmptySettings() {
+    return new MapSettings().asConfig();
+  }
+
+  private static Configuration newConfiguration(@Nullable String maxAge) {
+    MapSettings settings = new MapSettings();
+    settings.setProperty("sonar.issuetracking.closedissues.maxage", maxAge);
+    return settings.asConfig();
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/DefaultAssigneeIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/DefaultAssigneeIT.java
new file mode 100644 (file)
index 0000000..3d04097
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * 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.issue;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.ce.task.projectanalysis.component.ConfigurationRepository;
+import org.sonar.ce.task.projectanalysis.component.TestSettingsRepository;
+import org.sonar.db.DbTester;
+import org.sonar.db.user.UserDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DefaultAssigneeIT {
+
+  public static final String PROJECT_KEY = "PROJECT_KEY";
+
+  @Rule
+  public DbTester db = DbTester.create();
+
+  private final MapSettings settings = new MapSettings();
+  private final ConfigurationRepository settingsRepository = new TestSettingsRepository(settings.asConfig());
+  private final DefaultAssignee underTest = new DefaultAssignee(db.getDbClient(), settingsRepository);
+
+  @Test
+  public void no_default_assignee() {
+    assertThat(underTest.loadDefaultAssigneeUuid()).isNull();
+  }
+
+  @Test
+  public void set_default_assignee() {
+    settings.setProperty(CoreProperties.DEFAULT_ISSUE_ASSIGNEE, "erik");
+    UserDto userDto = db.users().insertUser("erik");
+
+    assertThat(underTest.loadDefaultAssigneeUuid()).isEqualTo(userDto.getUuid());
+  }
+
+  @Test
+  public void configured_login_does_not_exist() {
+    settings.setProperty(CoreProperties.DEFAULT_ISSUE_ASSIGNEE, "erik");
+
+    assertThat(underTest.loadDefaultAssigneeUuid()).isNull();
+  }
+
+  @Test
+  public void configured_login_is_disabled() {
+    settings.setProperty(CoreProperties.DEFAULT_ISSUE_ASSIGNEE, "erik");
+    db.users().insertUser(user -> user.setLogin("erik").setActive(false));
+
+    assertThat(underTest.loadDefaultAssigneeUuid()).isNull();
+  }
+
+  @Test
+  public void default_assignee_is_cached() {
+    settings.setProperty(CoreProperties.DEFAULT_ISSUE_ASSIGNEE, "erik");
+    UserDto userDto = db.users().insertUser("erik");
+    assertThat(underTest.loadDefaultAssigneeUuid()).isEqualTo(userDto.getUuid());
+
+    // The setting is updated but the assignee hasn't changed
+    settings.setProperty(CoreProperties.DEFAULT_ISSUE_ASSIGNEE, "other");
+    assertThat(underTest.loadDefaultAssigneeUuid()).isEqualTo(userDto.getUuid());
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorIT.java
new file mode 100644 (file)
index 0000000..bd14249
--- /dev/null
@@ -0,0 +1,366 @@
+/*
+ * 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.issue;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.ArgumentCaptor;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
+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.FileStatuses;
+import org.sonar.ce.task.projectanalysis.component.ReferenceBranchComponentUuids;
+import org.sonar.ce.task.projectanalysis.component.ReportComponent;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
+import org.sonar.ce.task.projectanalysis.component.TypeAwareVisitor;
+import org.sonar.ce.task.projectanalysis.filemove.MovedFilesRepository;
+import org.sonar.ce.task.projectanalysis.issue.filter.IssueFilter;
+import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRulesHolder;
+import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRulesHolderRule;
+import org.sonar.ce.task.projectanalysis.qualityprofile.AlwaysActiveRulesHolderImpl;
+import org.sonar.ce.task.projectanalysis.source.NewLinesRepository;
+import org.sonar.ce.task.projectanalysis.source.SourceLinesHashRepository;
+import org.sonar.ce.task.projectanalysis.source.SourceLinesRepository;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.FieldDiffs;
+import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.core.issue.tracking.Tracker;
+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.issue.IssueDto;
+import org.sonar.db.issue.IssueTesting;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleTesting;
+import org.sonar.scanner.protocol.Constants;
+import org.sonar.scanner.protocol.output.ScannerReport;
+import org.sonar.server.issue.IssueFieldsSetter;
+import org.sonar.server.issue.workflow.IssueWorkflow;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+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.when;
+
+public class IntegrateIssuesVisitorIT {
+
+  private static final String FILE_UUID = "FILE_UUID";
+  private static final String FILE_UUID_ON_BRANCH = "FILE_UUID_BRANCH";
+  private static final String FILE_KEY = "FILE_KEY";
+  private static final int FILE_REF = 2;
+
+  private static final Component FILE = ReportComponent.builder(Component.Type.FILE, FILE_REF)
+    .setKey(FILE_KEY)
+    .setUuid(FILE_UUID)
+    .build();
+
+  private static final String PROJECT_KEY = "PROJECT_KEY";
+  private static final String PROJECT_UUID = "PROJECT_UUID";
+  private static final String PROJECT_UUID_ON_BRANCH = "PROJECT_UUID_BRANCH";
+  private static final int PROJECT_REF = 1;
+  private static final Component PROJECT = ReportComponent.builder(Component.Type.PROJECT, PROJECT_REF)
+    .setKey(PROJECT_KEY)
+    .setUuid(PROJECT_UUID)
+    .addChildren(FILE)
+    .build();
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+  @Rule
+  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
+  @Rule
+  public ActiveRulesHolderRule activeRulesHolderRule = new ActiveRulesHolderRule();
+  @Rule
+  public RuleRepositoryRule ruleRepositoryRule = new RuleRepositoryRule();
+
+  private final AnalysisMetadataHolder analysisMetadataHolder = mock(AnalysisMetadataHolder.class);
+  private final IssueFilter issueFilter = mock(IssueFilter.class);
+  private final MovedFilesRepository movedFilesRepository = mock(MovedFilesRepository.class);
+  private final IssueChangeContext issueChangeContext = mock(IssueChangeContext.class);
+  private final IssueLifecycle issueLifecycle = new IssueLifecycle(analysisMetadataHolder, issueChangeContext, mock(IssueWorkflow.class), new IssueFieldsSetter(),
+    mock(DebtCalculator.class), ruleRepositoryRule);
+  private final IssueVisitor issueVisitor = mock(IssueVisitor.class);
+  private final ReferenceBranchComponentUuids mergeBranchComponentsUuids = mock(ReferenceBranchComponentUuids.class);
+  private final SiblingsIssueMerger issueStatusCopier = mock(SiblingsIssueMerger.class);
+  private final ReferenceBranchComponentUuids referenceBranchComponentUuids = mock(ReferenceBranchComponentUuids.class);
+  private final SourceLinesHashRepository sourceLinesHash = mock(SourceLinesHashRepository.class);
+  private final NewLinesRepository newLinesRepository = mock(NewLinesRepository.class);
+  private final TargetBranchComponentUuids targetBranchComponentUuids = mock(TargetBranchComponentUuids.class);
+  private final SourceLinesRepository sourceLinesRepository = mock(SourceLinesRepository.class);
+  private final FileStatuses fileStatuses = mock(FileStatuses.class);
+  private ArgumentCaptor<DefaultIssue> defaultIssueCaptor;
+
+  private final ComponentIssuesLoader issuesLoader = new ComponentIssuesLoader(dbTester.getDbClient(), ruleRepositoryRule, activeRulesHolderRule, new MapSettings().asConfig(),
+    System2.INSTANCE, mock(IssueChangesToDeleteRepository.class));
+  private IssueTrackingDelegator trackingDelegator;
+  private TrackerExecution tracker;
+  private PullRequestTrackerExecution prBranchTracker;
+  private ReferenceBranchTrackerExecution mergeBranchTracker;
+  private final ActiveRulesHolder activeRulesHolder = new AlwaysActiveRulesHolderImpl();
+  private ProtoIssueCache protoIssueCache;
+
+  private TypeAwareVisitor underTest;
+
+  @Before
+  public void setUp() throws Exception {
+    IssueVisitors issueVisitors = new IssueVisitors(new IssueVisitor[] {issueVisitor});
+
+    defaultIssueCaptor = ArgumentCaptor.forClass(DefaultIssue.class);
+    when(movedFilesRepository.getOriginalFile(any(Component.class))).thenReturn(Optional.empty());
+
+    DbClient dbClient = dbTester.getDbClient();
+    TrackerRawInputFactory rawInputFactory = new TrackerRawInputFactory(treeRootHolder, reportReader, sourceLinesHash, issueFilter,
+      ruleRepositoryRule, activeRulesHolder);
+    TrackerBaseInputFactory baseInputFactory = new TrackerBaseInputFactory(issuesLoader, dbClient, movedFilesRepository);
+    TrackerTargetBranchInputFactory targetInputFactory = new TrackerTargetBranchInputFactory(issuesLoader, targetBranchComponentUuids, dbClient, movedFilesRepository);
+    TrackerReferenceBranchInputFactory mergeInputFactory = new TrackerReferenceBranchInputFactory(issuesLoader, mergeBranchComponentsUuids, dbClient);
+    ClosedIssuesInputFactory closedIssuesInputFactory = new ClosedIssuesInputFactory(issuesLoader, dbClient, movedFilesRepository);
+    tracker = new TrackerExecution(baseInputFactory, closedIssuesInputFactory, new Tracker<>(), issuesLoader, analysisMetadataHolder);
+    mergeBranchTracker = new ReferenceBranchTrackerExecution(mergeInputFactory, new Tracker<>());
+    prBranchTracker = new PullRequestTrackerExecution(baseInputFactory, targetInputFactory, new Tracker<>(), newLinesRepository);
+    trackingDelegator = new IssueTrackingDelegator(prBranchTracker, mergeBranchTracker, tracker, analysisMetadataHolder);
+    treeRootHolder.setRoot(PROJECT);
+    protoIssueCache = new ProtoIssueCache(temp.newFile(), System2.INSTANCE);
+    when(issueFilter.accept(any(DefaultIssue.class), eq(FILE))).thenReturn(true);
+    when(issueChangeContext.date()).thenReturn(new Date());
+    underTest = new IntegrateIssuesVisitor(protoIssueCache, rawInputFactory, baseInputFactory, issueLifecycle, issueVisitors, trackingDelegator, issueStatusCopier,
+      referenceBranchComponentUuids, mock(PullRequestSourceBranchMerger.class), fileStatuses);
+  }
+
+  @Test
+  public void process_new_issue() {
+    ruleRepositoryRule.add(RuleKey.of("xoo", "S001"));
+    when(analysisMetadataHolder.isBranch()).thenReturn(true);
+    ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
+      .setMsg("the message")
+      .setRuleRepository("xoo")
+      .setRuleKey("S001")
+      .setSeverity(Constants.Severity.BLOCKER)
+      .build();
+    reportReader.putIssues(FILE_REF, singletonList(reportIssue));
+
+    underTest.visitAny(FILE);
+
+    assertThat(newArrayList(protoIssueCache.traverse())).hasSize(1);
+  }
+
+  @Test
+  public void process_existing_issue() {
+    RuleKey ruleKey = RuleTesting.XOO_X1;
+    // Issue from db has severity major
+    addBaseIssue(ruleKey);
+
+    // Issue from report has severity blocker
+    ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
+      .setMsg("new message")
+      .setRuleRepository(ruleKey.repository())
+      .setRuleKey(ruleKey.rule())
+      .setSeverity(Constants.Severity.BLOCKER)
+      .build();
+    reportReader.putIssues(FILE_REF, singletonList(reportIssue));
+
+    underTest.visitAny(FILE);
+
+    List<DefaultIssue> issues = newArrayList(protoIssueCache.traverse());
+    assertThat(issues).hasSize(1);
+    assertThat(issues.get(0).severity()).isEqualTo(Severity.BLOCKER);
+  }
+
+  @Test
+  public void dont_cache_existing_issue_if_unmodified() {
+    RuleKey ruleKey = RuleTesting.XOO_X1;
+    // Issue from db has severity major
+    addBaseIssue(ruleKey);
+
+    // Issue from report has severity blocker
+    ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
+      .setMsg("the message")
+      .setRuleRepository(ruleKey.repository())
+      .setRuleKey(ruleKey.rule())
+      .setSeverity(Constants.Severity.BLOCKER)
+      .build();
+    reportReader.putIssues(FILE_REF, singletonList(reportIssue));
+
+    underTest.visitAny(FILE);
+
+    List<DefaultIssue> issues = newArrayList(protoIssueCache.traverse());
+    assertThat(issues).hasSize(1);
+    assertThat(issues.get(0).severity()).isEqualTo(Severity.BLOCKER);
+  }
+
+  @Test
+  public void execute_issue_visitors() {
+    ruleRepositoryRule.add(RuleKey.of("xoo", "S001"));
+    ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
+      .setMsg("the message")
+      .setRuleRepository("xoo")
+      .setRuleKey("S001")
+      .setSeverity(Constants.Severity.BLOCKER)
+      .build();
+    reportReader.putIssues(FILE_REF, singletonList(reportIssue));
+
+    underTest.visitAny(FILE);
+
+    verify(issueVisitor).beforeComponent(FILE);
+    verify(issueVisitor).afterComponent(FILE);
+    verify(issueVisitor).onIssue(eq(FILE), defaultIssueCaptor.capture());
+    assertThat(defaultIssueCaptor.getValue().ruleKey().rule()).isEqualTo("S001");
+  }
+
+  @Test
+  public void close_unmatched_base_issue() {
+    RuleKey ruleKey = RuleTesting.XOO_X1;
+    addBaseIssue(ruleKey);
+
+    // No issue in the report
+    underTest.visitAny(FILE);
+
+    List<DefaultIssue> issues = newArrayList(protoIssueCache.traverse());
+    assertThat(issues).isEmpty();
+  }
+
+  @Test
+  public void remove_uuid_of_original_file_from_componentsWithUnprocessedIssues_if_component_has_one() {
+    String originalFileUuid = "original file uuid";
+    when(movedFilesRepository.getOriginalFile(FILE))
+      .thenReturn(Optional.of(new MovedFilesRepository.OriginalFile(originalFileUuid, "original file key")));
+
+    underTest.visitAny(FILE);
+  }
+
+  @Test
+  public void reuse_issues_when_data_unchanged() {
+    RuleKey ruleKey = RuleTesting.XOO_X1;
+    // Issue from db has severity major
+    addBaseIssue(ruleKey);
+
+    // Issue from report has severity blocker
+    ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
+      .setMsg("new message")
+      .setRuleRepository(ruleKey.repository())
+      .setRuleKey(ruleKey.rule())
+      .setSeverity(Constants.Severity.BLOCKER)
+      .build();
+    reportReader.putIssues(FILE_REF, singletonList(reportIssue));
+    when(fileStatuses.isDataUnchanged(FILE)).thenReturn(true);
+
+    underTest.visitAny(FILE);
+
+    // visitors get called, so measures created from issues should be calculated taking these issues into account
+    verify(issueVisitor).onIssue(eq(FILE), defaultIssueCaptor.capture());
+    assertThat(defaultIssueCaptor.getValue().ruleKey().rule()).isEqualTo(ruleKey.rule());
+
+    // most issues won't go to the cache since they aren't changed and don't need to be persisted
+    // In this test they are being closed but the workflows aren't working (we mock them) so nothing is changed on the issue is not cached.
+    assertThat(newArrayList(protoIssueCache.traverse())).isEmpty();
+  }
+
+  @Test
+  public void copy_issues_when_creating_new_non_main_branch() {
+    when(mergeBranchComponentsUuids.getComponentUuid(FILE_KEY)).thenReturn(FILE_UUID_ON_BRANCH);
+    when(referenceBranchComponentUuids.getReferenceBranchName()).thenReturn("master");
+
+    when(analysisMetadataHolder.isBranch()).thenReturn(true);
+    when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(true);
+    Branch branch = mock(Branch.class);
+    when(branch.isMain()).thenReturn(false);
+    when(branch.getType()).thenReturn(BranchType.BRANCH);
+    when(analysisMetadataHolder.getBranch()).thenReturn(branch);
+
+    RuleKey ruleKey = RuleTesting.XOO_X1;
+    // Issue from main branch has severity major
+    addBaseIssueOnBranch(ruleKey);
+
+    // Issue from report has severity blocker
+    ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
+      .setMsg("the message")
+      .setRuleRepository(ruleKey.repository())
+      .setRuleKey(ruleKey.rule())
+      .setSeverity(Constants.Severity.BLOCKER)
+      .build();
+    reportReader.putIssues(FILE_REF, singletonList(reportIssue));
+
+    underTest.visitAny(FILE);
+
+    List<DefaultIssue> issues = newArrayList(protoIssueCache.traverse());
+    assertThat(issues).hasSize(1);
+    assertThat(issues.get(0).severity()).isEqualTo(Severity.BLOCKER);
+    assertThat(issues.get(0).isNew()).isFalse();
+    assertThat(issues.get(0).isCopied()).isTrue();
+    assertThat(issues.get(0).changes()).hasSize(1);
+    assertThat(issues.get(0).changes().get(0).diffs()).contains(entry(IssueFieldsSetter.FROM_BRANCH, new FieldDiffs.Diff<>("master", null)));
+  }
+
+  private void addBaseIssue(RuleKey ruleKey) {
+    ComponentDto project = ComponentTesting.newPrivateProjectDto(PROJECT_UUID).setKey(PROJECT_KEY);
+    ComponentDto file = ComponentTesting.newFileDto(project, null, FILE_UUID).setKey(FILE_KEY);
+    dbTester.components().insertComponents(project, file);
+
+    RuleDto ruleDto = RuleTesting.newRule(ruleKey);
+    dbTester.rules().insert(ruleDto);
+    ruleRepositoryRule.add(ruleKey);
+
+    IssueDto issue = IssueTesting.newIssue(ruleDto, project, file)
+      .setKee("ISSUE")
+      .setSeverity(Severity.MAJOR);
+    dbTester.getDbClient().issueDao().insert(dbTester.getSession(), issue);
+    dbTester.getSession().commit();
+  }
+
+  private void addBaseIssueOnBranch(RuleKey ruleKey) {
+    ComponentDto project = ComponentTesting.newPrivateProjectDto(PROJECT_UUID_ON_BRANCH).setKey(PROJECT_KEY);
+    ComponentDto file = ComponentTesting.newFileDto(project, null, FILE_UUID_ON_BRANCH).setKey(FILE_KEY);
+    dbTester.components().insertComponents(project, file);
+
+    RuleDto ruleDto = RuleTesting.newRule(ruleKey);
+    dbTester.rules().insert(ruleDto);
+    ruleRepositoryRule.add(ruleKey);
+
+    IssueDto issue = IssueTesting.newIssue(ruleDto, project, file)
+      .setKee("ISSUE")
+      .setSeverity(Severity.MAJOR)
+      .setChecksum(null);
+    dbTester.getDbClient().issueDao().insert(dbTester.getSession(), issue);
+    dbTester.getSession().commit();
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/ProjectTrackerBaseLazyInputIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/ProjectTrackerBaseLazyInputIT.java
new file mode 100644 (file)
index 0000000..871ba92
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * 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.issue;
+
+import java.util.Date;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+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.component.Component;
+import org.sonar.ce.task.projectanalysis.component.ReportComponent;
+import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRulesHolderRule;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.rule.RuleDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.sonar.api.utils.DateUtils.parseDate;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+
+public class ProjectTrackerBaseLazyInputIT {
+
+  private static final Date ANALYSIS_DATE = parseDate("2016-06-01");
+
+  @Rule
+  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule().setAnalysisDate(ANALYSIS_DATE);
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  @Rule
+  public ActiveRulesHolderRule activeRulesHolderRule = new ActiveRulesHolderRule();
+  @Rule
+  public RuleRepositoryRule ruleRepositoryRule = new RuleRepositoryRule();
+
+  private DbClient dbClient = dbTester.getDbClient();
+  private ProjectTrackerBaseLazyInput underTest;
+  private RuleDto rule;
+  private ComponentDto rootProjectDto;
+  private ComponentIssuesLoader issuesLoader = new ComponentIssuesLoader(dbTester.getDbClient(), ruleRepositoryRule, activeRulesHolderRule, new MapSettings().asConfig(),
+    System2.INSTANCE, mock(IssueChangesToDeleteRepository.class));
+
+  @Before
+  public void prepare() {
+    rule = dbTester.rules().insert();
+    ruleRepositoryRule.add(rule.getKey());
+    rootProjectDto = dbTester.components().insertPublicProject();
+    ReportComponent rootProject = ReportComponent.builder(Component.Type.FILE, 1)
+      .setKey(rootProjectDto.getKey())
+      .setUuid(rootProjectDto.uuid()).build();
+    underTest = new ProjectTrackerBaseLazyInput(dbClient, issuesLoader, rootProject);
+  }
+
+  @Test
+  public void return_only_open_project_issues_if_no_folders() {
+    ComponentDto file = dbTester.components().insertComponent(newFileDto(rootProjectDto));
+    IssueDto openIssueOnProject = dbTester.issues().insert(rule, rootProjectDto, rootProjectDto, i -> i.setStatus("OPEN").setResolution(null));
+    IssueDto closedIssueOnProject = dbTester.issues().insert(rule, rootProjectDto, rootProjectDto, i -> i.setStatus("CLOSED").setResolution("FIXED"));
+    IssueDto openIssue1OnFile = dbTester.issues().insert(rule, rootProjectDto, file, i -> i.setStatus("OPEN").setResolution(null));
+
+    assertThat(underTest.loadIssues()).extracting(DefaultIssue::key).containsOnly(openIssueOnProject.getKey());
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/ScmAccountToUserLoaderIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/ScmAccountToUserLoaderIT.java
new file mode 100644 (file)
index 0000000..411a708
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * 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.issue;
+
+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.db.DbTester;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.user.index.UserIndex;
+import org.sonar.server.user.index.UserIndexer;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+
+public class ScmAccountToUserLoaderIT {
+
+  @Rule
+  public DbTester db = DbTester.create();
+  @Rule
+  public EsTester es = EsTester.create();
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  private UserIndexer userIndexer = new UserIndexer(db.getDbClient(), es.client());
+
+  @Test
+  public void load_login_for_scm_account() {
+    UserDto user = db.users().insertUser(u -> u.setScmAccounts(asList("charlie", "jesuis@charlie.com")));
+    userIndexer.indexAll();
+
+    UserIndex index = new UserIndex(es.client(), System2.INSTANCE);
+    ScmAccountToUserLoader underTest = new ScmAccountToUserLoader(index);
+
+    assertThat(underTest.load("missing")).isNull();
+    assertThat(underTest.load("jesuis@charlie.com")).isEqualTo(user.getUuid());
+  }
+
+  @Test
+  public void warn_if_multiple_users_share_the_same_scm_account() {
+    db.users().insertUser(u -> u.setLogin("charlie").setScmAccounts(asList("charlie", "jesuis@charlie.com")));
+    db.users().insertUser(u -> u.setLogin("another.charlie").setScmAccounts(asList("charlie")));
+    userIndexer.indexAll();
+
+    UserIndex index = new UserIndex(es.client(), System2.INSTANCE);
+    ScmAccountToUserLoader underTest = new ScmAccountToUserLoader(index);
+
+    assertThat(underTest.load("charlie")).isNull();
+    assertThat(logTester.logs(LoggerLevel.WARN)).contains("Multiple users share the SCM account 'charlie': another.charlie, charlie");
+  }
+
+  @Test
+  public void load_by_multiple_scm_accounts_is_not_supported_yet() {
+    UserIndex index = new UserIndex(es.client(), System2.INSTANCE);
+    ScmAccountToUserLoader underTest = new ScmAccountToUserLoader(index);
+    try {
+      underTest.loadAll(emptyList());
+      fail();
+    } catch (UnsupportedOperationException ignored) {
+    }
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMergerIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMergerIT.java
new file mode 100644 (file)
index 0000000..870e352
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+ * 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.issue;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Collections;
+import java.util.Date;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.rule.RuleKey;
+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.ce.task.projectanalysis.component.SiblingComponentsWithOpenIssues;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.FieldDiffs;
+import org.sonar.core.issue.tracking.SimpleTracker;
+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.issue.IssueDto;
+import org.sonar.db.issue.IssueTesting;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.project.Project;
+
+import static org.assertj.core.api.Assertions.assertThat;
+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.ce.task.projectanalysis.component.ReportComponent.builder;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+
+public class SiblingsIssueMergerIT {
+  private final IssueLifecycle issueLifecycle = mock(IssueLifecycle.class);
+  private final Branch branch = mock(Branch.class);
+
+  @Rule
+  public DbTester db = DbTester.create();
+
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule()
+    .setRoot(builder(org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT, PROJECT_REF).setKey(PROJECT_KEY).setUuid(PROJECT_UUID)
+      .addChildren(FILE_1)
+      .build());
+
+  @Rule
+  public AnalysisMetadataHolderRule metadataHolder = new AnalysisMetadataHolderRule();
+
+  private static final String PROJECT_KEY = "project";
+  private static final int PROJECT_REF = 1;
+  private static final String PROJECT_UUID = "projectUuid";
+  private static final int FILE_1_REF = 12341;
+  private static final String FILE_1_KEY = "fileKey";
+  private static final String FILE_1_UUID = "fileUuid";
+
+  private static final org.sonar.ce.task.projectanalysis.component.Component FILE_1 = builder(
+    org.sonar.ce.task.projectanalysis.component.Component.Type.FILE, FILE_1_REF)
+    .setKey(FILE_1_KEY)
+    .setUuid(FILE_1_UUID)
+    .build();
+
+  private final SimpleTracker<DefaultIssue, SiblingIssue> tracker = new SimpleTracker<>();
+  private SiblingsIssueMerger copier;
+  private ComponentDto fileOnBranch1Dto;
+  private ComponentDto fileOnBranch2Dto;
+  private ComponentDto fileOnBranch3Dto;
+  private ComponentDto projectDto;
+  private ComponentDto branch1Dto;
+  private ComponentDto branch2Dto;
+  private ComponentDto branch3Dto;
+  private RuleDto rule;
+
+  @Before
+  public void setUp() {
+    DbClient dbClient = db.getDbClient();
+    ComponentIssuesLoader componentIssuesLoader = new ComponentIssuesLoader(dbClient, null, null, new MapSettings().asConfig(), System2.INSTANCE,
+      mock(IssueChangesToDeleteRepository.class));
+    copier = new SiblingsIssueMerger(new SiblingsIssuesLoader(new SiblingComponentsWithOpenIssues(treeRootHolder, metadataHolder, dbClient), dbClient, componentIssuesLoader),
+      tracker,
+      issueLifecycle);
+    projectDto = db.components().insertPublicProject(p -> p.setKey(PROJECT_KEY).setUuid(PROJECT_UUID));
+    branch1Dto = db.components().insertProjectBranch(projectDto, b -> b.setKey("myBranch1")
+      .setBranchType(BranchType.PULL_REQUEST)
+      .setMergeBranchUuid(projectDto.uuid()));
+    branch2Dto = db.components().insertProjectBranch(projectDto, b -> b.setKey("myBranch2")
+      .setBranchType(BranchType.PULL_REQUEST)
+      .setMergeBranchUuid(projectDto.uuid()));
+    branch3Dto = db.components().insertProjectBranch(projectDto, b -> b.setKey("myBranch3")
+      .setBranchType(BranchType.PULL_REQUEST)
+      .setMergeBranchUuid(projectDto.uuid()));
+    fileOnBranch1Dto = db.components().insertComponent(newFileDto(branch1Dto).setKey(FILE_1_KEY));
+    fileOnBranch2Dto = db.components().insertComponent(newFileDto(branch2Dto).setKey(FILE_1_KEY));
+    fileOnBranch3Dto = db.components().insertComponent(newFileDto(branch3Dto).setKey(FILE_1_KEY));
+    rule = db.rules().insert();
+    when(branch.getReferenceBranchUuid()).thenReturn(projectDto.uuid());
+    metadataHolder.setBranch(branch);
+    metadataHolder.setProject(new Project(projectDto.uuid(), projectDto.getKey(), projectDto.name(), projectDto.description(), Collections.emptyList()));
+  }
+
+  @Test
+  public void do_nothing_if_no_match() {
+    DefaultIssue i = createIssue("issue1", rule.getKey(), Issue.STATUS_CONFIRMED, new Date());
+    copier.tryMerge(FILE_1, Collections.singleton(i));
+
+    verifyNoInteractions(issueLifecycle);
+  }
+
+  @Test
+  public void do_nothing_if_no_new_issue() {
+    db.issues().insert(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum"));
+    copier.tryMerge(FILE_1, Collections.emptyList());
+
+    verifyNoInteractions(issueLifecycle);
+  }
+
+  @Test
+  public void merge_confirmed_issues() {
+    db.issues().insert(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum"));
+    DefaultIssue newIssue = createIssue("issue2", rule.getKey(), Issue.STATUS_OPEN, new Date());
+
+    copier.tryMerge(FILE_1, Collections.singleton(newIssue));
+
+    ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class);
+    verify(issueLifecycle).mergeConfirmedOrResolvedFromPrOrBranch(eq(newIssue), issueToMerge.capture(), eq(BranchType.PULL_REQUEST), eq("myBranch1"));
+
+    assertThat(issueToMerge.getValue().key()).isEqualTo("issue1");
+  }
+
+  @Test
+  public void prefer_more_recently_updated_issues() {
+    Instant now = Instant.now();
+    db.issues().insert(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_REOPENED).setLine(1).setChecksum("checksum")
+      .setIssueUpdateDate(Date.from(now.plus(2, ChronoUnit.SECONDS))));
+    db.issues().insert(IssueTesting.newIssue(rule, branch2Dto, fileOnBranch2Dto).setKee("issue2").setStatus(Issue.STATUS_OPEN).setLine(1).setChecksum("checksum")
+      .setIssueUpdateDate(Date.from(now.plus(1, ChronoUnit.SECONDS))));
+    db.issues().insert(IssueTesting.newIssue(rule, branch3Dto, fileOnBranch3Dto).setKee("issue3").setStatus(Issue.STATUS_OPEN).setLine(1).setChecksum("checksum")
+      .setIssueUpdateDate(Date.from(now)));
+    DefaultIssue newIssue = createIssue("newIssue", rule.getKey(), Issue.STATUS_OPEN, new Date());
+
+    copier.tryMerge(FILE_1, Collections.singleton(newIssue));
+
+    ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class);
+    verify(issueLifecycle).mergeConfirmedOrResolvedFromPrOrBranch(eq(newIssue), issueToMerge.capture(), eq(BranchType.PULL_REQUEST), eq("myBranch1"));
+
+    assertThat(issueToMerge.getValue().key()).isEqualTo("issue1");
+  }
+
+  @Test
+  public void lazy_load_changes() {
+    UserDto user = db.users().insertUser();
+    IssueDto issue = db.issues()
+      .insert(IssueTesting.newIssue(rule, branch2Dto, fileOnBranch2Dto).setKee("issue").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum"));
+    db.issues().insertComment(issue, user, "A comment 2");
+    db.issues().insertFieldDiffs(issue, FieldDiffs.parse("severity=BLOCKER|MINOR,assignee=foo|bar").setCreationDate(new Date()));
+    DefaultIssue newIssue = createIssue("newIssue", rule.getKey(), Issue.STATUS_OPEN, new Date());
+
+    copier.tryMerge(FILE_1, Collections.singleton(newIssue));
+
+    ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class);
+    verify(issueLifecycle).mergeConfirmedOrResolvedFromPrOrBranch(eq(newIssue), issueToMerge.capture(), eq(BranchType.PULL_REQUEST), eq("myBranch2"));
+
+    assertThat(issueToMerge.getValue().key()).isEqualTo("issue");
+    assertThat(issueToMerge.getValue().defaultIssueComments()).isNotEmpty();
+    assertThat(issueToMerge.getValue().changes()).isNotEmpty();
+  }
+
+  private static DefaultIssue createIssue(String key, RuleKey ruleKey, String status, Date creationDate) {
+    DefaultIssue issue = new DefaultIssue();
+    issue.setKey(key);
+    issue.setRuleKey(ruleKey);
+    issue.setMessage("msg");
+    issue.setLine(1);
+    issue.setStatus(status);
+    issue.setResolution(null);
+    issue.setCreationDate(creationDate);
+    issue.setChecksum("checksum");
+    return issue;
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/SourceBranchComponentUuidsIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/SourceBranchComponentUuidsIT.java
new file mode 100644 (file)
index 0000000..c901b3c
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * 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.issue;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.ce.task.projectanalysis.analysis.Branch;
+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.protobuf.DbProjectBranches;
+import org.sonar.server.project.Project;
+
+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.SnapshotTesting.newAnalysis;
+
+public class SourceBranchComponentUuidsIT {
+
+  private static final String BRANCH_KEY = "branch1";
+  private static final String PR_KEY = "pr1";
+
+  @Rule
+  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
+
+  @Rule
+  public DbTester db = DbTester.create();
+
+  private SourceBranchComponentUuids underTest;
+  private final Branch branch = mock(Branch.class);
+  private ComponentDto branch1;
+  private ComponentDto branch1File;
+  private ComponentDto pr1File;
+
+  @Before
+  public void setup() {
+    underTest = new SourceBranchComponentUuids(analysisMetadataHolder, db.getDbClient());
+    Project project = mock(Project.class);
+    analysisMetadataHolder.setProject(project);
+    analysisMetadataHolder.setBranch(branch);
+
+    ComponentDto projectDto = db.components().insertPublicProject();
+    when(project.getUuid()).thenReturn(projectDto.uuid());
+    branch1 = db.components().insertProjectBranch(projectDto, b -> b.setKey(BRANCH_KEY));
+    ComponentDto pr1branch = db.components().insertProjectBranch(projectDto, b -> b.setKey(PR_KEY)
+      .setBranchType(BranchType.PULL_REQUEST)
+      .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder().setBranch(BRANCH_KEY).build())
+      .setMergeBranchUuid(projectDto.getMainBranchProjectUuid()));
+    branch1File = ComponentTesting.newFileDto(branch1, null, "file").setUuid("branch1File");
+    pr1File = ComponentTesting.newFileDto(pr1branch, null, "file").setUuid("file1");
+    db.components().insertComponents(branch1File, pr1File);
+  }
+
+  @Test
+  public void should_support_db_key_when_looking_for_source_branch_component() {
+    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
+    when(branch.getName()).thenReturn(BRANCH_KEY);
+    when(branch.getPullRequestKey()).thenReturn(PR_KEY);
+    db.components().insertSnapshot(newAnalysis(branch1));
+
+    assertThat(underTest.getSourceBranchComponentUuid(pr1File.getKey())).isEqualTo(branch1File.uuid());
+    assertThat(underTest.hasSourceBranchAnalysis()).isTrue();
+  }
+
+  @Test
+  public void should_support_key_when_looking_for_source_branch_component() {
+    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
+    when(branch.getName()).thenReturn(BRANCH_KEY);
+    when(branch.getPullRequestKey()).thenReturn(PR_KEY);
+    db.components().insertSnapshot(newAnalysis(branch1));
+
+    assertThat(underTest.getSourceBranchComponentUuid(pr1File.getKey())).isEqualTo(branch1File.uuid());
+  }
+
+  @Test
+  public void return_null_if_file_doesnt_exist() {
+    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
+    when(branch.getName()).thenReturn(BRANCH_KEY);
+    when(branch.getPullRequestKey()).thenReturn(PR_KEY);
+    db.components().insertSnapshot(newAnalysis(branch1));
+
+    assertThat(underTest.getSourceBranchComponentUuid("doesnt exist")).isNull();
+  }
+
+  @Test
+  public void skip_init_if_not_a_pull_request() {
+    when(branch.getType()).thenReturn(BranchType.BRANCH);
+    when(branch.getName()).thenReturn(BRANCH_KEY);
+
+    assertThat(underTest.getSourceBranchComponentUuid(pr1File.getKey())).isNull();
+  }
+
+  @Test
+  public void skip_init_if_no_source_branch_analysis() {
+    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
+    when(branch.getName()).thenReturn(BRANCH_KEY);
+
+    assertThat(underTest.getSourceBranchComponentUuid(pr1File.getKey())).isNull();
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/TargetBranchComponentUuidsIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/TargetBranchComponentUuidsIT.java
new file mode 100644 (file)
index 0000000..af811e8
--- /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.ce.task.projectanalysis.issue;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.ce.task.projectanalysis.analysis.Branch;
+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.protobuf.DbProjectBranches;
+import org.sonar.server.project.Project;
+
+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.SnapshotTesting.newAnalysis;
+
+public class TargetBranchComponentUuidsIT {
+  private static final String BRANCH_KEY = "branch1";
+  private static final String PR_KEY = "pr1";
+
+  @Rule
+  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
+
+  @Rule
+  public DbTester db = DbTester.create();
+
+  private TargetBranchComponentUuids underTest;
+  private final Branch branch = mock(Branch.class);
+  private ComponentDto branch1;
+  private ComponentDto branch1File;
+  private ComponentDto pr1File;
+
+  @Before
+  public void setup() {
+    underTest = new TargetBranchComponentUuids(analysisMetadataHolder, db.getDbClient());
+    Project project = mock(Project.class);
+    analysisMetadataHolder.setProject(project);
+    analysisMetadataHolder.setBranch(branch);
+
+    ComponentDto projectDto = db.components().insertPublicProject();
+    when(project.getUuid()).thenReturn(projectDto.uuid());
+    branch1 = db.components().insertProjectBranch(projectDto, b -> b.setKey(BRANCH_KEY));
+    ComponentDto pr1branch = db.components().insertProjectBranch(projectDto, b -> b.setKey(PR_KEY)
+      .setBranchType(BranchType.PULL_REQUEST)
+      .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder().setTarget(BRANCH_KEY).build())
+      .setMergeBranchUuid(projectDto.getMainBranchProjectUuid()));
+    branch1File = ComponentTesting.newFileDto(branch1, null, "file").setUuid("branch1File");
+    pr1File = ComponentTesting.newFileDto(pr1branch, null, "file").setUuid("file1");
+    db.components().insertComponents(branch1File, pr1File);
+  }
+
+  @Test
+  public void should_support_db_key_when_looking_for_target_branch_component() {
+    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
+    when(branch.getName()).thenReturn("prBranch");
+    when(branch.getTargetBranchName()).thenReturn(BRANCH_KEY);
+
+    when(branch.getPullRequestKey()).thenReturn(PR_KEY);
+    db.components().insertSnapshot(newAnalysis(branch1));
+
+    assertThat(underTest.getTargetBranchComponentUuid(pr1File.getKey())).isEqualTo(branch1File.uuid());
+    assertThat(underTest.hasTargetBranchAnalysis()).isTrue();
+  }
+
+  @Test
+  public void should_support_key_when_looking_for_target_branch_component() {
+    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
+    when(branch.getName()).thenReturn("prBranch");
+    when(branch.getTargetBranchName()).thenReturn(BRANCH_KEY);
+    when(branch.getPullRequestKey()).thenReturn(PR_KEY);
+    db.components().insertSnapshot(newAnalysis(branch1));
+
+    assertThat(underTest.getTargetBranchComponentUuid(pr1File.getKey())).isEqualTo(branch1File.uuid());
+  }
+
+  @Test
+  public void return_null_if_file_doesnt_exist() {
+    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
+    when(branch.getName()).thenReturn("prBranch");
+    when(branch.getTargetBranchName()).thenReturn(BRANCH_KEY);
+    when(branch.getPullRequestKey()).thenReturn(PR_KEY);
+    db.components().insertSnapshot(newAnalysis(branch1));
+
+    assertThat(underTest.getTargetBranchComponentUuid("doesnt exist")).isNull();
+  }
+
+  @Test
+  public void skip_init_if_not_a_pull_request() {
+    when(branch.getType()).thenReturn(BranchType.BRANCH);
+    when(branch.getName()).thenReturn("prBranch");
+    when(branch.getTargetBranchName()).thenReturn(BRANCH_KEY);
+
+    assertThat(underTest.getTargetBranchComponentUuid(pr1File.getKey())).isNull();
+  }
+
+  @Test
+  public void skip_init_if_no_target_branch_analysis() {
+    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
+    when(branch.getName()).thenReturn("prBranch");
+    when(branch.getTargetBranchName()).thenReturn(BRANCH_KEY);
+
+    assertThat(underTest.getTargetBranchComponentUuid(pr1File.getKey())).isNull();
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/TrackerReferenceBranchInputFactoryIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/TrackerReferenceBranchInputFactoryIT.java
new file mode 100644 (file)
index 0000000..916a98c
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * 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.issue;
+
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.ReferenceBranchComponentUuids;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.tracking.Input;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TrackerReferenceBranchInputFactoryIT {
+  private final static String COMPONENT_KEY = "file1";
+  private final static String COMPONENT_UUID = "uuid1";
+
+  @Rule
+  public DbTester db = DbTester.create();
+
+  private ComponentIssuesLoader componentIssuesLoader = mock(ComponentIssuesLoader.class);
+  private ReferenceBranchComponentUuids referenceBranchComponentUuids = mock(ReferenceBranchComponentUuids.class);
+  private TrackerReferenceBranchInputFactory underTest;
+
+  @Before
+  public void setUp() {
+    underTest = new TrackerReferenceBranchInputFactory(componentIssuesLoader, referenceBranchComponentUuids, db.getDbClient());
+  }
+
+  @Test
+  public void gets_issues_and_hashes_in_matching_component() {
+    DefaultIssue issue1 = new DefaultIssue();
+    when(referenceBranchComponentUuids.getComponentUuid(COMPONENT_KEY)).thenReturn(COMPONENT_UUID);
+    when(componentIssuesLoader.loadOpenIssuesWithChanges(COMPONENT_UUID)).thenReturn(Collections.singletonList(issue1));
+    ComponentDto fileDto = ComponentTesting.newFileDto(ComponentTesting.newPublicProjectDto()).setUuid(COMPONENT_UUID);
+    db.fileSources().insertFileSource(fileDto, 3);
+
+    Component component = mock(Component.class);
+    when(component.getKey()).thenReturn(COMPONENT_KEY);
+    when(component.getType()).thenReturn(Component.Type.FILE);
+    Input<DefaultIssue> input = underTest.create(component);
+
+    assertThat(input.getIssues()).containsOnly(issue1);
+    assertThat(input.getLineHashSequence().length()).isEqualTo(3);
+  }
+
+  @Test
+  public void gets_nothing_when_there_is_no_matching_component() {
+    Component component = mock(Component.class);
+    when(component.getKey()).thenReturn(COMPONENT_KEY);
+    when(component.getType()).thenReturn(Component.Type.FILE);
+    Input<DefaultIssue> input = underTest.create(component);
+
+    assertThat(input.getIssues()).isEmpty();
+    assertThat(input.getLineHashSequence().length()).isZero();
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/TrackerSourceBranchInputFactoryIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/TrackerSourceBranchInputFactoryIT.java
new file mode 100644 (file)
index 0000000..6cf56ee
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * 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.issue;
+
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.tracking.Input;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TrackerSourceBranchInputFactoryIT {
+  private final static String COMPONENT_KEY = "file1";
+  private final static String COMPONENT_UUID = "uuid1";
+
+  @Rule
+  public DbTester db = DbTester.create();
+
+  private final ComponentIssuesLoader componentIssuesLoader = mock(ComponentIssuesLoader.class);
+  private final SourceBranchComponentUuids sourceBranchComponentUuids = mock(SourceBranchComponentUuids.class);
+  private TrackerSourceBranchInputFactory underTest;
+
+  @Before
+  public void setUp() {
+    underTest = new TrackerSourceBranchInputFactory(componentIssuesLoader, sourceBranchComponentUuids, db.getDbClient());
+  }
+
+  @Test
+  public void gets_issues_and_hashes_in_matching_component() {
+    DefaultIssue issue1 = new DefaultIssue();
+    when(sourceBranchComponentUuids.getSourceBranchComponentUuid(COMPONENT_KEY)).thenReturn(COMPONENT_UUID);
+    when(componentIssuesLoader.loadOpenIssuesWithChanges(COMPONENT_UUID)).thenReturn(Collections.singletonList(issue1));
+    ComponentDto fileDto = ComponentTesting.newFileDto(ComponentTesting.newPublicProjectDto()).setUuid(COMPONENT_UUID);
+    db.fileSources().insertFileSource(fileDto, 3);
+
+    Component component = mock(Component.class);
+    when(component.getKey()).thenReturn(COMPONENT_KEY);
+    when(component.getType()).thenReturn(Component.Type.FILE);
+    Input<DefaultIssue> input = underTest.createForSourceBranch(component);
+
+    assertThat(input.getIssues()).containsOnly(issue1);
+    assertThat(input.getLineHashSequence().length()).isEqualTo(3);
+  }
+
+  @Test
+  public void get_issues_without_line_hashes() {
+    DefaultIssue issue1 = new DefaultIssue();
+    when(sourceBranchComponentUuids.getSourceBranchComponentUuid(COMPONENT_KEY)).thenReturn(COMPONENT_UUID);
+    when(componentIssuesLoader.loadOpenIssuesWithChanges(COMPONENT_UUID)).thenReturn(Collections.singletonList(issue1));
+    ComponentDto fileDto = ComponentTesting.newFileDto(ComponentTesting.newPublicProjectDto()).setUuid(COMPONENT_UUID);
+    db.fileSources().insertFileSource(fileDto, 0);
+
+    Component component = mock(Component.class);
+    when(component.getKey()).thenReturn(COMPONENT_KEY);
+    when(component.getType()).thenReturn(Component.Type.FILE);
+    Input<DefaultIssue> input = underTest.createForSourceBranch(component);
+
+    assertThat(input.getIssues()).containsOnly(issue1);
+    assertThat(input.getLineHashSequence().length()).isZero();
+  }
+
+  @Test
+  public void gets_nothing_when_there_is_no_matching_component() {
+    Component component = mock(Component.class);
+    when(component.getKey()).thenReturn(COMPONENT_KEY);
+    when(component.getType()).thenReturn(Component.Type.FILE);
+    Input<DefaultIssue> input = underTest.createForSourceBranch(component);
+
+    assertThat(input.getIssues()).isEmpty();
+    assertThat(input.getLineHashSequence().length()).isZero();
+  }
+
+  @Test
+  public void hasSourceBranchAnalysis_returns_true_if_source_branch_of_pr_was_analysed() {
+    when(sourceBranchComponentUuids.hasSourceBranchAnalysis()).thenReturn(true);
+
+    assertThat(underTest.hasSourceBranchAnalysis()).isTrue();
+  }
+
+  @Test
+  public void hasSourceBranchAnalysis_returns_false_if_no_source_branch_analysis() {
+    when(sourceBranchComponentUuids.hasSourceBranchAnalysis()).thenReturn(false);
+
+    assertThat(underTest.hasSourceBranchAnalysis()).isFalse();
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/TrackerTargetBranchInputFactoryIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/TrackerTargetBranchInputFactoryIT.java
new file mode 100644 (file)
index 0000000..6bbebc1
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * 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.issue;
+
+import java.util.Collections;
+import java.util.Optional;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.filemove.MovedFilesRepository;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.tracking.Input;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class TrackerTargetBranchInputFactoryIT {
+  private final static String COMPONENT_KEY = "file1";
+  private final static String COMPONENT_UUID = "uuid1";
+  private final static String ORIGINAL_COMPONENT_KEY = "file2";
+  private final static String ORIGINAL_COMPONENT_UUID = "uuid2";
+
+  @Rule
+  public DbTester db = DbTester.create();
+
+  private final ComponentIssuesLoader componentIssuesLoader = mock(ComponentIssuesLoader.class);
+  private final TargetBranchComponentUuids targetBranchComponentUuids = mock(TargetBranchComponentUuids.class);
+  private final MovedFilesRepository movedFilesRepository = mock(MovedFilesRepository.class);
+  private TrackerTargetBranchInputFactory underTest;
+
+  @Before
+  public void setUp() {
+    underTest = new TrackerTargetBranchInputFactory(componentIssuesLoader, targetBranchComponentUuids, db.getDbClient(), movedFilesRepository);
+  }
+
+  @Test
+  public void gets_issues_and_hashes_in_matching_component() {
+    DefaultIssue issue1 = new DefaultIssue();
+    when(targetBranchComponentUuids.getTargetBranchComponentUuid(COMPONENT_KEY)).thenReturn(COMPONENT_UUID);
+    when(componentIssuesLoader.loadOpenIssuesWithChanges(COMPONENT_UUID)).thenReturn(Collections.singletonList(issue1));
+    ComponentDto fileDto = ComponentTesting.newFileDto(ComponentTesting.newPublicProjectDto()).setUuid(COMPONENT_UUID);
+    db.fileSources().insertFileSource(fileDto, 3);
+
+    Component component = getComponent();
+    Input<DefaultIssue> input = underTest.createForTargetBranch(component);
+
+    assertThat(input.getIssues()).containsOnly(issue1);
+    assertThat(input.getLineHashSequence().length()).isEqualTo(3);
+  }
+
+  @Test
+  public void get_issues_without_line_hashes() {
+    DefaultIssue issue1 = new DefaultIssue();
+    when(targetBranchComponentUuids.getTargetBranchComponentUuid(COMPONENT_KEY)).thenReturn(COMPONENT_UUID);
+    when(componentIssuesLoader.loadOpenIssuesWithChanges(COMPONENT_UUID)).thenReturn(Collections.singletonList(issue1));
+    ComponentDto fileDto = ComponentTesting.newFileDto(ComponentTesting.newPublicProjectDto()).setUuid(COMPONENT_UUID);
+    db.fileSources().insertFileSource(fileDto, 0);
+
+    Component component = getComponent();
+    Input<DefaultIssue> input = underTest.createForTargetBranch(component);
+
+    assertThat(input.getIssues()).containsOnly(issue1);
+    assertThat(input.getLineHashSequence().length()).isZero();
+  }
+
+  @Test
+  public void gets_nothing_when_there_is_no_matching_component() {
+    Component component = getComponent();
+    Input<DefaultIssue> input = underTest.createForTargetBranch(component);
+
+    assertThat(input.getIssues()).isEmpty();
+    assertThat(input.getLineHashSequence().length()).isZero();
+  }
+
+  @Test
+  public void uses_original_component_uuid_when_component_is_moved_file() {
+    Component component = getComponent();
+    MovedFilesRepository.OriginalFile originalFile = new MovedFilesRepository.OriginalFile(ORIGINAL_COMPONENT_UUID, ORIGINAL_COMPONENT_KEY);
+    when(movedFilesRepository.getOriginalPullRequestFile(component)).thenReturn(Optional.of(originalFile));
+    when(targetBranchComponentUuids.getTargetBranchComponentUuid(ORIGINAL_COMPONENT_KEY))
+      .thenReturn(ORIGINAL_COMPONENT_UUID);
+    DefaultIssue issue1 = new DefaultIssue();
+    when(componentIssuesLoader.loadOpenIssuesWithChanges(ORIGINAL_COMPONENT_UUID)).thenReturn(Collections.singletonList(issue1));
+
+
+    Input<DefaultIssue> targetBranchIssue = underTest.createForTargetBranch(component);
+
+    verify(targetBranchComponentUuids).getTargetBranchComponentUuid(ORIGINAL_COMPONENT_KEY);
+    assertThat(targetBranchIssue.getIssues()).containsOnly(issue1);
+  }
+
+  private Component getComponent() {
+    Component component = mock(Component.class);
+    when(component.getKey()).thenReturn(COMPONENT_KEY);
+    when(component.getType()).thenReturn(Component.Type.FILE);
+    return component;
+  }
+
+  @Test
+  public void hasTargetBranchAnalysis_returns_true_if_source_branch_of_pr_was_analysed() {
+    when(targetBranchComponentUuids.hasTargetBranchAnalysis()).thenReturn(true);
+
+    assertThat(underTest.hasTargetBranchAnalysis()).isTrue();
+  }
+
+  @Test
+  public void hasTargetBranchAnalysis_returns_false_if_no_target_branch_analysis() {
+    when(targetBranchComponentUuids.hasTargetBranchAnalysis()).thenReturn(false);
+
+    assertThat(underTest.hasTargetBranchAnalysis()).isFalse();
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/measure/MeasureRepositoryImplIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/measure/MeasureRepositoryImplIT.java
new file mode 100644 (file)
index 0000000..3a0953c
--- /dev/null
@@ -0,0 +1,408 @@
+/*
+ * 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.measure;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import javax.annotation.Nullable;
+import org.junit.Before;
+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.batch.BatchReportReader;
+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.metric.Metric;
+import org.sonar.ce.task.projectanalysis.metric.MetricImpl;
+import org.sonar.ce.task.projectanalysis.metric.MetricRepository;
+import org.sonar.ce.task.projectanalysis.metric.ReportMetricValidator;
+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.SnapshotDto;
+import org.sonar.db.measure.MeasureDto;
+import org.sonar.db.metric.MetricDto;
+import org.sonar.scanner.protocol.output.ScannerReport;
+import org.sonar.scanner.protocol.output.ScannerReport.Measure.StringValue;
+
+import static com.google.common.collect.FluentIterable.from;
+import static java.lang.String.format;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+
+@RunWith(DataProviderRunner.class)
+public class MeasureRepositoryImplIT {
+
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  @Rule
+  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
+
+  private static final String FILE_COMPONENT_KEY = "file cpt key";
+  private static final ReportComponent FILE_COMPONENT = ReportComponent.builder(Component.Type.FILE, 1).setKey(FILE_COMPONENT_KEY).build();
+  private static final ReportComponent OTHER_COMPONENT = ReportComponent.builder(Component.Type.FILE, 2).setKey("some other key").build();
+  private static final String METRIC_KEY_1 = "metric 1";
+  private static final int METRIC_ID_1 = 1;
+  private static final String METRIC_KEY_2 = "metric 2";
+  private static final int METRIC_ID_2 = 2;
+  private final Metric metric1 = mock(Metric.class);
+  private final Metric metric2 = mock(Metric.class);
+  private static final String LAST_ANALYSIS_UUID = "u123";
+  private static final String OTHER_ANALYSIS_UUID = "u369";
+  private static final Measure SOME_MEASURE = Measure.newMeasureBuilder().create("some value");
+  private static final String SOME_DATA = "some data";
+
+  private ReportMetricValidator reportMetricValidator = mock(ReportMetricValidator.class);
+
+  private DbClient dbClient = dbTester.getDbClient();
+  private MetricRepository metricRepository = mock(MetricRepository.class);
+  private MeasureRepositoryImpl underTest = new MeasureRepositoryImpl(dbClient, reportReader, metricRepository, reportMetricValidator);
+
+  private DbClient mockedDbClient = mock(DbClient.class);
+  private BatchReportReader mockBatchReportReader = mock(BatchReportReader.class);
+  private MeasureRepositoryImpl underTestWithMock = new MeasureRepositoryImpl(mockedDbClient, mockBatchReportReader, metricRepository, reportMetricValidator);
+
+  private DbSession dbSession = dbTester.getSession();
+
+  @Before
+  public void setUp() {
+    when(metric1.getKey()).thenReturn(METRIC_KEY_1);
+    when(metric1.getType()).thenReturn(Metric.MetricType.STRING);
+    when(metric2.getKey()).thenReturn(METRIC_KEY_2);
+    when(metric2.getType()).thenReturn(Metric.MetricType.STRING);
+
+    // references to metrics are consistent with DB by design
+    when(metricRepository.getByKey(METRIC_KEY_1)).thenReturn(metric1);
+    when(metricRepository.getByKey(METRIC_KEY_2)).thenReturn(metric2);
+  }
+
+  @Test
+  public void getBaseMeasure_throws_NPE_and_does_not_open_session_if_component_is_null() {
+    try {
+      underTestWithMock.getBaseMeasure(null, metric1);
+      fail("an NPE should have been raised");
+    } catch (NullPointerException e) {
+      verifyNoInteractions(mockedDbClient);
+    }
+  }
+
+  @Test
+  public void getBaseMeasure_throws_NPE_and_does_not_open_session_if_metric_is_null() {
+    try {
+      underTestWithMock.getBaseMeasure(FILE_COMPONENT, null);
+      fail("an NPE should have been raised");
+    } catch (NullPointerException e) {
+      verifyNoInteractions(mockedDbClient);
+    }
+  }
+
+  @Test
+  public void getBaseMeasure_returns_absent_if_measure_does_not_exist_in_DB() {
+    Optional<Measure> res = underTest.getBaseMeasure(FILE_COMPONENT, metric1);
+
+    assertThat(res).isNotPresent();
+  }
+
+  @Test
+  public void getBaseMeasure_returns_Measure_if_measure_of_last_snapshot_only_in_DB() {
+    ComponentDto project = dbTester.components().insertPrivateProject();
+    dbTester.components().insertComponent(newFileDto(project).setUuid(FILE_COMPONENT.getUuid()));
+    SnapshotDto lastAnalysis = dbTester.components().insertSnapshot(project, t -> t.setLast(true));
+    SnapshotDto oldAnalysis = dbTester.components().insertSnapshot(project, t -> t.setLast(false));
+    MetricDto metric1 = dbTester.measures().insertMetric(t -> t.setValueType(org.sonar.api.measures.Metric.ValueType.STRING.name()));
+    MetricDto metric2 = dbTester.measures().insertMetric(t -> t.setValueType(org.sonar.api.measures.Metric.ValueType.STRING.name()));
+    dbClient.measureDao().insert(dbSession, createMeasureDto(metric1.getUuid(), FILE_COMPONENT.getUuid(), lastAnalysis.getUuid()));
+    dbClient.measureDao().insert(dbSession, createMeasureDto(metric1.getUuid(), FILE_COMPONENT.getUuid(), oldAnalysis.getUuid()));
+    dbSession.commit();
+
+    // metric 1 is associated to snapshot with "last=true"
+    assertThat(underTest.getBaseMeasure(FILE_COMPONENT, metricOf(metric1)).get().getStringValue())
+      .isEqualTo(SOME_DATA);
+    // metric 2 is associated to snapshot with "last=false" => not retrieved
+    assertThat(underTest.getBaseMeasure(FILE_COMPONENT, metricOf(metric2))).isNotPresent();
+  }
+
+  private Metric metricOf(MetricDto metricDto) {
+    Metric res = mock(Metric.class);
+    when(res.getKey()).thenReturn(metricDto.getKey());
+    when(res.getUuid()).thenReturn(metricDto.getUuid());
+    when(res.getType()).thenReturn(Metric.MetricType.valueOf(metricDto.getValueType()));
+    return res;
+  }
+
+  @Test
+  public void add_throws_NPE_if_Component_argument_is_null() {
+    assertThatThrownBy(() -> underTest.add(null, metric1, SOME_MEASURE))
+      .isInstanceOf(NullPointerException.class);
+  }
+
+  @Test
+  public void add_throws_NPE_if_Component_metric_is_null() {
+    assertThatThrownBy(() -> underTest.add(FILE_COMPONENT, null, SOME_MEASURE))
+      .isInstanceOf(NullPointerException.class);
+  }
+
+  @Test
+  public void add_throws_NPE_if_Component_measure_is_null() {
+    assertThatThrownBy(() -> underTest.add(FILE_COMPONENT, metric1, null))
+      .isInstanceOf(NullPointerException.class);
+  }
+
+  @Test
+  public void add_throws_UOE_if_measure_already_exists() {
+    assertThatThrownBy(() -> {
+      underTest.add(FILE_COMPONENT, metric1, SOME_MEASURE);
+      underTest.add(FILE_COMPONENT, metric1, SOME_MEASURE);
+    })
+      .isInstanceOf(UnsupportedOperationException.class);
+  }
+
+  @Test
+  public void update_throws_NPE_if_Component_metric_is_null() {
+    assertThatThrownBy(() -> underTest.update(FILE_COMPONENT, null, SOME_MEASURE))
+      .isInstanceOf(NullPointerException.class);
+  }
+
+  @Test
+  public void update_throws_NPE_if_Component_measure_is_null() {
+    assertThatThrownBy(() -> underTest.update(FILE_COMPONENT, metric1, null))
+      .isInstanceOf(NullPointerException.class);
+  }
+
+  @Test
+  public void update_throws_UOE_if_measure_does_not_exists() {
+    assertThatThrownBy(() -> underTest.update(FILE_COMPONENT, metric1, SOME_MEASURE))
+      .isInstanceOf(UnsupportedOperationException.class);
+  }
+
+  private static final List<Measure> MEASURES = ImmutableList.of(
+    Measure.newMeasureBuilder().create(1),
+    Measure.newMeasureBuilder().create(1L),
+    Measure.newMeasureBuilder().create(1d, 1),
+    Measure.newMeasureBuilder().create(true),
+    Measure.newMeasureBuilder().create(false),
+    Measure.newMeasureBuilder().create("sds"),
+    Measure.newMeasureBuilder().create(Measure.Level.OK),
+    Measure.newMeasureBuilder().createNoValue());
+
+  @DataProvider
+  public static Object[][] measures() {
+    return from(MEASURES).transform(new Function<Measure, Object[]>() {
+      @Nullable
+      @Override
+      public Object[] apply(Measure input) {
+        return new Measure[] {input};
+      }
+    }).toArray(Object[].class);
+  }
+
+  @Test
+  public void add_accepts_NO_VALUE_as_measure_arg() {
+    for (Metric.MetricType metricType : Metric.MetricType.values()) {
+      underTest.add(FILE_COMPONENT, new MetricImpl("1", "key" + metricType, "name" + metricType, metricType), Measure.newMeasureBuilder().createNoValue());
+    }
+  }
+
+  @Test
+  @UseDataProvider("measures")
+  public void update_throws_IAE_if_valueType_of_Measure_is_not_the_same_as_the_Metric_valueType_unless_NO_VALUE(Measure measure) {
+    for (Metric.MetricType metricType : Metric.MetricType.values()) {
+      if (metricType.getValueType() == measure.getValueType() || measure.getValueType() == Measure.ValueType.NO_VALUE) {
+        continue;
+      }
+
+      try {
+        final MetricImpl metric = new MetricImpl("1", "key" + metricType, "name" + metricType, metricType);
+        underTest.add(FILE_COMPONENT, metric, getSomeMeasureByValueType(metricType));
+        underTest.update(FILE_COMPONENT, metric, measure);
+        fail("An IllegalArgumentException should have been raised");
+      } catch (IllegalArgumentException e) {
+        assertThat(e).hasMessage(format(
+          "Measure's ValueType (%s) is not consistent with the Metric's ValueType (%s)",
+          measure.getValueType(), metricType.getValueType()));
+      }
+    }
+  }
+
+  @Test
+  public void update_accepts_NO_VALUE_as_measure_arg() {
+    for (Metric.MetricType metricType : Metric.MetricType.values()) {
+      MetricImpl metric = new MetricImpl("1", "key" + metricType, "name" + metricType, metricType);
+      underTest.add(FILE_COMPONENT, metric, getSomeMeasureByValueType(metricType));
+      underTest.update(FILE_COMPONENT, metric, Measure.newMeasureBuilder().createNoValue());
+    }
+  }
+
+  private Measure getSomeMeasureByValueType(final Metric.MetricType metricType) {
+    return MEASURES.stream().filter(input -> input.getValueType() == metricType.getValueType()).findFirst().get();
+  }
+
+  @Test
+  public void update_supports_updating_to_the_same_value() {
+    underTest.add(FILE_COMPONENT, metric1, SOME_MEASURE);
+    underTest.update(FILE_COMPONENT, metric1, SOME_MEASURE);
+  }
+
+  @Test
+  public void update_updates_the_stored_value() {
+    Measure newMeasure = Measure.updatedMeasureBuilder(SOME_MEASURE).create();
+
+    underTest.add(FILE_COMPONENT, metric1, SOME_MEASURE);
+    underTest.update(FILE_COMPONENT, metric1, newMeasure);
+
+    assertThat(underTest.getRawMeasure(FILE_COMPONENT, metric1)).containsSame(newMeasure);
+  }
+
+  @Test
+  public void getRawMeasure_throws_NPE_without_reading_batch_report_if_component_arg_is_null() {
+    try {
+      underTestWithMock.getRawMeasure(null, metric1);
+      fail("an NPE should have been raised");
+    } catch (NullPointerException e) {
+      verifyNoMoreInteractions(mockBatchReportReader);
+    }
+  }
+
+  @Test
+  public void getRawMeasure_throws_NPE_without_reading_batch_report_if_metric_arg_is_null() {
+    try {
+      underTestWithMock.getRawMeasure(FILE_COMPONENT, null);
+      fail("an NPE should have been raised");
+    } catch (NullPointerException e) {
+      verifyNoMoreInteractions(mockBatchReportReader);
+    }
+  }
+
+  @Test
+  public void getRawMeasure_returns_measure_added_through_add_method() {
+    underTest.add(FILE_COMPONENT, metric1, SOME_MEASURE);
+
+    Optional<Measure> res = underTest.getRawMeasure(FILE_COMPONENT, metric1);
+
+    assertThat(res)
+      .isPresent()
+      .containsSame(SOME_MEASURE);
+
+    // make sure we really match on the specified component and metric
+    assertThat(underTest.getRawMeasure(OTHER_COMPONENT, metric1)).isNotPresent();
+    assertThat(underTest.getRawMeasure(FILE_COMPONENT, metric2)).isNotPresent();
+  }
+
+  @Test
+  public void getRawMeasure_returns_measure_from_batch_if_not_added_through_add_method() {
+    String value = "trololo";
+
+    when(reportMetricValidator.validate(METRIC_KEY_1)).thenReturn(true);
+
+    reportReader.putMeasures(FILE_COMPONENT.getReportAttributes().getRef(), ImmutableList.of(
+      ScannerReport.Measure.newBuilder().setMetricKey(METRIC_KEY_1).setStringValue(StringValue.newBuilder().setValue(value)).build()));
+
+    Optional<Measure> res = underTest.getRawMeasure(FILE_COMPONENT, metric1);
+
+    assertThat(res).isPresent();
+    assertThat(res.get().getStringValue()).isEqualTo(value);
+
+    // make sure we really match on the specified component and metric
+    assertThat(underTest.getRawMeasure(FILE_COMPONENT, metric2)).isNotPresent();
+    assertThat(underTest.getRawMeasure(OTHER_COMPONENT, metric1)).isNotPresent();
+  }
+
+  @Test
+  public void getRawMeasure_returns_only_validate_measure_from_batch_if_not_added_through_add_method() {
+    when(reportMetricValidator.validate(METRIC_KEY_1)).thenReturn(true);
+    when(reportMetricValidator.validate(METRIC_KEY_2)).thenReturn(false);
+
+    reportReader.putMeasures(FILE_COMPONENT.getReportAttributes().getRef(), ImmutableList.of(
+      ScannerReport.Measure.newBuilder().setMetricKey(METRIC_KEY_1).setStringValue(StringValue.newBuilder().setValue("value1")).build(),
+      ScannerReport.Measure.newBuilder().setMetricKey(METRIC_KEY_2).setStringValue(StringValue.newBuilder().setValue("value2")).build()));
+
+    assertThat(underTest.getRawMeasure(FILE_COMPONENT, metric1)).isPresent();
+    assertThat(underTest.getRawMeasure(FILE_COMPONENT, metric2)).isNotPresent();
+  }
+
+  @Test
+  public void getRawMeasure_retrieves_added_measure_over_batch_measure() {
+    when(reportMetricValidator.validate(METRIC_KEY_1)).thenReturn(true);
+    reportReader.putMeasures(FILE_COMPONENT.getReportAttributes().getRef(), ImmutableList.of(
+      ScannerReport.Measure.newBuilder().setMetricKey(METRIC_KEY_1).setStringValue(StringValue.newBuilder().setValue("some value")).build()));
+
+    Measure addedMeasure = SOME_MEASURE;
+    underTest.add(FILE_COMPONENT, metric1, addedMeasure);
+
+    Optional<Measure> res = underTest.getRawMeasure(FILE_COMPONENT, metric1);
+
+    assertThat(res)
+      .isPresent()
+      .containsSame(addedMeasure);
+  }
+
+  @Test
+  public void getRawMeasure_retrieves_measure_from_batch_and_caches_it_locally_so_that_it_can_be_updated() {
+    when(reportMetricValidator.validate(METRIC_KEY_1)).thenReturn(true);
+    reportReader.putMeasures(FILE_COMPONENT.getReportAttributes().getRef(), ImmutableList.of(
+      ScannerReport.Measure.newBuilder().setMetricKey(METRIC_KEY_1).setStringValue(StringValue.newBuilder().setValue("some value")).build()));
+
+    Optional<Measure> measure = underTest.getRawMeasure(FILE_COMPONENT, metric1);
+
+    underTest.update(FILE_COMPONENT, metric1, Measure.updatedMeasureBuilder(measure.get()).create());
+  }
+
+  @Test
+  public void getRawMeasures_returns_added_measures_over_batch_measures() {
+    when(reportMetricValidator.validate(METRIC_KEY_1)).thenReturn(true);
+    when(reportMetricValidator.validate(METRIC_KEY_2)).thenReturn(true);
+    ScannerReport.Measure batchMeasure1 = ScannerReport.Measure.newBuilder().setMetricKey(METRIC_KEY_1).setStringValue(StringValue.newBuilder().setValue("some value")).build();
+    ScannerReport.Measure batchMeasure2 = ScannerReport.Measure.newBuilder().setMetricKey(METRIC_KEY_2).setStringValue(StringValue.newBuilder().setValue("some value")).build();
+    reportReader.putMeasures(FILE_COMPONENT.getReportAttributes().getRef(), ImmutableList.of(batchMeasure1, batchMeasure2));
+
+    Measure addedMeasure = SOME_MEASURE;
+    underTest.add(FILE_COMPONENT, metric1, addedMeasure);
+
+    Map<String, Measure> rawMeasures = underTest.getRawMeasures(FILE_COMPONENT);
+
+    assertThat(rawMeasures.keySet()).hasSize(2);
+    assertThat(rawMeasures).containsEntry(METRIC_KEY_1, addedMeasure);
+    assertThat(rawMeasures.get(METRIC_KEY_2)).extracting(Measure::getStringValue).isEqualTo("some value");
+  }
+
+  private static MeasureDto createMeasureDto(String metricUuid, String componentUuid, String analysisUuid) {
+    return new MeasureDto()
+      .setComponentUuid(componentUuid)
+      .setAnalysisUuid(analysisUuid)
+      .setData(SOME_DATA)
+      .setMetricUuid(metricUuid);
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/period/NewCodeReferenceBranchComponentUuidsIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/period/NewCodeReferenceBranchComponentUuidsIT.java
new file mode 100644 (file)
index 0000000..0e98bfe
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * 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.period;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+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.newcodeperiod.NewCodePeriodType;
+import org.sonar.server.project.Project;
+
+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.db.component.SnapshotTesting.newAnalysis;
+
+public class NewCodeReferenceBranchComponentUuidsIT {
+  @Rule
+  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
+
+  @Rule
+  public PeriodHolderRule periodHolder = new PeriodHolderRule();
+  @Rule
+  public DbTester db = DbTester.create();
+
+  private NewCodeReferenceBranchComponentUuids underTest = new NewCodeReferenceBranchComponentUuids(analysisMetadataHolder, periodHolder, db.getDbClient());
+
+  private ComponentDto branch1;
+  private ComponentDto branch1File;
+  private ComponentDto pr1File;
+  private ComponentDto pr2File;
+  private Project project = mock(Project.class);
+  private ComponentDto pr1;
+  private ComponentDto pr2;
+  private ComponentDto branch2;
+  private ComponentDto branch2File;
+
+  @Before
+  public void setUp() {
+    analysisMetadataHolder.setProject(project);
+
+    ComponentDto projectDto = db.components().insertPublicProject();
+    when(project.getUuid()).thenReturn(projectDto.uuid());
+    branch1 = db.components().insertProjectBranch(projectDto, b -> b.setKey("branch1"));
+    branch2 = db.components().insertProjectBranch(projectDto, b -> b.setKey("branch2"));
+    pr1 = db.components().insertProjectBranch(projectDto, b -> b.setKey("pr1").setBranchType(BranchType.PULL_REQUEST).setMergeBranchUuid(branch1.uuid()));
+    pr2 = db.components().insertProjectBranch(projectDto, b -> b.setKey("pr2").setBranchType(BranchType.PULL_REQUEST).setMergeBranchUuid(branch1.uuid()));
+    branch1File = ComponentTesting.newFileDto(branch1, null, "file").setUuid("branch1File");
+    branch2File = ComponentTesting.newFileDto(branch2, null, "file").setUuid("branch2File");
+    pr1File = ComponentTesting.newFileDto(pr1, null, "file").setUuid("file1");
+    pr2File = ComponentTesting.newFileDto(pr2, null, "file").setUuid("file2");
+    db.components().insertComponents(branch1File, pr1File, pr2File, branch2File);
+  }
+
+  @Test
+  public void should_support_db_key_when_looking_for_reference_component() {
+    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "branch1", null));
+    db.components().insertSnapshot(newAnalysis(branch1));
+    assertThat(underTest.getComponentUuid(pr1File.getKey())).isEqualTo(branch1File.uuid());
+  }
+
+  @Test
+  public void should_support_key_when_looking_for_reference_component() {
+    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "branch1", null));
+    db.components().insertSnapshot(newAnalysis(branch1));
+    assertThat(underTest.getComponentUuid(pr1File.getKey())).isEqualTo(branch1File.uuid());
+  }
+
+  @Test
+  public void return_null_if_file_doesnt_exist() {
+    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "branch1", null));
+    db.components().insertSnapshot(newAnalysis(branch1));
+    assertThat(underTest.getComponentUuid("doesnt exist")).isNull();
+  }
+
+  @Test
+  public void skip_init_if_no_reference_branch_analysis() {
+    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "branch1", null));
+    assertThat(underTest.getComponentUuid(pr1File.getKey())).isNull();
+  }
+
+  @Test
+  public void skip_init_if_branch_not_found() {
+    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "unknown", null));
+    assertThat(underTest.getComponentUuid(pr1File.getKey())).isNull();
+  }
+
+  @Test
+  public void throw_ise_if_mode_is_not_reference_branch() {
+    periodHolder.setPeriod(new Period(NewCodePeriodType.NUMBER_OF_DAYS.name(), "10", 1000L));
+    assertThatThrownBy(() -> underTest.getComponentUuid(pr1File.getKey()))
+      .isInstanceOf(IllegalStateException.class);
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/source/DbLineHashVersionIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/source/DbLineHashVersionIT.java
new file mode 100644 (file)
index 0000000..bae1631
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * 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.source;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.ReferenceBranchComponentUuids;
+import org.sonar.ce.task.projectanalysis.component.ReportComponent;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.source.LineHashVersion;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class DbLineHashVersionIT {
+  @Rule
+  public DbTester db = DbTester.create();
+
+  private AnalysisMetadataHolder analysisMetadataHolder = mock(AnalysisMetadataHolder.class);
+  private ReferenceBranchComponentUuids referenceBranchComponentUuids = mock(ReferenceBranchComponentUuids.class);
+  private DbLineHashVersion underTest = new DbLineHashVersion(db.getDbClient(), analysisMetadataHolder, referenceBranchComponentUuids);
+
+  @Test
+  public void hasLineHashWithSignificantCode_should_return_true() {
+    ComponentDto project = db.components().insertPublicProject();
+    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
+
+    db.fileSources().insertFileSource(file, dto -> dto.setLineHashesVersion(LineHashVersion.WITH_SIGNIFICANT_CODE.getDbValue()));
+    Component component = ReportComponent.builder(Component.Type.FILE, 1).setKey("key").setUuid(file.uuid()).build();
+    assertThat(underTest.hasLineHashesWithSignificantCode(component)).isTrue();
+  }
+
+  @Test
+  public void hasLineHashWithSignificantCode_should_return_false_if_file_is_not_found() {
+    Component component = ReportComponent.builder(Component.Type.FILE, 1).setKey("key").setUuid("123").build();
+    assertThat(underTest.hasLineHashesWithSignificantCode(component)).isFalse();
+  }
+
+  @Test
+  public void hasLineHashWithSignificantCode_should_return_false_if_pr_reference_doesnt_have_file() {
+    when(analysisMetadataHolder.isPullRequest()).thenReturn(true);
+    Component component = ReportComponent.builder(Component.Type.FILE, 1).setKey("key").setUuid("123").build();
+    assertThat(underTest.hasLineHashesWithSignificantCode(component)).isFalse();
+
+    verify(analysisMetadataHolder).isPullRequest();
+    verify(referenceBranchComponentUuids).getComponentUuid(component.getKey());
+  }
+
+  @Test
+  public void hasLineHashWithSignificantCode_should_return_false_if_pr_reference_has_file_but_it_is_not_in_db() {
+    ComponentDto project = db.components().insertPublicProject();
+    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
+
+    when(analysisMetadataHolder.isPullRequest()).thenReturn(true);
+    when(referenceBranchComponentUuids.getComponentUuid("key")).thenReturn(file.uuid());
+
+    Component component = ReportComponent.builder(Component.Type.FILE, 1).setKey("key").setUuid("123").build();
+    assertThat(underTest.hasLineHashesWithSignificantCode(component)).isFalse();
+
+    verify(analysisMetadataHolder).isPullRequest();
+    verify(referenceBranchComponentUuids).getComponentUuid(component.getKey());
+  }
+
+  @Test
+  public void hasLineHashWithSignificantCode_should_return_true_if_pr_reference_has_file_and_it_is_in_db() {
+    ComponentDto project = db.components().insertPublicProject();
+    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
+    db.fileSources().insertFileSource(file, dto -> dto.setLineHashesVersion(LineHashVersion.WITH_SIGNIFICANT_CODE.getDbValue()));
+
+    when(analysisMetadataHolder.isPullRequest()).thenReturn(true);
+    when(referenceBranchComponentUuids.getComponentUuid("key")).thenReturn(file.uuid());
+
+    Component component = ReportComponent.builder(Component.Type.FILE, 1).setKey("key").setUuid("123").build();
+    assertThat(underTest.hasLineHashesWithSignificantCode(component)).isTrue();
+
+    verify(analysisMetadataHolder).isPullRequest();
+    verify(referenceBranchComponentUuids).getComponentUuid(component.getKey());
+  }
+
+  @Test
+  public void should_cache_line_hash_version_from_db() {
+    ComponentDto project = db.components().insertPublicProject();
+    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
+
+    db.fileSources().insertFileSource(file, dto -> dto.setLineHashesVersion(LineHashVersion.WITH_SIGNIFICANT_CODE.getDbValue()));
+    Component component = ReportComponent.builder(Component.Type.FILE, 1).setKey("key").setUuid(file.uuid()).build();
+    assertThat(underTest.hasLineHashesWithSignificantCode(component)).isTrue();
+
+    assertThat(db.countRowsOfTable("file_sources")).isOne();
+    db.executeUpdateSql("delete from file_sources");
+    db.commit();
+    assertThat(db.countRowsOfTable("file_sources")).isZero();
+
+    // still true because it uses cache
+    assertThat(underTest.hasLineHashesWithSignificantCode(component)).isTrue();
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/source/PersistFileSourcesStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/source/PersistFileSourcesStepIT.java
new file mode 100644 (file)
index 0000000..eb795c8
--- /dev/null
@@ -0,0 +1,482 @@
+/*
+ * 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.source;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Consumer;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.FileAttributes;
+import org.sonar.ce.task.projectanalysis.component.PreviousSourceHashRepository;
+import org.sonar.ce.task.projectanalysis.component.ReportComponent;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
+import org.sonar.ce.task.projectanalysis.scm.Changeset;
+import org.sonar.ce.task.projectanalysis.step.BaseStepTest;
+import org.sonar.ce.task.step.ComputationStep;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.core.util.SequenceUuidFactory;
+import org.sonar.core.util.Uuids;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.protobuf.DbFileSources;
+import org.sonar.db.source.FileHashesDto;
+import org.sonar.db.source.FileSourceDto;
+import org.sonar.db.source.LineHashVersion;
+
+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 PersistFileSourcesStepIT extends BaseStepTest {
+
+  private static final int FILE1_REF = 3;
+  private static final String PROJECT_UUID = "PROJECT";
+  private static final String PROJECT_KEY = "PROJECT_KEY";
+  private static final String FILE1_UUID = "FILE1";
+  private static final long NOW = 123456789L;
+  private static final long PAST = 15000L;
+
+  private final System2 system2 = mock(System2.class);
+
+  @Rule
+  public DbTester dbTester = DbTester.create(system2);
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+
+  private final SourceLinesHashRepository sourceLinesHashRepository = mock(SourceLinesHashRepository.class);
+  private final SourceLinesHashRepositoryImpl.LineHashesComputer lineHashesComputer = mock(SourceLinesHashRepositoryImpl.LineHashesComputer.class);
+  private final FileSourceDataComputer fileSourceDataComputer = mock(FileSourceDataComputer.class);
+  private final FileSourceDataWarnings fileSourceDataWarnings = mock(FileSourceDataWarnings.class);
+  private final PreviousSourceHashRepository previousSourceHashRepository = mock(PreviousSourceHashRepository.class);
+
+  private final DbClient dbClient = dbTester.getDbClient();
+  private final DbSession session = dbTester.getSession();
+
+  private PersistFileSourcesStep underTest;
+
+  @Before
+  public void setup() {
+    when(system2.now()).thenReturn(NOW);
+    when(sourceLinesHashRepository.getLineHashesComputerToPersist(any(Component.class))).thenReturn(lineHashesComputer);
+    underTest = new PersistFileSourcesStep(dbClient, system2, treeRootHolder, sourceLinesHashRepository, fileSourceDataComputer, fileSourceDataWarnings,
+      new SequenceUuidFactory(), previousSourceHashRepository);
+    initBasicReport(1);
+  }
+
+  @Override
+  protected ComputationStep step() {
+    return underTest;
+  }
+
+  @Test
+  public void persist_sources() {
+    List<String> lineHashes = Arrays.asList("137f72c3708c6bd0de00a0e5a69c699b", "e6251bcf1a7dc3ba5e7933e325bbe605");
+    String sourceHash = "ee5a58024a155466b43bc559d953e018";
+    DbFileSources.Data fileSourceData = DbFileSources.Data.newBuilder()
+      .addAllLines(Arrays.asList(
+        DbFileSources.Line.newBuilder().setSource("line1").setLine(1).build(),
+        DbFileSources.Line.newBuilder().setSource("line2").setLine(2).build()))
+      .build();
+    when(fileSourceDataComputer.compute(fileComponent().build(), fileSourceDataWarnings))
+      .thenReturn(new FileSourceDataComputer.Data(fileSourceData, lineHashes, sourceHash, null));
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbTester.countRowsOfTable("file_sources")).isOne();
+    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
+    assertThat(fileSourceDto.getProjectUuid()).isEqualTo(PROJECT_UUID);
+    assertThat(fileSourceDto.getFileUuid()).isEqualTo(FILE1_UUID);
+    assertThat(fileSourceDto.getBinaryData()).isNotEmpty();
+    assertThat(fileSourceDto.getDataHash()).isNotEmpty();
+    assertThat(fileSourceDto.getLineHashesVersion()).isEqualTo(LineHashVersion.WITHOUT_SIGNIFICANT_CODE.getDbValue());
+    assertThat(fileSourceDto.getLineHashes()).isNotEmpty();
+    assertThat(fileSourceDto.getCreatedAt()).isEqualTo(NOW);
+    assertThat(fileSourceDto.getUpdatedAt()).isEqualTo(NOW);
+
+    DbFileSources.Data data = fileSourceDto.getSourceData();
+    assertThat(data.getLinesCount()).isEqualTo(2);
+    assertThat(data.getLines(0).getLine()).isOne();
+    assertThat(data.getLines(0).getSource()).isEqualTo("line1");
+    assertThat(data.getLines(1).getLine()).isEqualTo(2);
+    assertThat(data.getLines(1).getSource()).isEqualTo("line2");
+    verify(fileSourceDataWarnings).commitWarnings();
+  }
+
+  @Test
+  public void persist_source_hashes() {
+    List<String> lineHashes = Arrays.asList("137f72c3708c6bd0de00a0e5a69c699b", "e6251bcf1a7dc3ba5e7933e325bbe605");
+    String sourceHash = "ee5a58024a155466b43bc559d953e018";
+    setComputedData(DbFileSources.Data.newBuilder().build(), lineHashes, sourceHash, null);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbTester.countRowsOfTable("file_sources")).isOne();
+    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
+    assertThat(fileSourceDto.getLineHashes()).containsExactly("137f72c3708c6bd0de00a0e5a69c699b", "e6251bcf1a7dc3ba5e7933e325bbe605");
+    assertThat(fileSourceDto.getSrcHash()).isEqualTo("ee5a58024a155466b43bc559d953e018");
+    verify(fileSourceDataWarnings).commitWarnings();
+  }
+
+  @Test
+  public void persist_coverage() {
+    DbFileSources.Data dbData = DbFileSources.Data.newBuilder().addLines(
+      DbFileSources.Line.newBuilder()
+        .setConditions(10)
+        .setCoveredConditions(2)
+        .setLineHits(1)
+        .setLine(1)
+        .build())
+      .build();
+    setComputedData(dbData);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbTester.countRowsOfTable("file_sources")).isOne();
+    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
+    assertThat(fileSourceDto.getSourceData()).isEqualTo(dbData);
+    verify(fileSourceDataWarnings).commitWarnings();
+  }
+
+  private ReportComponent.Builder fileComponent() {
+    return ReportComponent.builder(Component.Type.FILE, FILE1_REF).setUuid(FILE1_UUID).setKey("PROJECT_KEY" + ":src/Foo.java");
+  }
+
+  @Test
+  public void persist_scm() {
+    DbFileSources.Data dbData = DbFileSources.Data.newBuilder().addLines(
+      DbFileSources.Line.newBuilder()
+        .setScmAuthor("john")
+        .setScmDate(123456789L)
+        .setScmRevision("rev-1")
+        .build())
+      .build();
+    setComputedData(dbData);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbTester.countRowsOfTable("file_sources")).isOne();
+    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
+    assertThat(fileSourceDto.getSourceData()).isEqualTo(dbData);
+    assertThat(fileSourceDto.getRevision()).isNull();
+    verify(fileSourceDataWarnings).commitWarnings();
+  }
+
+  @Test
+  public void persist_scm_some_lines() {
+    DbFileSources.Data dbData = DbFileSources.Data.newBuilder().addAllLines(Arrays.asList(
+      DbFileSources.Line.newBuilder()
+        .setScmAuthor("john")
+        .setScmDate(123456789L)
+        .setScmRevision("rev-1")
+        .build(),
+      DbFileSources.Line.newBuilder()
+        .setScmDate(223456789L)
+        .build(),
+      DbFileSources.Line.newBuilder()
+        .build()))
+      .build();
+    setComputedData(dbData);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbTester.countRowsOfTable("file_sources")).isOne();
+    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
+
+    DbFileSources.Data data = fileSourceDto.getSourceData();
+
+    assertThat(data.getLinesList()).hasSize(3);
+
+    assertThat(data.getLines(0).getScmAuthor()).isEqualTo("john");
+    assertThat(data.getLines(0).getScmDate()).isEqualTo(123456789L);
+    assertThat(data.getLines(0).getScmRevision()).isEqualTo("rev-1");
+
+    assertThat(data.getLines(1).getScmAuthor()).isEmpty();
+    assertThat(data.getLines(1).getScmDate()).isEqualTo(223456789L);
+    assertThat(data.getLines(1).getScmRevision()).isEmpty();
+
+    assertThat(data.getLines(2).getScmAuthor()).isEmpty();
+    assertThat(data.getLines(2).getScmDate()).isZero();
+    assertThat(data.getLines(2).getScmRevision()).isEmpty();
+    verify(fileSourceDataWarnings).commitWarnings();
+  }
+
+  @Test
+  public void persist_highlighting() {
+    DbFileSources.Data dbData = DbFileSources.Data.newBuilder().addLines(
+      DbFileSources.Line.newBuilder()
+        .setHighlighting("2,4,a")
+        .build())
+      .build();
+    setComputedData(dbData);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbTester.countRowsOfTable("file_sources")).isOne();
+    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
+    DbFileSources.Data data = fileSourceDto.getSourceData();
+    assertThat(data).isEqualTo(dbData);
+    assertThat(data.getLinesList()).hasSize(1);
+    assertThat(data.getLines(0).getHighlighting()).isEqualTo("2,4,a");
+    verify(fileSourceDataWarnings).commitWarnings();
+  }
+
+  @Test
+  public void persist_symbols() {
+    DbFileSources.Data dbData = DbFileSources.Data.newBuilder().addAllLines(Arrays.asList(
+      DbFileSources.Line.newBuilder()
+        .setSymbols("2,4,1")
+        .build(),
+      DbFileSources.Line.newBuilder().build(),
+      DbFileSources.Line.newBuilder()
+        .setSymbols("1,3,1")
+        .build()))
+      .build();
+    setComputedData(dbData);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbTester.countRowsOfTable("file_sources")).isOne();
+    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
+    assertThat(fileSourceDto.getSourceData()).isEqualTo(dbData);
+    verify(fileSourceDataWarnings).commitWarnings();
+  }
+
+  @Test
+  public void persist_duplication() {
+    DbFileSources.Data dbData = DbFileSources.Data.newBuilder().addLines(
+      DbFileSources.Line.newBuilder()
+        .addDuplication(2)
+        .build())
+      .build();
+    setComputedData(dbData);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbTester.countRowsOfTable("file_sources")).isOne();
+    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
+    assertThat(fileSourceDto.getSourceData()).isEqualTo(dbData);
+    verify(fileSourceDataWarnings).commitWarnings();
+  }
+
+  @Test
+  public void save_revision() {
+    Changeset latest = Changeset.newChangesetBuilder().setDate(0L).setRevision("rev-1").build();
+    setComputedData(DbFileSources.Data.newBuilder().build(), Collections.singletonList("lineHashes"), "srcHash", latest);
+
+    underTest.execute(new TestComputationStepContext());
+
+    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
+    assertThat(fileSourceDto.getRevision()).isEqualTo("rev-1");
+    verify(fileSourceDataWarnings).commitWarnings();
+  }
+
+  @Test
+  public void not_save_revision() {
+    setComputedData(DbFileSources.Data.newBuilder().build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
+    assertThat(fileSourceDto.getRevision()).isNull();
+    verify(fileSourceDataWarnings).commitWarnings();
+  }
+
+  @Test
+  public void not_update_sources_when_nothing_has_changed() {
+    setPastAnalysisHashes();
+    dbClient.fileSourceDao().insert(dbTester.getSession(), createDto());
+    dbTester.getSession().commit();
+
+    Changeset changeset = Changeset.newChangesetBuilder().setDate(1L).setRevision("rev-1").build();
+    setComputedData(DbFileSources.Data.newBuilder().build(), Collections.singletonList("lineHash"), "sourceHash", changeset);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbTester.countRowsOfTable("file_sources")).isOne();
+    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
+    assertThat(fileSourceDto.getSrcHash()).isEqualTo("sourceHash");
+    assertThat(fileSourceDto.getLineHashes()).isEqualTo(Collections.singletonList("lineHash"));
+    assertThat(fileSourceDto.getCreatedAt()).isEqualTo(PAST);
+    assertThat(fileSourceDto.getUpdatedAt()).isEqualTo(PAST);
+    verify(fileSourceDataWarnings).commitWarnings();
+  }
+
+  @Test
+  public void update_sources_when_source_updated() {
+    // Existing sources
+    long past = 150000L;
+    FileSourceDto dbFileSources = new FileSourceDto()
+      .setUuid(Uuids.createFast())
+      .setProjectUuid(PROJECT_UUID)
+      .setFileUuid(FILE1_UUID)
+      .setSrcHash("5b4bd9815cdb17b8ceae19eb1810c34c")
+      .setLineHashes(Collections.singletonList("6438c669e0d0de98e6929c2cc0fac474"))
+      .setDataHash("6cad150e3d065976c230cddc5a09efaa")
+      .setSourceData(DbFileSources.Data.newBuilder()
+        .addLines(DbFileSources.Line.newBuilder()
+          .setLine(1)
+          .setSource("old line")
+          .build())
+        .build())
+      .setCreatedAt(past)
+      .setUpdatedAt(past)
+      .setRevision("rev-0");
+    dbClient.fileSourceDao().insert(dbTester.getSession(), dbFileSources);
+    dbTester.getSession().commit();
+    setPastAnalysisHashes(dbFileSources);
+
+    DbFileSources.Data newSourceData = DbFileSources.Data.newBuilder()
+      .addLines(DbFileSources.Line.newBuilder()
+        .setLine(1)
+        .setSource("old line")
+        .setScmDate(123456789L)
+        .setScmRevision("rev-1")
+        .setScmAuthor("john")
+        .build())
+      .build();
+
+    Changeset changeset = Changeset.newChangesetBuilder().setDate(1L).setRevision("rev-1").build();
+    setComputedData(newSourceData, Collections.singletonList("6438c669e0d0de98e6929c2cc0fac474"), "5b4bd9815cdb17b8ceae19eb1810c34c", changeset);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbTester.countRowsOfTable("file_sources")).isOne();
+    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
+    assertThat(fileSourceDto.getCreatedAt()).isEqualTo(past);
+    assertThat(fileSourceDto.getUpdatedAt()).isEqualTo(NOW);
+    assertThat(fileSourceDto.getRevision()).isEqualTo("rev-1");
+    verify(fileSourceDataWarnings).commitWarnings();
+  }
+
+  @Test
+  public void update_sources_when_src_hash_is_missing() {
+    FileSourceDto dbFileSources = createDto(dto -> dto.setSrcHash(null));
+    dbClient.fileSourceDao().insert(dbTester.getSession(), dbFileSources);
+    dbTester.getSession().commit();
+    setPastAnalysisHashes(dbFileSources);
+
+    DbFileSources.Data sourceData = DbFileSources.Data.newBuilder().build();
+    setComputedData(sourceData, Collections.singletonList("lineHash"), "newSourceHash", null);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbTester.countRowsOfTable("file_sources")).isOne();
+    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
+    assertThat(fileSourceDto.getCreatedAt()).isEqualTo(PAST);
+    assertThat(fileSourceDto.getUpdatedAt()).isEqualTo(NOW);
+    assertThat(fileSourceDto.getSrcHash()).isEqualTo("newSourceHash");
+    verify(fileSourceDataWarnings).commitWarnings();
+  }
+
+  @Test
+  public void update_sources_when_revision_is_missing() {
+    DbFileSources.Data sourceData = DbFileSources.Data.newBuilder()
+      .addLines(DbFileSources.Line.newBuilder()
+        .setLine(1)
+        .setSource("line")
+        .build())
+      .build();
+
+    FileSourceDto dbFileSources = createDto(dto -> dto.setRevision(null));
+    dbClient.fileSourceDao().insert(dbTester.getSession(), dbFileSources);
+    dbTester.getSession().commit();
+    setPastAnalysisHashes(dbFileSources);
+
+    Changeset changeset = Changeset.newChangesetBuilder().setDate(1L).setRevision("revision").build();
+    setComputedData(sourceData, Collections.singletonList("137f72c3708c6bd0de00a0e5a69c699b"), "29f25900140c94db38035128cb6de6a2", changeset);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbTester.countRowsOfTable("file_sources")).isOne();
+    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
+    assertThat(fileSourceDto.getCreatedAt()).isEqualTo(PAST);
+    assertThat(fileSourceDto.getUpdatedAt()).isEqualTo(NOW);
+    assertThat(fileSourceDto.getRevision()).isEqualTo("revision");
+    verify(fileSourceDataWarnings).commitWarnings();
+  }
+
+  private FileSourceDto createDto() {
+    return createDto(dto -> {
+    });
+  }
+
+  private FileSourceDto createDto(Consumer<FileSourceDto> modifier) {
+    DbFileSources.Data sourceData = DbFileSources.Data.newBuilder().build();
+    byte[] data = FileSourceDto.encodeSourceData(sourceData);
+    String dataHash = DigestUtils.md5Hex(data);
+
+    FileSourceDto dto = new FileSourceDto()
+      .setUuid(Uuids.createFast())
+      .setProjectUuid(PROJECT_UUID)
+      .setFileUuid(FILE1_UUID)
+      .setSrcHash("sourceHash")
+      .setLineHashes(Collections.singletonList("lineHash"))
+      .setDataHash(dataHash)
+      .setRevision("rev-1")
+      .setSourceData(sourceData)
+      .setCreatedAt(PAST)
+      .setUpdatedAt(PAST);
+
+    modifier.accept(dto);
+    return dto;
+  }
+
+  private void setPastAnalysisHashes() {
+    DbFileSources.Data sourceData = DbFileSources.Data.newBuilder().build();
+    byte[] data = FileSourceDto.encodeSourceData(sourceData);
+    String dataHash = DigestUtils.md5Hex(data);
+    FileHashesDto fileHashesDto = new FileHashesDto()
+      .setSrcHash("sourceHash")
+      .setDataHash(dataHash)
+      .setRevision("rev-1");
+    setPastAnalysisHashes(fileHashesDto);
+  }
+
+  private void setPastAnalysisHashes(FileHashesDto fileHashesDto) {
+    when(previousSourceHashRepository.getDbFile(any(Component.class))).thenReturn(Optional.of(fileHashesDto));
+  }
+
+  private void setComputedData(DbFileSources.Data data, List<String> lineHashes, String sourceHash, Changeset latestChangeWithRevision) {
+    FileSourceDataComputer.Data computedData = new FileSourceDataComputer.Data(data, lineHashes, sourceHash, latestChangeWithRevision);
+    when(fileSourceDataComputer.compute(fileComponent().build(), fileSourceDataWarnings)).thenReturn(computedData);
+  }
+
+  private void setComputedData(DbFileSources.Data data) {
+    FileSourceDataComputer.Data computedData = new FileSourceDataComputer.Data(data, Collections.emptyList(), "", null);
+    when(fileSourceDataComputer.compute(fileComponent().build(), fileSourceDataWarnings)).thenReturn(computedData);
+  }
+
+  private void initBasicReport(int numberOfLines) {
+    ReportComponent root = ReportComponent.builder(Component.Type.PROJECT, 1).setUuid(PROJECT_UUID).setKey(PROJECT_KEY).addChildren(
+      fileComponent().setFileAttributes(new FileAttributes(false, null, numberOfLines)).build())
+      .build();
+    treeRootHolder.setRoots(root, root);
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/CleanIssueChangesStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/CleanIssueChangesStepIT.java
new file mode 100644 (file)
index 0000000..7911b15
--- /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.ce.task.projectanalysis.step;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.ce.task.projectanalysis.issue.IssueChangesToDeleteRepository;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.db.DbTester;
+import org.sonar.db.issue.IssueChangeDto;
+import org.sonar.db.issue.IssueDto;
+
+import static java.util.Collections.singleton;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class CleanIssueChangesStepIT {
+  @Rule
+  public DbTester db = DbTester.create();
+  private final IssueChangesToDeleteRepository repository = new IssueChangesToDeleteRepository();
+  private final CleanIssueChangesStep cleanIssueChangesStep = new CleanIssueChangesStep(repository, db.getDbClient());
+  private final TestComputationStepContext context = new TestComputationStepContext();
+
+  @Test
+  public void steps_deletes_all_changes_in_repository() {
+    IssueDto issue1 = db.issues().insert();
+    IssueChangeDto change1 = db.issues().insertChange(issue1);
+    IssueChangeDto change2 = db.issues().insertChange(issue1);
+
+    repository.add(change1.getUuid());
+
+    cleanIssueChangesStep.execute(context);
+    assertThat(db.getDbClient().issueChangeDao().selectByIssueKeys(db.getSession(), singleton(issue1.getKey())))
+      .extracting(IssueChangeDto::getUuid)
+      .containsOnly(change2.getUuid());
+  }
+
+  @Test
+  public void steps_does_nothing_if_no_uuid() {
+    IssueDto issue1 = db.issues().insert();
+    IssueChangeDto change1 = db.issues().insertChange(issue1);
+    IssueChangeDto change2 = db.issues().insertChange(issue1);
+
+    cleanIssueChangesStep.execute(context);
+
+    assertThat(db.getDbClient().issueChangeDao().selectByIssueKeys(db.getSession(), singleton(issue1.getKey())))
+      .extracting(IssueChangeDto::getUuid)
+      .containsOnly(change1.getUuid(), change2.getUuid());
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/EnableAnalysisStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/EnableAnalysisStepIT.java
new file mode 100644 (file)
index 0000000..e96c977
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * 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.Optional;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.projectanalysis.analysis.MutableAnalysisMetadataHolderRule;
+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.step.TestComputationStepContext;
+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 static org.assertj.core.api.Assertions.assertThat;
+
+public class EnableAnalysisStepIT {
+
+  private static final ReportComponent REPORT_PROJECT = ReportComponent.builder(Component.Type.PROJECT, 1).build();
+  private static final String PREVIOUS_ANALYSIS_UUID = "ANALYSIS_1";
+  private static final String CURRENT_ANALYSIS_UUID = "ANALYSIS_2";
+
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+
+  @Rule
+  public MutableAnalysisMetadataHolderRule analysisMetadataHolder = new MutableAnalysisMetadataHolderRule();
+
+  private EnableAnalysisStep underTest = new EnableAnalysisStep(db.getDbClient(), treeRootHolder, analysisMetadataHolder);
+
+  @Test
+  public void switch_islast_flag_and_mark_analysis_as_processed() {
+    ComponentDto project = ComponentTesting.newPrivateProjectDto(REPORT_PROJECT.getUuid());
+    db.components().insertComponent(project);
+    insertAnalysis(project, PREVIOUS_ANALYSIS_UUID, SnapshotDto.STATUS_PROCESSED, true);
+    insertAnalysis(project, CURRENT_ANALYSIS_UUID, SnapshotDto.STATUS_UNPROCESSED, false);
+    db.commit();
+    treeRootHolder.setRoot(REPORT_PROJECT);
+    analysisMetadataHolder.setUuid(CURRENT_ANALYSIS_UUID);
+
+    underTest.execute(new TestComputationStepContext());
+
+    verifyAnalysis(PREVIOUS_ANALYSIS_UUID, SnapshotDto.STATUS_PROCESSED, false);
+    verifyAnalysis(CURRENT_ANALYSIS_UUID, SnapshotDto.STATUS_PROCESSED, true);
+  }
+
+  @Test
+  public void set_islast_flag_and_mark_as_processed_if_no_previous_analysis() {
+    ComponentDto project = ComponentTesting.newPrivateProjectDto(REPORT_PROJECT.getUuid());
+    db.components().insertComponent(project);
+    insertAnalysis(project, CURRENT_ANALYSIS_UUID, SnapshotDto.STATUS_UNPROCESSED, false);
+    db.commit();
+    treeRootHolder.setRoot(REPORT_PROJECT);
+    analysisMetadataHolder.setUuid(CURRENT_ANALYSIS_UUID);
+
+    underTest.execute(new TestComputationStepContext());
+
+    verifyAnalysis(CURRENT_ANALYSIS_UUID, SnapshotDto.STATUS_PROCESSED, true);
+  }
+
+  private void verifyAnalysis(String uuid, String expectedStatus, boolean expectedLastFlag) {
+    Optional<SnapshotDto> analysis = db.getDbClient().snapshotDao().selectByUuid(db.getSession(), uuid);
+    assertThat(analysis.get().getStatus()).isEqualTo(expectedStatus);
+    assertThat(analysis.get().getLast()).isEqualTo(expectedLastFlag);
+  }
+
+  private void insertAnalysis(ComponentDto project, String uuid, String status, boolean isLastFlag) {
+    SnapshotDto snapshot = SnapshotTesting.newAnalysis(project)
+      .setLast(isLastFlag)
+      .setStatus(status)
+      .setUuid(uuid);
+    db.getDbClient().snapshotDao().insert(db.getSession(), snapshot);
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ExtractReportStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ExtractReportStepIT.java
new file mode 100644 (file)
index 0000000..edea98f
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * 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.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import org.apache.commons.io.FileUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.impl.utils.JUnitTempFolder;
+import org.sonar.api.utils.MessageException;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.ZipUtils;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.ce.task.CeTask;
+import org.sonar.ce.task.projectanalysis.batch.BatchReportDirectoryHolderImpl;
+import org.sonar.ce.task.projectanalysis.batch.MutableBatchReportDirectoryHolder;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.db.DbTester;
+import org.sonar.db.ce.CeTaskTypes;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+public class ExtractReportStepIT {
+
+  private static final String TASK_UUID = "1";
+
+  @Rule
+  public JUnitTempFolder tempFolder = new JUnitTempFolder();
+
+  @Rule
+  public LogTester logTester = new LogTester();
+
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private MutableBatchReportDirectoryHolder reportDirectoryHolder = new BatchReportDirectoryHolderImpl();
+  private CeTask ceTask = new CeTask.Builder()
+    .setType(CeTaskTypes.REPORT)
+    .setUuid(TASK_UUID)
+    .build();
+
+  private ExtractReportStep underTest = new ExtractReportStep(dbTester.getDbClient(), ceTask, tempFolder, reportDirectoryHolder);
+
+  @Test
+  public void fail_if_report_zip_does_not_exist() {
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(MessageException.class)
+      .hasMessage("Analysis report 1 is missing in database");
+  }
+
+  @Test
+  public void unzip_report() throws Exception {
+    logTester.setLevel(LoggerLevel.DEBUG);
+    File reportFile = generateReport();
+    try (InputStream input = FileUtils.openInputStream(reportFile)) {
+      dbTester.getDbClient().ceTaskInputDao().insert(dbTester.getSession(), TASK_UUID, input);
+    }
+    dbTester.getSession().commit();
+    dbTester.getSession().close();
+
+    underTest.execute(new TestComputationStepContext());
+
+    // directory contains the uncompressed report (which contains only metadata.pb in this test)
+    File unzippedDir = reportDirectoryHolder.getDirectory();
+    assertThat(unzippedDir).isDirectory().exists();
+    assertThat(unzippedDir.listFiles()).hasSize(1);
+    assertThat(new File(unzippedDir, "metadata.pb")).hasContent("{metadata}");
+
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).anyMatch(log -> log.matches("Analysis report is \\d+ bytes uncompressed"));
+  }
+
+  @Test
+  public void unzip_report_should_fail_if_unzip_size_exceed_threshold() throws Exception {
+    logTester.setLevel(LoggerLevel.DEBUG);
+    URL zipBombFile = getClass().getResource("/org/sonar/ce/task/projectanalysis/step/ExtractReportStepIT/zip-bomb.zip");
+    try (InputStream input = zipBombFile.openStream()) {
+      dbTester.getDbClient().ceTaskInputDao().insert(dbTester.getSession(), TASK_UUID, input);
+    }
+    dbTester.getSession().commit();
+    dbTester.getSession().close();
+
+    TestComputationStepContext testComputationStepContext = new TestComputationStepContext();
+    assertThatThrownBy(() -> underTest.execute(testComputationStepContext))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Decompression failed because unzipped size reached threshold: 4000000000 bytes");
+  }
+
+  private File generateReport() throws IOException {
+    File zipDir = tempFolder.newDir();
+    File metadataFile = new File(zipDir, "metadata.pb");
+    FileUtils.write(metadataFile, "{metadata}");
+    File zip = tempFolder.newFile();
+    ZipUtils.zipDir(zipDir, zip);
+    return zip;
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/IndexAnalysisStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/IndexAnalysisStepIT.java
new file mode 100644 (file)
index 0000000..342ffe9
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * 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.Set;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.FileStatuses;
+import org.sonar.ce.task.projectanalysis.component.ReportComponent;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
+import org.sonar.ce.task.projectanalysis.component.ViewsComponent;
+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.component.BranchDao;
+import org.sonar.server.es.ProjectIndexer;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT;
+import static org.sonar.ce.task.projectanalysis.component.Component.Type.VIEW;
+
+public class IndexAnalysisStepIT extends BaseStepTest {
+
+  private static final String PROJECT_KEY = "PROJECT_KEY";
+  private static final String PROJECT_UUID = "PROJECT_UUID";
+
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+  @Rule
+  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
+
+  private final DbClient dbClient = mock(DbClient.class);
+
+  private final FileStatuses fileStatuses = mock(FileStatuses.class);
+
+  private final ProjectIndexer projectIndexer = mock(ProjectIndexer.class);
+
+  private final DbSession dbSession = mock(DbSession.class);
+
+  private final BranchDao branchDao = mock(BranchDao.class);
+
+  private final IndexAnalysisStep underTest = new IndexAnalysisStep(treeRootHolder, fileStatuses, dbClient, projectIndexer);
+
+  private TestComputationStepContext testComputationStepContext;
+
+  @Before
+  public void init() {
+    testComputationStepContext = new TestComputationStepContext();
+
+    when(dbClient.openSession(false)).thenReturn(dbSession);
+    when(dbClient.branchDao()).thenReturn(branchDao);
+  }
+
+  @Test
+  public void call_indexByProjectUuid_of_indexer_for_project() {
+    Component project = ReportComponent.builder(PROJECT, 1).setUuid(PROJECT_UUID).setKey(PROJECT_KEY).build();
+    treeRootHolder.setRoot(project);
+
+    underTest.execute(testComputationStepContext);
+
+    verify(projectIndexer).indexOnAnalysis(PROJECT_UUID, Set.of());
+  }
+
+  @Test
+  public void call_indexByProjectUuid_of_indexer_for_view() {
+    Component view = ViewsComponent.builder(VIEW, PROJECT_KEY).setUuid(PROJECT_UUID).build();
+    treeRootHolder.setRoot(view);
+
+    underTest.execute(testComputationStepContext);
+
+    verify(projectIndexer).indexOnAnalysis(PROJECT_UUID, Set.of());
+  }
+
+  @Test
+  public void execute_whenMarkAsUnchangedFlagActivated_shouldCallIndexOnAnalysisWithChangedComponents() {
+    Component project = ReportComponent.builder(PROJECT, 1).setUuid(PROJECT_UUID).setKey(PROJECT_KEY).build();
+    treeRootHolder.setRoot(project);
+    Set<String> anyUuids = Set.of("any-uuid");
+    when(fileStatuses.getFileUuidsMarkedAsUnchanged()).thenReturn(anyUuids);
+
+    underTest.execute(testComputationStepContext);
+
+    verify(projectIndexer).indexOnAnalysis(PROJECT_UUID, anyUuids);
+  }
+
+  @Test
+  public void execute_whenBranchIsNeedIssueSync_shouldReindexEverything() {
+    Component project = ReportComponent.builder(PROJECT, 1).setUuid(PROJECT_UUID).setKey(PROJECT_KEY).build();
+    treeRootHolder.setRoot(project);
+    when(branchDao.isBranchNeedIssueSync(dbSession, PROJECT_UUID)).thenReturn(true);
+
+    underTest.execute(testComputationStepContext);
+
+    verify(projectIndexer).indexOnAnalysis(PROJECT_UUID);
+  }
+
+  @Override
+  protected ComputationStep step() {
+    return underTest;
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/LoadFileHashesAndStatusStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/LoadFileHashesAndStatusStepIT.java
new file mode 100644 (file)
index 0000000..ea7043b
--- /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.ce.task.projectanalysis.step;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.FileStatusesImpl;
+import org.sonar.ce.task.projectanalysis.component.PreviousSourceHashRepositoryImpl;
+import org.sonar.ce.task.projectanalysis.component.ReportComponent;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.source.FileHashesDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class LoadFileHashesAndStatusStepIT {
+  @Rule
+  public DbTester db = DbTester.create();
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+  public PreviousSourceHashRepositoryImpl previousFileHashesRepository = new PreviousSourceHashRepositoryImpl();
+  public FileStatusesImpl fileStatuses = mock(FileStatusesImpl.class);
+  public AnalysisMetadataHolder analysisMetadataHolder = mock(AnalysisMetadataHolder.class);
+
+  private final LoadFileHashesAndStatusStep underTest = new LoadFileHashesAndStatusStep(db.getDbClient(), previousFileHashesRepository, fileStatuses,
+    db.getDbClient().fileSourceDao(), treeRootHolder);
+
+  @Before
+  public void before() {
+    when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false);
+  }
+
+  @Test
+  public void initialized_file_statuses() {
+    Component project = ReportComponent.builder(Component.Type.PROJECT, 1, "project").build();
+    treeRootHolder.setRoot(project);
+    underTest.execute(new TestComputationStepContext());
+    verify(fileStatuses).initialize();
+  }
+
+  @Test
+  public void loads_file_hashes_for_project_branch() {
+    ComponentDto project1 = db.components().insertPublicProject();
+    ComponentDto project2 = db.components().insertPublicProject();
+
+    ComponentDto dbFile1 = db.components().insertComponent(ComponentTesting.newFileDto(project1));
+    ComponentDto dbFile2 = db.components().insertComponent(ComponentTesting.newFileDto(project1));
+
+    insertFileSources(dbFile1, dbFile2);
+
+    Component reportFile1 = ReportComponent.builder(Component.Type.FILE, 2, dbFile1.getKey()).setUuid(dbFile1.uuid()).build();
+    Component reportFile2 = ReportComponent.builder(Component.Type.FILE, 3, dbFile2.getKey()).setUuid(dbFile2.uuid()).build();
+    Component reportFile3 = ReportComponent.builder(Component.Type.FILE, 4, dbFile2.getKey()).build();
+
+    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1, project1.getKey()).setUuid(project1.uuid())
+      .addChildren(reportFile1, reportFile2, reportFile3).build());
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(previousFileHashesRepository.getMap()).hasSize(2);
+    assertThat(previousFileHashesRepository.getDbFile(reportFile1).get())
+      .extracting(FileHashesDto::getSrcHash, FileHashesDto::getRevision, FileHashesDto::getDataHash)
+      .containsOnly("srcHash" + dbFile1.getKey(), "revision" + dbFile1.getKey(), "dataHash" + dbFile1.getKey());
+    assertThat(previousFileHashesRepository.getDbFile(reportFile2).get())
+      .extracting(FileHashesDto::getSrcHash, FileHashesDto::getRevision, FileHashesDto::getDataHash)
+      .containsOnly("srcHash" + dbFile2.getKey(), "revision" + dbFile2.getKey(), "dataHash" + dbFile2.getKey());
+    assertThat(previousFileHashesRepository.getDbFile(reportFile3)).isEmpty();
+  }
+
+  @Test
+  public void loads_high_number_of_files() {
+    ComponentDto project1 = db.components().insertPublicProject();
+    List<Component> files = new ArrayList<>(2000);
+
+    for (int i = 0; i < 2000; i++) {
+      ComponentDto dbFile = db.components().insertComponent(ComponentTesting.newFileDto(project1));
+      insertFileSources(dbFile);
+      files.add(ReportComponent.builder(Component.Type.FILE, 2, dbFile.getKey()).setUuid(dbFile.uuid()).build());
+    }
+
+    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1, project1.getKey()).setUuid(project1.uuid())
+      .addChildren(files.toArray(Component[]::new)).build());
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(previousFileHashesRepository.getMap()).hasSize(2000);
+    for (Component file : files) {
+      assertThat(previousFileHashesRepository.getDbFile(file)).isPresent();
+    }
+  }
+
+  private void insertFileSources(ComponentDto... files) {
+    for (ComponentDto file : files) {
+      db.fileSources().insertFileSource(file, f -> f
+        .setSrcHash("srcHash" + file.getKey())
+        .setRevision("revision" + file.getKey())
+        .setDataHash("dataHash" + file.getKey()));
+    }
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStepIT.java
new file mode 100644 (file)
index 0000000..7a34a18
--- /dev/null
@@ -0,0 +1,561 @@
+/*
+ * 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.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.Random;
+import java.util.stream.Stream;
+import javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.sonar.api.utils.MessageException;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.log.LogAndArguments;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.ce.task.log.CeTaskMessages;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
+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.period.NewCodePeriodResolver;
+import org.sonar.ce.task.projectanalysis.period.Period;
+import org.sonar.ce.task.projectanalysis.period.PeriodHolderImpl;
+import org.sonar.ce.task.step.ComputationStep;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.core.util.SequenceUuidFactory;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.event.EventTesting;
+import org.sonar.db.newcodeperiod.NewCodePeriodDao;
+import org.sonar.db.newcodeperiod.NewCodePeriodType;
+import org.sonar.server.project.Project;
+
+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.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.verifyNoInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.db.component.SnapshotDto.STATUS_PROCESSED;
+import static org.sonar.db.component.SnapshotDto.STATUS_UNPROCESSED;
+import static org.sonar.db.event.EventDto.CATEGORY_VERSION;
+import static org.sonar.db.event.EventTesting.newEvent;
+
+@RunWith(DataProviderRunner.class)
+public class LoadPeriodsStepIT extends BaseStepTest {
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  private final AnalysisMetadataHolder analysisMetadataHolder = mock(AnalysisMetadataHolder.class);
+  private final PeriodHolderImpl periodsHolder = new PeriodHolderImpl();
+  private final System2 system2Mock = mock(System2.class);
+  private final NewCodePeriodDao dao = new NewCodePeriodDao(system2Mock, new SequenceUuidFactory());
+  private final NewCodePeriodResolver newCodePeriodResolver = new NewCodePeriodResolver(dbTester.getDbClient(), analysisMetadataHolder);
+  private final ZonedDateTime analysisDate = ZonedDateTime.of(2019, 3, 20, 5, 30, 40, 0, ZoneId.systemDefault());
+  private final CeTaskMessages ceTaskMessages = mock(CeTaskMessages.class);
+  private final LoadPeriodsStep underTest = new LoadPeriodsStep(analysisMetadataHolder, dao, treeRootHolder, periodsHolder, dbTester.getDbClient(), newCodePeriodResolver,
+    ceTaskMessages, system2Mock);
+
+  private ComponentDto project;
+
+  @Override
+  protected ComputationStep step() {
+    return underTest;
+  }
+
+  @Before
+  public void setUp() {
+    project = dbTester.components().insertPublicProject();
+
+    when(analysisMetadataHolder.isBranch()).thenReturn(true);
+    when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false);
+    when(analysisMetadataHolder.getAnalysisDate()).thenReturn(analysisDate.toInstant().toEpochMilli());
+  }
+
+  @Test
+  public void no_period_on_first_analysis() {
+    setupRoot(project);
+
+    when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(true);
+    underTest.execute(new TestComputationStepContext());
+
+    verify(analysisMetadataHolder).isFirstAnalysis();
+    verify(analysisMetadataHolder).isBranch();
+    verify(analysisMetadataHolder).getProject();
+    verify(analysisMetadataHolder).getNewCodeReferenceBranch();
+    assertThat(periodsHolder.hasPeriod()).isFalse();
+    verifyNoMoreInteractions(analysisMetadataHolder);
+  }
+
+  @Test
+  public void no_period_date_if_not_branch() {
+    when(analysisMetadataHolder.isBranch()).thenReturn(false);
+    underTest.execute(new TestComputationStepContext());
+
+    verify(analysisMetadataHolder).isBranch();
+    assertThat(periodsHolder.hasPeriod()).isFalse();
+    verifyNoMoreInteractions(analysisMetadataHolder);
+  }
+
+  @Test
+  public void load_default_if_nothing_defined() {
+    setupRoot(project);
+
+    SnapshotDto analysis = dbTester.components().insertSnapshot(project,
+      snapshot -> snapshot.setCreatedAt(milisSinceEpoch(2019, 3, 15, 0)));
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertPeriod(NewCodePeriodType.PREVIOUS_VERSION, null, analysis.getCreatedAt());
+    verifyDebugLogs("Resolving first analysis as new code period as there is only one existing version");
+  }
+
+  @Test
+  public void load_number_of_days_global() {
+    setGlobalPeriod(NewCodePeriodType.NUMBER_OF_DAYS, "10");
+
+    testNumberOfDays(project);
+  }
+
+  @Test
+  public void load_number_of_days_on_project() {
+    setGlobalPeriod(NewCodePeriodType.PREVIOUS_VERSION, null);
+    setProjectPeriod(project.uuid(), NewCodePeriodType.NUMBER_OF_DAYS, "10");
+
+    testNumberOfDays(project);
+  }
+
+  @Test
+  public void load_number_of_days_on_branch() {
+    ComponentDto branch = dbTester.components().insertProjectBranch(project);
+
+    setGlobalPeriod(NewCodePeriodType.PREVIOUS_VERSION, null);
+    setProjectPeriod(project.uuid(), NewCodePeriodType.PREVIOUS_VERSION, null);
+    setBranchPeriod(project.uuid(), branch.uuid(), NewCodePeriodType.NUMBER_OF_DAYS, "10");
+
+    testNumberOfDays(branch);
+    verifyNoInteractions(ceTaskMessages);
+  }
+
+  @Test
+  public void load_reference_branch() {
+    ComponentDto branch = dbTester.components().insertProjectBranch(project);
+    setupRoot(branch);
+
+    setProjectPeriod(project.uuid(), NewCodePeriodType.REFERENCE_BRANCH, "master");
+
+    underTest.execute(new TestComputationStepContext());
+    assertPeriod(NewCodePeriodType.REFERENCE_BRANCH, "master", null);
+    verifyNoInteractions(ceTaskMessages);
+  }
+
+  @Test
+  public void scanner_overrides_branch_new_code_reference_branch() {
+    ComponentDto branch = dbTester.components().insertProjectBranch(project);
+    setupRoot(branch);
+
+    setBranchPeriod(project.uuid(), branch.uuid(), NewCodePeriodType.REFERENCE_BRANCH, "master");
+
+    String newCodeReferenceBranch = "newCodeReferenceBranch";
+    when(analysisMetadataHolder.getNewCodeReferenceBranch()).thenReturn(Optional.of(newCodeReferenceBranch));
+
+    underTest.execute(new TestComputationStepContext());
+    assertPeriod(NewCodePeriodType.REFERENCE_BRANCH, newCodeReferenceBranch, null);
+    verify(ceTaskMessages).add(any(CeTaskMessages.Message.class));
+  }
+
+  @Test
+  public void scanner_defines_new_code_reference_branch() {
+    ComponentDto branch = dbTester.components().insertProjectBranch(project);
+    setupRoot(branch);
+
+    String newCodeReferenceBranch = "newCodeReferenceBranch";
+    when(analysisMetadataHolder.getNewCodeReferenceBranch()).thenReturn(Optional.of(newCodeReferenceBranch));
+
+    underTest.execute(new TestComputationStepContext());
+    assertPeriod(NewCodePeriodType.REFERENCE_BRANCH, newCodeReferenceBranch, null);
+    verifyNoInteractions(ceTaskMessages);
+  }
+
+  @Test
+  public void scanner_overrides_project_new_code_reference_branch() {
+    ComponentDto branch = dbTester.components().insertProjectBranch(project);
+    setupRoot(branch);
+
+    setProjectPeriod(project.uuid(), NewCodePeriodType.REFERENCE_BRANCH, "master");
+
+    String newCodeReferenceBranch = "newCodeReferenceBranch";
+    when(analysisMetadataHolder.getNewCodeReferenceBranch()).thenReturn(Optional.of(newCodeReferenceBranch));
+
+    underTest.execute(new TestComputationStepContext());
+    assertPeriod(NewCodePeriodType.REFERENCE_BRANCH, newCodeReferenceBranch, null);
+    verifyNoInteractions(ceTaskMessages);
+  }
+
+  @Test
+  public void load_reference_branch_without_fork_date_in_report() {
+    ComponentDto branch = dbTester.components().insertProjectBranch(project);
+    setupRoot(branch);
+
+    setProjectPeriod(project.uuid(), NewCodePeriodType.REFERENCE_BRANCH, "master");
+
+    underTest.execute(new TestComputationStepContext());
+    assertPeriod(NewCodePeriodType.REFERENCE_BRANCH, "master", null);
+    verifyNoInteractions(ceTaskMessages);
+  }
+
+  private void testNumberOfDays(ComponentDto projectOrBranch) {
+    setupRoot(projectOrBranch);
+
+    SnapshotDto analysis = dbTester.components().insertSnapshot(projectOrBranch,
+      snapshot -> snapshot.setCreatedAt(milisSinceEpoch(2019, 3, 15, 0)));
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertPeriod(NewCodePeriodType.NUMBER_OF_DAYS, "10", analysis.getCreatedAt());
+    verifyDebugLogs("Resolving new code period by 10 days: 2019-03-10");
+  }
+
+  @Test
+  public void load_specific_analysis() {
+    ComponentDto branch = dbTester.components().insertProjectBranch(project);
+    SnapshotDto selectedAnalysis = dbTester.components().insertSnapshot(branch);
+    SnapshotDto aVersionAnalysis = dbTester.components().insertSnapshot(branch, snapshot -> snapshot.setCreatedAt(milisSinceEpoch(2019, 3, 12, 0)).setLast(false));
+    dbTester.events().insertEvent(EventTesting.newEvent(aVersionAnalysis).setName("a_version").setCategory(CATEGORY_VERSION));
+    dbTester.components().insertSnapshot(branch, snapshot -> snapshot.setCreatedAt(milisSinceEpoch(2019, 3, 15, 0)).setLast(true));
+
+    setBranchPeriod(project.uuid(), branch.uuid(), NewCodePeriodType.SPECIFIC_ANALYSIS, selectedAnalysis.getUuid());
+    setupRoot(branch);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertPeriod(NewCodePeriodType.SPECIFIC_ANALYSIS, selectedAnalysis.getUuid(), selectedAnalysis.getCreatedAt());
+    verifyDebugLogs("Resolving new code period with a specific analysis");
+    verifyNoInteractions(ceTaskMessages);
+  }
+
+  @Test
+  public void throw_ISE_if_no_analysis_found_for_number_of_days() {
+    setProjectPeriod(project.uuid(), NewCodePeriodType.NUMBER_OF_DAYS, "10");
+
+    setupRoot(project);
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessageContaining("Attempting to resolve period while no analysis exist");
+  }
+
+  @Test
+  public void throw_ISE_if_no_analysis_found_with_default() {
+    setupRoot(project);
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessageContaining("Attempting to resolve period while no analysis exist");
+  }
+
+  @Test
+  public void ignore_unprocessed_snapshots() {
+    SnapshotDto analysis1 = dbTester.components()
+      .insertSnapshot(project, snapshot -> snapshot.setStatus(STATUS_UNPROCESSED).setCreatedAt(milisSinceEpoch(2019, 3, 12, 0)).setLast(false));
+    SnapshotDto analysis2 = dbTester.components().insertSnapshot(project,
+      snapshot -> snapshot.setStatus(STATUS_PROCESSED).setProjectVersion("not provided").setCreatedAt(milisSinceEpoch(2019, 3, 15, 0)).setLast(false));
+    dbTester.events().insertEvent(newEvent(analysis1).setName("not provided").setCategory(CATEGORY_VERSION).setDate(analysis1.getCreatedAt()));
+    dbTester.events().insertEvent(newEvent(analysis2).setName("not provided").setCategory(CATEGORY_VERSION).setDate(analysis2.getCreatedAt()));
+    setupRoot(project);
+    setProjectPeriod(project.uuid(), NewCodePeriodType.NUMBER_OF_DAYS, "10");
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertPeriod(NewCodePeriodType.NUMBER_OF_DAYS, "10", analysis2.getCreatedAt());
+    verifyDebugLogs("Resolving new code period by 10 days: 2019-03-10");
+  }
+
+  @Test
+  public void throw_ISE_when_specific_analysis_is_set_but_does_not_exist_in_DB() {
+    ComponentDto project = dbTester.components().insertPublicProject();
+    setProjectPeriod(project.uuid(), NewCodePeriodType.SPECIFIC_ANALYSIS, "nonexistent");
+    setupRoot(project);
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Analysis 'nonexistent' of project '" + project.uuid() + "' defined as the baseline does not exist");
+  }
+
+  @Test
+  public void throw_ISE_when_specific_analysis_is_set_but_does_not_belong_to_current_project() {
+    ComponentDto otherProject = dbTester.components().insertPublicProject();
+    SnapshotDto otherProjectAnalysis = dbTester.components().insertSnapshot(otherProject);
+    setBranchPeriod(project.uuid(), project.uuid(), NewCodePeriodType.SPECIFIC_ANALYSIS, otherProjectAnalysis.getUuid());
+    setupRoot(project);
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Analysis '" + otherProjectAnalysis.getUuid() + "' of project '" + project.uuid() + "' defined as the baseline does not exist");
+  }
+
+  @Test
+  public void throw_ISE_when_specific_analysis_is_set_but_does_not_belong_to_current_branch() {
+    ComponentDto otherBranch = dbTester.components().insertProjectBranch(project);
+    SnapshotDto otherBranchAnalysis = dbTester.components().insertSnapshot(otherBranch);
+    setBranchPeriod(project.uuid(), project.uuid(), NewCodePeriodType.SPECIFIC_ANALYSIS, otherBranchAnalysis.getUuid());
+    setupRoot(project);
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Analysis '" + otherBranchAnalysis.getUuid() + "' of project '" + project.uuid() + "' defined as the baseline does not exist");
+  }
+
+  @Test
+  public void load_previous_version() {
+    SnapshotDto analysis1 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226379600000L).setProjectVersion("0.9").setLast(false)); // 2008-11-11
+    SnapshotDto analysis2 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226494680000L).setProjectVersion("1.0").setLast(false)); // 2008-11-12
+    SnapshotDto analysis3 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227157200000L).setProjectVersion("1.1").setLast(false)); // 2008-11-20
+    SnapshotDto analysis4 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227358680000L).setProjectVersion("1.1").setLast(false)); // 2008-11-22
+    SnapshotDto analysis5 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227934800000L).setProjectVersion("1.1").setLast(true)); // 2008-11-29
+    dbTester.events().insertEvent(newEvent(analysis1).setName("0.9").setCategory(CATEGORY_VERSION).setDate(analysis1.getCreatedAt()));
+    dbTester.events().insertEvent(newEvent(analysis2).setName("1.0").setCategory(CATEGORY_VERSION).setDate(analysis2.getCreatedAt()));
+    dbTester.events().insertEvent(newEvent(analysis5).setName("1.1").setCategory(CATEGORY_VERSION).setDate(analysis5.getCreatedAt()));
+    setupRoot(project, "1.1");
+    setProjectPeriod(project.uuid(), NewCodePeriodType.PREVIOUS_VERSION, null);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertPeriod(NewCodePeriodType.PREVIOUS_VERSION, "1.0", analysis2.getCreatedAt());
+
+    verifyDebugLogs("Resolving new code period by previous version: 1.0");
+  }
+
+  @Test
+  public void load_previous_version_when_version_is_changing() {
+    SnapshotDto analysis1 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226379600000L).setProjectVersion("0.9").setLast(false)); // 2008-11-11
+    SnapshotDto analysis2 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226494680000L).setProjectVersion("0.9").setLast(true)); // 2008-11-12
+
+    dbTester.events().insertEvent(newEvent(analysis2).setName("0.9").setCategory(CATEGORY_VERSION).setDate(analysis2.getCreatedAt()));
+    setupRoot(project, "1.0");
+    setProjectPeriod(project.uuid(), NewCodePeriodType.PREVIOUS_VERSION, null);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertPeriod(NewCodePeriodType.PREVIOUS_VERSION, "0.9", analysis2.getCreatedAt());
+
+    verifyDebugLogs("Resolving new code period by previous version: 0.9");
+  }
+
+  @Test
+  @UseDataProvider("zeroOrLess")
+  public void fail_with_MessageException_if_period_is_0_or_less(int zeroOrLess) {
+    setupRoot(project);
+    setProjectPeriod(project.uuid(), NewCodePeriodType.NUMBER_OF_DAYS, String.valueOf(zeroOrLess));
+
+    verifyFailWithInvalidValueMessageException(String.valueOf(zeroOrLess),
+      "Invalid code period '" + zeroOrLess + "': number of days is <= 0");
+  }
+
+  @Test
+  public void load_previous_version_with_previous_version_deleted() {
+    SnapshotDto analysis1 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226379600000L).setProjectVersion("0.9").setLast(false)); // 2008-11-11
+    SnapshotDto analysis2 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226494680000L).setProjectVersion("1.0").setLast(false)); // 2008-11-12
+    SnapshotDto analysis3 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227157200000L).setProjectVersion("1.1").setLast(true)); // 2008-11-20
+    dbTester.events().insertEvent(newEvent(analysis1).setName("0.9").setCategory(CATEGORY_VERSION));
+    // The "1.0" version was deleted from the history
+    setupRoot(project, "1.1");
+
+    underTest.execute(new TestComputationStepContext());
+
+    // Analysis form 2008-11-11
+    assertPeriod(NewCodePeriodType.PREVIOUS_VERSION, "0.9", analysis1.getCreatedAt());
+  }
+
+  @Test
+  public void load_previous_version_with_first_analysis_when_no_previous_version_found() {
+    SnapshotDto analysis1 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226379600000L).setProjectVersion("1.1").setLast(false)); // 2008-11-11
+    SnapshotDto analysis2 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227934800000L).setProjectVersion("1.1").setLast(true)); // 2008-11-29
+    dbTester.events().insertEvent(newEvent(analysis2).setName("1.1").setCategory(CATEGORY_VERSION));
+    setupRoot(project, "1.1");
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertPeriod(NewCodePeriodType.PREVIOUS_VERSION, null, analysis1.getCreatedAt());
+
+    verifyDebugLogs("Resolving first analysis as new code period as there is only one existing version");
+  }
+
+  @Test
+  public void load_previous_version_with_first_analysis_when_previous_snapshot_is_the_last_one() {
+    SnapshotDto analysis = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226379600000L).setProjectVersion("0.9").setLast(true)); // 2008-11-11
+    dbTester.events().insertEvent(newEvent(analysis).setName("0.9").setCategory(CATEGORY_VERSION));
+    setupRoot(project, "1.1");
+
+    dbTester.newCodePeriods().insert(NewCodePeriodType.PREVIOUS_VERSION, null);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertPeriod(NewCodePeriodType.PREVIOUS_VERSION, "0.9", analysis.getCreatedAt());
+    verifyDebugLogs("Resolving new code period by previous version: 0.9");
+  }
+
+  @Test
+  @UseDataProvider("anyValidLeakPeriodSettingValue")
+  public void leak_period_setting_is_ignored_for_PR(NewCodePeriodType type, @Nullable String value) {
+    when(analysisMetadataHolder.isBranch()).thenReturn(false);
+
+    dbTester.newCodePeriods().insert(type, value);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(periodsHolder.hasPeriod()).isFalse();
+  }
+
+  private void verifyFailWithInvalidValueMessageException(String propertyValue, String debugLog, String... otherDebugLogs) {
+    try {
+      underTest.execute(new TestComputationStepContext());
+      fail("a Message Exception should have been thrown");
+    } catch (MessageException e) {
+      verifyInvalidValueMessage(e, propertyValue);
+      verifyDebugLogs(debugLog, otherDebugLogs);
+    }
+  }
+
+  @DataProvider
+  public static Object[][] zeroOrLess() {
+    return new Object[][] {
+      {0},
+      {-1 - new Random().nextInt(30)}
+    };
+  }
+
+  @DataProvider
+  public static Object[][] stringConsideredAsVersions() {
+    return new Object[][] {
+      {randomAlphabetic(5)},
+      {"1,3"},
+      {"1.3"},
+      {"0 1"},
+      {"1-SNAPSHOT"},
+      {"01-12-2018"}, // unsupported date format
+    };
+  }
+
+  @DataProvider
+  public static Object[][] projectVersionNullOrNot() {
+    return new Object[][] {
+      {null},
+      {randomAlphabetic(15)},
+    };
+  }
+
+  @DataProvider
+  public static Object[][] anyValidLeakPeriodSettingValue() {
+    return new Object[][] {
+      // days
+      {NewCodePeriodType.NUMBER_OF_DAYS, "100"},
+      // previous_version
+      {NewCodePeriodType.PREVIOUS_VERSION, null}
+    };
+  }
+
+  private List<SnapshotDto> createSnapshots(ComponentDto project) {
+    ArrayList<SnapshotDto> list = new ArrayList<>();
+    list.add(dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226379600000L).setLast(false))); // 2008-11-11
+    list.add(dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226494680000L).setLast(false))); // 2008-11-12
+    list.add(dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227157200000L).setLast(false))); // 2008-11-20
+    list.add(dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227358680000L).setLast(false))); // 2008-11-22
+    list.add(dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227934800000L).setLast(true))); // 2008-11-29
+    return list;
+  }
+
+  private long milisSinceEpoch(int year, int month, int day, int hour) {
+    return ZonedDateTime.of(year, month, day, hour, 0, 0, 0, ZoneId.systemDefault())
+      .toInstant().toEpochMilli();
+  }
+
+  private void setProjectPeriod(String projectUuid, NewCodePeriodType type, @Nullable String value) {
+    dbTester.newCodePeriods().insert(projectUuid, type, value);
+  }
+
+  private void setBranchPeriod(String projectUuid, String branchUuid, NewCodePeriodType type, @Nullable String value) {
+    dbTester.newCodePeriods().insert(projectUuid, branchUuid, type, value);
+  }
+
+  private void setGlobalPeriod(NewCodePeriodType type, @Nullable String value) {
+    dbTester.newCodePeriods().insert(type, value);
+  }
+
+  private void assertPeriod(NewCodePeriodType type, @Nullable String value, @Nullable Long date) {
+    Period period = periodsHolder.getPeriod();
+    assertThat(period).isNotNull();
+    assertThat(period.getMode()).isEqualTo(type.name());
+    assertThat(period.getModeParameter()).isEqualTo(value);
+    assertThat(period.getDate()).isEqualTo(date);
+  }
+
+  private void verifyDebugLogs(String log, String... otherLogs) {
+    assertThat(logTester.getLogs()).hasSize(1 + otherLogs.length);
+    assertThat(logTester.getLogs(LoggerLevel.DEBUG))
+      .extracting(LogAndArguments::getFormattedMsg)
+      .containsOnly(Stream.concat(Stream.of(log), Arrays.stream(otherLogs)).toArray(String[]::new));
+  }
+
+  private void setupRoot(ComponentDto project) {
+    setupRoot(project, randomAlphanumeric(3));
+  }
+
+  private void setupRoot(ComponentDto projectDto, String version) {
+    treeRootHolder.setRoot(ReportComponent
+      .builder(Component.Type.PROJECT, 1)
+      .setUuid(projectDto.uuid())
+      .setKey(projectDto.getKey())
+      .setProjectVersion(version)
+      .build());
+
+    Project project = mock(Project.class);
+    when(project.getUuid()).thenReturn(projectDto.getMainBranchProjectUuid() != null ? projectDto.getMainBranchProjectUuid() : projectDto.uuid());
+    when(analysisMetadataHolder.getProject()).thenReturn(project);
+  }
+
+  private static void verifyInvalidValueMessage(MessageException e, String propertyValue) {
+    assertThat(e).hasMessage("Invalid new code period. '" + propertyValue
+      + "' is not one of: integer > 0, date before current analysis j, \"previous_version\", or version string that exists in the project' \n" +
+      "Please contact a project administrator to correct this setting");
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStepIT.java
new file mode 100644 (file)
index 0000000..1759169
--- /dev/null
@@ -0,0 +1,352 @@
+/*
+ * 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.DataProviderRunner;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import org.assertj.core.api.Assertions;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.sonar.api.Plugin;
+import org.sonar.api.utils.MessageException;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.CeTask;
+import org.sonar.ce.task.projectanalysis.analysis.MutableAnalysisMetadataHolderRule;
+import org.sonar.ce.task.projectanalysis.analysis.ScannerPlugin;
+import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
+import org.sonar.ce.task.projectanalysis.component.BranchLoader;
+import org.sonar.ce.task.step.ComputationStep;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.platform.PluginRepository;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.scanner.protocol.output.ScannerReport;
+import org.sonar.server.project.DefaultBranchNameResolver;
+import org.sonar.server.project.Project;
+
+import static java.util.Arrays.stream;
+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;
+
+@RunWith(DataProviderRunner.class)
+public class LoadReportAnalysisMetadataHolderStepIT {
+
+  private static final String PROJECT_KEY = "project_key";
+  private static final long ANALYSIS_DATE = 123456789L;
+
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+  @Rule
+  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
+  @Rule
+  public MutableAnalysisMetadataHolderRule analysisMetadataHolder = new MutableAnalysisMetadataHolderRule();
+
+  private final DbClient dbClient = db.getDbClient();
+  private final TestPluginRepository pluginRepository = new TestPluginRepository();
+  private ComponentDto project;
+  private ComputationStep underTest;
+
+  @Before
+  public void setUp() {
+    CeTask defaultOrgCeTask = createCeTask(PROJECT_KEY);
+    underTest = createStep(defaultOrgCeTask);
+    project = db.components().insertPublicProject(p -> p.setKey(PROJECT_KEY));
+  }
+
+  @Test
+  public void set_root_component_ref() {
+    reportReader.setMetadata(
+      newBatchReportBuilder()
+        .setRootComponentRef(1)
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(analysisMetadataHolder.getRootComponentRef()).isOne();
+  }
+
+  @Test
+  public void set_analysis_date() {
+    reportReader.setMetadata(
+      newBatchReportBuilder()
+        .setAnalysisDate(ANALYSIS_DATE)
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(analysisMetadataHolder.getAnalysisDate()).isEqualTo(ANALYSIS_DATE);
+  }
+
+  @Test
+  public void set_new_code_reference_branch() {
+    String newCodeReferenceBranch = "newCodeReferenceBranch";
+    reportReader.setMetadata(
+      newBatchReportBuilder()
+        .setNewCodeReferenceBranch(newCodeReferenceBranch)
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(analysisMetadataHolder.getNewCodeReferenceBranch()).hasValue(newCodeReferenceBranch);
+  }
+
+  @Test
+  public void set_project_from_dto() {
+    reportReader.setMetadata(
+      newBatchReportBuilder()
+        .setRootComponentRef(1)
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    Project project = analysisMetadataHolder.getProject();
+    assertThat(project.getUuid()).isEqualTo(this.project.uuid());
+    assertThat(project.getKey()).isEqualTo(this.project.getKey());
+    assertThat(project.getName()).isEqualTo(this.project.name());
+    assertThat(project.getDescription()).isEqualTo(this.project.description());
+  }
+
+  @Test
+  public void set_cross_project_duplication_to_true() {
+    reportReader.setMetadata(
+      newBatchReportBuilder()
+        .setCrossProjectDuplicationActivated(true)
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(analysisMetadataHolder.isCrossProjectDuplicationEnabled()).isTrue();
+  }
+
+  @Test
+  public void set_cross_project_duplication_to_false() {
+    reportReader.setMetadata(
+      newBatchReportBuilder()
+        .setCrossProjectDuplicationActivated(false)
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(analysisMetadataHolder.isCrossProjectDuplicationEnabled()).isFalse();
+  }
+
+  @Test
+  public void set_cross_project_duplication_to_false_when_nothing_in_the_report() {
+    reportReader.setMetadata(
+      newBatchReportBuilder()
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(analysisMetadataHolder.isCrossProjectDuplicationEnabled()).isFalse();
+  }
+
+  @Test
+  public void execute_fails_with_ISE_if_component_is_null_in_CE_task() {
+    CeTask res = mock(CeTask.class);
+    when(res.getComponent()).thenReturn(Optional.empty());
+    reportReader.setMetadata(ScannerReport.Metadata.newBuilder().build());
+
+    ComputationStep underTest = createStep(res);
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("component missing on ce task");
+  }
+
+  @Test
+  public void execute_fails_with_MessageException_if_main_projectKey_is_null_in_CE_task() {
+    CeTask res = mock(CeTask.class);
+    Optional<CeTask.Component> component = Optional.of(new CeTask.Component("main_prj_uuid", null, null));
+    when(res.getComponent()).thenReturn(component);
+    when(res.getMainComponent()).thenReturn(component);
+    reportReader.setMetadata(ScannerReport.Metadata.newBuilder().build());
+
+    ComputationStep underTest = createStep(res);
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(MessageException.class)
+      .hasMessage("Compute Engine task main component key is null. Project with UUID main_prj_uuid must have been deleted since report was uploaded. Can not proceed.");
+  }
+
+  @Test
+  public void execute_fails_with_MessageException_if_projectKey_is_null_in_CE_task() {
+    CeTask res = mock(CeTask.class);
+    Optional<CeTask.Component> component = Optional.of(new CeTask.Component("prj_uuid", null, null));
+    when(res.getComponent()).thenReturn(component);
+    when(res.getMainComponent()).thenReturn(Optional.of(new CeTask.Component("main_prj_uuid", "main_prj_key", null)));
+    reportReader.setMetadata(ScannerReport.Metadata.newBuilder().build());
+
+    ComputationStep underTest = createStep(res);
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(MessageException.class)
+      .hasMessage("Compute Engine task component key is null. Project with UUID prj_uuid must have been deleted since report was uploaded. Can not proceed.");
+  }
+
+  @Test
+  public void execute_fails_with_MessageException_when_projectKey_in_report_is_different_from_componentKey_in_CE_task() {
+    ComponentDto otherProject = db.components().insertPublicProject();
+    reportReader.setMetadata(
+      ScannerReport.Metadata.newBuilder()
+        .setProjectKey(otherProject.getKey())
+        .build());
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(MessageException.class)
+      .hasMessage("ProjectKey in report (" + otherProject.getKey() + ") is not consistent with projectKey under which the report has been submitted (" + PROJECT_KEY + ")");
+
+  }
+
+  @Test
+  public void execute_sets_branch_even_if_MessageException_is_thrown_because_projectKey_in_report_is_different_from_componentKey_in_CE_task() {
+    ComponentDto otherProject = db.components().insertPublicProject();
+    reportReader.setMetadata(
+      ScannerReport.Metadata.newBuilder()
+        .setProjectKey(otherProject.getKey())
+        .build());
+
+    try {
+      underTest.execute(new TestComputationStepContext());
+    } catch (MessageException e) {
+      assertThat(analysisMetadataHolder.getBranch()).isNotNull();
+    }
+  }
+
+  @Test
+  public void execute_sets_analysis_date_even_if_MessageException_is_thrown_because_projectKey_is_different_from_componentKey_in_CE_task() {
+    ComponentDto otherProject = db.components().insertPublicProject();
+    reportReader.setMetadata(
+      ScannerReport.Metadata.newBuilder()
+        .setProjectKey(otherProject.getKey())
+        .setAnalysisDate(ANALYSIS_DATE)
+        .build());
+
+    try {
+      underTest.execute(new TestComputationStepContext());
+    } catch (MessageException e) {
+      assertThat(analysisMetadataHolder.getAnalysisDate()).isEqualTo(ANALYSIS_DATE);
+    }
+  }
+
+  @Test
+  public void execute_does_not_fail_when_report_has_a_quality_profile_that_does_not_exist_anymore() {
+    ComponentDto project = db.components().insertPublicProject();
+    ScannerReport.Metadata.Builder metadataBuilder = newBatchReportBuilder();
+    metadataBuilder
+      .setProjectKey(project.getKey());
+    metadataBuilder.putQprofilesPerLanguage("js", ScannerReport.Metadata.QProfile.newBuilder().setKey("p1").setName("Sonar way").setLanguage("js").build());
+    reportReader.setMetadata(metadataBuilder.build());
+
+    ComputationStep underTest = createStep(createCeTask(project.getKey()));
+
+    underTest.execute(new TestComputationStepContext());
+  }
+
+  @Test
+  public void execute_read_plugins_from_report() {
+    // the installed plugins
+    pluginRepository.add(
+      new PluginInfo("java"),
+      new PluginInfo("customjava").setBasePlugin("java"),
+      new PluginInfo("php"));
+
+    // the plugins sent by scanner
+    ScannerReport.Metadata.Builder metadataBuilder = newBatchReportBuilder();
+    metadataBuilder.putPluginsByKey("java", ScannerReport.Metadata.Plugin.newBuilder().setKey("java").setUpdatedAt(10L).build());
+    metadataBuilder.putPluginsByKey("php", ScannerReport.Metadata.Plugin.newBuilder().setKey("php").setUpdatedAt(20L).build());
+    metadataBuilder.putPluginsByKey("customjava", ScannerReport.Metadata.Plugin.newBuilder().setKey("customjava").setUpdatedAt(30L).build());
+    metadataBuilder.putPluginsByKey("uninstalled", ScannerReport.Metadata.Plugin.newBuilder().setKey("uninstalled").setUpdatedAt(40L).build());
+    reportReader.setMetadata(metadataBuilder.build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    Assertions.assertThat(analysisMetadataHolder.getScannerPluginsByKey().values()).extracting(ScannerPlugin::getKey, ScannerPlugin::getBasePluginKey, ScannerPlugin::getUpdatedAt)
+      .containsExactlyInAnyOrder(
+        tuple("java", null, 10L),
+        tuple("php", null, 20L),
+        tuple("customjava", "java", 30L),
+        tuple("uninstalled", null, 40L));
+  }
+
+  private LoadReportAnalysisMetadataHolderStep createStep(CeTask ceTask) {
+    return new LoadReportAnalysisMetadataHolderStep(ceTask, reportReader, analysisMetadataHolder,
+      dbClient, new BranchLoader(analysisMetadataHolder, mock(DefaultBranchNameResolver.class)), pluginRepository);
+  }
+
+  private static ScannerReport.Metadata.Builder newBatchReportBuilder() {
+    return ScannerReport.Metadata.newBuilder()
+      .setProjectKey(PROJECT_KEY);
+  }
+
+  private CeTask createCeTask(String projectKey) {
+    CeTask res = mock(CeTask.class);
+    Optional<CeTask.Component> component = Optional.of(new CeTask.Component(projectKey + "_uuid", projectKey, projectKey + "_name"));
+    when(res.getComponent()).thenReturn(component);
+    when(res.getMainComponent()).thenReturn(component);
+    return res;
+  }
+
+  private static class TestPluginRepository implements PluginRepository {
+    private final Map<String, PluginInfo> pluginsMap = new HashMap<>();
+
+    void add(PluginInfo... plugins) {
+      stream(plugins).forEach(p -> pluginsMap.put(p.getKey(), p));
+    }
+
+    @Override
+    public Collection<PluginInfo> getPluginInfos() {
+      return pluginsMap.values();
+    }
+
+    @Override
+    public PluginInfo getPluginInfo(String key) {
+      if (!pluginsMap.containsKey(key)) {
+        throw new IllegalArgumentException();
+      }
+      return pluginsMap.get(key);
+    }
+
+    @Override
+    public Plugin getPluginInstance(String key) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Collection<Plugin> getPluginInstances() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean hasPlugin(String key) {
+      return pluginsMap.containsKey(key);
+    }
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistAdHocRulesStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistAdHocRulesStepIT.java
new file mode 100644 (file)
index 0000000..85319db
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * 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.Optional;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.projectanalysis.issue.AdHocRuleCreator;
+import org.sonar.ce.task.projectanalysis.issue.NewAdHocRule;
+import org.sonar.ce.task.projectanalysis.issue.RuleRepositoryImpl;
+import org.sonar.ce.task.step.ComputationStep;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.core.util.SequenceUuidFactory;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.rule.RuleDao;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.scanner.protocol.output.ScannerReport;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.rule.index.RuleIndexDefinition;
+import org.sonar.server.rule.index.RuleIndexer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class PersistAdHocRulesStepIT extends BaseStepTest {
+
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+
+  private DbClient dbClient = db.getDbClient();
+
+  private ComputationStep underTest;
+  private RuleRepositoryImpl ruleRepository;
+
+  @org.junit.Rule
+  public EsTester es = EsTester.create();
+
+  private RuleIndexer indexer = new RuleIndexer(es.client(), dbClient);
+  private AdHocRuleCreator adHocRuleCreator = new AdHocRuleCreator(dbClient, System2.INSTANCE, indexer, new SequenceUuidFactory());
+
+  @Override
+  protected ComputationStep step() {
+    return underTest;
+  }
+
+  @Before
+  public void setup() {
+    ruleRepository = new RuleRepositoryImpl(adHocRuleCreator, dbClient);
+    underTest = new PersistAdHocRulesStep(dbClient, ruleRepository);
+  }
+
+  @Test
+  public void persist_and_index_new_ad_hoc_rules() {
+    RuleKey ruleKey = RuleKey.of("external_eslint", "no-cond-assign");
+    ruleRepository.addOrUpdateAddHocRuleIfNeeded(ruleKey,
+      () -> new NewAdHocRule(ScannerReport.ExternalIssue.newBuilder().setEngineId("eslint").setRuleId("no-cond-assign").build()));
+
+    underTest.execute(new TestComputationStepContext());
+
+    RuleDao ruleDao = dbClient.ruleDao();
+    Optional<RuleDto> ruleDefinitionDtoOptional = ruleDao.selectByKey(dbClient.openSession(false), ruleKey);
+    assertThat(ruleDefinitionDtoOptional).isPresent();
+
+    RuleDto reloaded = ruleDefinitionDtoOptional.get();
+    assertThat(reloaded.getRuleKey()).isEqualTo("no-cond-assign");
+    assertThat(reloaded.getRepositoryKey()).isEqualTo("external_eslint");
+    assertThat(reloaded.isExternal()).isTrue();
+    assertThat(reloaded.isAdHoc()).isTrue();
+    assertThat(reloaded.getType()).isZero();
+    assertThat(reloaded.getSeverity()).isNull();
+    assertThat(reloaded.getName()).isEqualTo("eslint:no-cond-assign");
+
+    assertThat(es.countDocuments(RuleIndexDefinition.TYPE_RULE)).isOne();
+    assertThat(es.getDocuments(RuleIndexDefinition.TYPE_RULE).iterator().next().getId()).isEqualTo(reloaded.getUuid());
+  }
+
+  @Test
+  public void do_not_persist_existing_external_rules() {
+    RuleKey ruleKey = RuleKey.of("eslint", "no-cond-assign");
+    db.rules().insert(ruleKey, r -> r.setIsExternal(true));
+    ruleRepository.addOrUpdateAddHocRuleIfNeeded(ruleKey,
+      () -> new NewAdHocRule(ScannerReport.ExternalIssue.newBuilder().setEngineId("eslint").setRuleId("no-cond-assign").build()));
+
+    underTest.execute(new TestComputationStepContext());
+
+    RuleDao ruleDao = dbClient.ruleDao();
+    assertThat(ruleDao.selectAll(dbClient.openSession(false))).hasSize(1);
+    assertThat(es.countDocuments(RuleIndexDefinition.TYPE_RULE)).isZero();
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistDuplicationDataStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistDuplicationDataStepIT.java
new file mode 100644 (file)
index 0000000..169fe26
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ * 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.Optional;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.projectanalysis.analysis.MutableAnalysisMetadataHolderRule;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
+import org.sonar.ce.task.projectanalysis.duplication.DuplicationRepositoryRule;
+import org.sonar.ce.task.projectanalysis.duplication.TextBlock;
+import org.sonar.ce.task.projectanalysis.measure.MeasureToMeasureDto;
+import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule;
+import org.sonar.ce.task.step.ComputationStep;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.measure.LiveMeasureDto;
+import org.sonar.db.metric.MetricDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.measures.CoreMetrics.DUPLICATIONS_DATA_KEY;
+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;
+
+public class PersistDuplicationDataStepIT extends BaseStepTest {
+
+  private static final int ROOT_REF = 1;
+  private static final String PROJECT_KEY = "PROJECT_KEY";
+  private static final String PROJECT_UUID = "u1";
+
+  private static final int FILE_1_REF = 2;
+  private static final String FILE_1_KEY = "FILE_1_KEY";
+  private static final String FILE_1_UUID = "u2";
+
+  private static final int FILE_2_REF = 3;
+  private static final String FILE_2_KEY = "FILE_2_KEY";
+  private static final String FILE_2_UUID = "u3";
+
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule()
+    .setRoot(
+      builder(PROJECT, ROOT_REF).setKey(PROJECT_KEY).setUuid(PROJECT_UUID)
+        .addChildren(
+          builder(FILE, FILE_1_REF).setKey(FILE_1_KEY).setUuid(FILE_1_UUID)
+            .build(),
+          builder(FILE, FILE_2_REF).setKey(FILE_2_KEY).setUuid(FILE_2_UUID)
+            .build())
+        .build());
+
+  @Rule
+  public MutableAnalysisMetadataHolderRule analysisMetadataHolder = new MutableAnalysisMetadataHolderRule();
+  @Rule
+  public DuplicationRepositoryRule duplicationRepository = DuplicationRepositoryRule.create(treeRootHolder);
+  @Rule
+  public MetricRepositoryRule metricRepository = new MetricRepositoryRule();
+
+  @Before
+  public void setUp() {
+    MetricDto metric = db.measures().insertMetric(m -> m.setKey(DUPLICATIONS_DATA_KEY).setValueType(Metric.ValueType.STRING.name()));
+    insertComponent(PROJECT_KEY, PROJECT_UUID);
+    insertComponent(FILE_1_KEY, FILE_1_UUID);
+    insertComponent(FILE_2_KEY, FILE_2_UUID);
+    db.commit();
+    metricRepository.add(metric.getUuid(), new Metric.Builder(DUPLICATIONS_DATA_KEY, DUPLICATIONS_DATA_KEY, Metric.ValueType.STRING).create());
+  }
+
+  @Override
+  protected ComputationStep step() {
+    return underTest();
+  }
+
+  @Test
+  public void nothing_to_persist_when_no_duplication() {
+    TestComputationStepContext context = new TestComputationStepContext();
+
+    underTest().execute(context);
+
+    assertThatNothingPersisted();
+    verifyStatistics(context, 0);
+  }
+
+  @Test
+  public void compute_duplications_on_same_file() {
+    duplicationRepository.addDuplication(FILE_1_REF, new TextBlock(1, 5), new TextBlock(6, 10));
+    TestComputationStepContext context = new TestComputationStepContext();
+
+    underTest().execute(context);
+
+    assertThat(selectMeasureData(FILE_1_UUID)).hasValue("<duplications><g><b s=\"1\" l=\"5\" t=\"false\" r=\"" + FILE_1_KEY + "\"/><b s=\"6\" l=\"5\" t=\"false\" r=\""
+        + FILE_1_KEY + "\"/></g></duplications>");
+    assertThat(selectMeasureData(FILE_2_UUID)).isEmpty();
+    assertThat(selectMeasureData(PROJECT_UUID)).isEmpty();
+  }
+
+  @Test
+  public void compute_duplications_on_different_files() {
+    duplicationRepository.addDuplication(FILE_1_REF, new TextBlock(1, 5), FILE_2_REF, new TextBlock(6, 10));
+    TestComputationStepContext context = new TestComputationStepContext();
+
+    underTest().execute(context);
+
+    assertThat(selectMeasureData(FILE_1_UUID)).hasValue(
+      "<duplications><g><b s=\"1\" l=\"5\" t=\"false\" r=\"" + FILE_1_KEY + "\"/><b s=\"6\" l=\"5\" t=\"false\" r=\""
+        + FILE_2_KEY + "\"/></g></duplications>");
+    assertThat(selectMeasureData(FILE_2_UUID)).isEmpty();
+    assertThat(selectMeasureData(PROJECT_UUID)).isEmpty();
+  }
+
+  @Test
+  public void compute_duplications_on_unchanged_file() {
+    duplicationRepository.addExtendedProjectDuplication(FILE_1_REF, new TextBlock(1, 5), FILE_2_REF, new TextBlock(6, 10));
+    TestComputationStepContext context = new TestComputationStepContext();
+
+    underTest().execute(context);
+
+    assertThat(selectMeasureData(FILE_1_UUID)).hasValue(
+      "<duplications><g><b s=\"1\" l=\"5\" t=\"false\" r=\"" + FILE_1_KEY + "\"/><b s=\"6\" l=\"5\" t=\"true\" r=\""
+        + FILE_2_KEY + "\"/></g></duplications>");
+    assertThat(selectMeasureData(FILE_2_UUID)).isEmpty();
+    assertThat(selectMeasureData(PROJECT_UUID)).isEmpty();
+  }
+
+  @Test
+  public void compute_duplications_on_different_projects() {
+    String fileKeyFromOtherProject = "PROJECT2_KEY:file2";
+    duplicationRepository.addCrossProjectDuplication(FILE_1_REF, new TextBlock(1, 5), fileKeyFromOtherProject, new TextBlock(6, 10));
+    TestComputationStepContext context = new TestComputationStepContext();
+
+    underTest().execute(context);
+
+    assertThat(selectMeasureData(FILE_1_UUID)).hasValue(
+      "<duplications><g><b s=\"1\" l=\"5\" t=\"false\" r=\"" + FILE_1_KEY + "\"/><b s=\"6\" l=\"5\" t=\"false\" r=\""
+        + fileKeyFromOtherProject + "\"/></g></duplications>");
+    assertThat(selectMeasureData(FILE_2_UUID)).isEmpty();
+    assertThat(selectMeasureData(PROJECT_UUID)).isEmpty();
+  }
+
+  private PersistDuplicationDataStep underTest() {
+    return new PersistDuplicationDataStep(db.getDbClient(), treeRootHolder, metricRepository, duplicationRepository,
+      new MeasureToMeasureDto(analysisMetadataHolder, treeRootHolder));
+  }
+
+  private void assertThatNothingPersisted() {
+    assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isZero();
+  }
+
+  private Optional<String> selectMeasureData(String componentUuid) {
+      return db.getDbClient().liveMeasureDao().selectMeasure(db.getSession(), componentUuid, "duplications_data")
+      .map(LiveMeasureDto::getTextValue);
+  }
+
+  private ComponentDto insertComponent(String key, String uuid) {
+    ComponentDto componentDto = new ComponentDto()
+      .setKey(key)
+      .setUuid(uuid)
+      .setUuidPath(uuid + ".")
+      .setBranchUuid(uuid);
+    db.components().insertComponent(componentDto);
+    return componentDto;
+  }
+
+  private static void verifyStatistics(TestComputationStepContext context, int expectedInsertsOrUpdates) {
+    context.getStatistics().assertValue("insertsOrUpdates", expectedInsertsOrUpdates);
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStepIT.java
new file mode 100644 (file)
index 0000000..99d9bac
--- /dev/null
@@ -0,0 +1,631 @@
+/*
+ * 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.Date;
+import java.util.List;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.ArgumentCaptor;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.RuleType;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
+import org.sonar.ce.task.projectanalysis.issue.AdHocRuleCreator;
+import org.sonar.ce.task.projectanalysis.issue.ProtoIssueCache;
+import org.sonar.ce.task.projectanalysis.issue.RuleRepositoryImpl;
+import org.sonar.ce.task.projectanalysis.issue.UpdateConflictResolver;
+import org.sonar.ce.task.projectanalysis.period.Period;
+import org.sonar.ce.task.projectanalysis.period.PeriodHolderRule;
+import org.sonar.ce.task.projectanalysis.util.cache.DiskCache;
+import org.sonar.ce.task.step.ComputationStep;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.DefaultIssueComment;
+import org.sonar.core.issue.FieldDiffs;
+import org.sonar.core.util.UuidFactoryImpl;
+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.issue.IssueChangeDto;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.issue.IssueMapper;
+import org.sonar.db.newcodeperiod.NewCodePeriodType;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleTesting;
+import org.sonar.scanner.protocol.output.ScannerReport;
+import org.sonar.server.issue.IssueStorage;
+
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.data.MapEntry.entry;
+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.when;
+import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
+import static org.sonar.api.issue.Issue.STATUS_CLOSED;
+import static org.sonar.api.issue.Issue.STATUS_OPEN;
+import static org.sonar.api.rule.Severity.BLOCKER;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+import static org.sonar.db.issue.IssueTesting.newCodeReferenceIssue;
+
+public class PersistIssuesStepIT extends BaseStepTest {
+
+  private static final long NOW = 1_400_000_000_000L;
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+  @Rule
+  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
+  @Rule
+  public PeriodHolderRule periodHolder = new PeriodHolderRule();
+
+  private System2 system2 = mock(System2.class);
+  private DbSession session = db.getSession();
+  private DbClient dbClient = db.getDbClient();
+  private UpdateConflictResolver conflictResolver = mock(UpdateConflictResolver.class);
+  private ProtoIssueCache protoIssueCache;
+  private ComputationStep underTest;
+
+  private AdHocRuleCreator adHocRuleCreator = mock(AdHocRuleCreator.class);
+
+  @Override
+  protected ComputationStep step() {
+    return underTest;
+  }
+
+  @Before
+  public void setup() throws Exception {
+    periodHolder.setPeriod(new Period(NewCodePeriodType.NUMBER_OF_DAYS.name(), "10", 1000L));
+
+    protoIssueCache = new ProtoIssueCache(temp.newFile(), System2.INSTANCE);
+    reportReader.setMetadata(ScannerReport.Metadata.getDefaultInstance());
+
+    underTest = new PersistIssuesStep(dbClient, system2, conflictResolver, new RuleRepositoryImpl(adHocRuleCreator, dbClient), periodHolder,
+      protoIssueCache, new IssueStorage(), UuidFactoryImpl.INSTANCE);
+  }
+
+  @After
+  public void tearDown() {
+    session.close();
+  }
+
+  @Test
+  public void insert_copied_issue() {
+    RuleDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01"));
+    db.rules().insert(rule);
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project, null));
+    when(system2.now()).thenReturn(NOW);
+    String issueKey = "ISSUE-1";
+
+    protoIssueCache.newAppender().append(new DefaultIssue()
+        .setKey(issueKey)
+        .setType(RuleType.CODE_SMELL)
+        .setRuleKey(rule.getKey())
+        .setComponentUuid(file.uuid())
+        .setComponentKey(file.getKey())
+        .setProjectUuid(project.uuid())
+        .setProjectKey(project.getKey())
+        .setSeverity(BLOCKER)
+        .setStatus(STATUS_OPEN)
+        .setTags(singletonList("test"))
+        .setNew(false)
+        .setCopied(true)
+        .setType(RuleType.BUG)
+        .setCreationDate(new Date(NOW))
+        .setSelectedAt(NOW)
+        .addComment(new DefaultIssueComment()
+          .setKey("COMMENT")
+          .setIssueKey(issueKey)
+          .setUserUuid("john_uuid")
+          .setMarkdownText("Some text")
+          .setCreatedAt(new Date(NOW))
+          .setUpdatedAt(new Date(NOW))
+          .setNew(true))
+        .setCurrentChange(
+          new FieldDiffs()
+            .setIssueKey(issueKey)
+            .setUserUuid("john_uuid")
+            .setDiff("technicalDebt", null, 1L)
+            .setCreationDate(new Date(NOW))))
+      .close();
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    IssueDto result = dbClient.issueDao().selectOrFailByKey(session, issueKey);
+
+    assertThat(result.getKey()).isEqualTo(issueKey);
+    assertThat(result.getRuleKey()).isEqualTo(rule.getKey());
+    assertThat(result.getComponentUuid()).isEqualTo(file.uuid());
+    assertThat(result.getProjectUuid()).isEqualTo(project.uuid());
+    assertThat(result.getSeverity()).isEqualTo(BLOCKER);
+    assertThat(result.getStatus()).isEqualTo(STATUS_OPEN);
+    assertThat(result.getType()).isEqualTo(RuleType.BUG.getDbConstant());
+    assertThat(result.getTags()).containsExactlyInAnyOrder("test");
+    assertThat(result.isNewCodeReferenceIssue()).isFalse();
+
+    List<IssueChangeDto> changes = dbClient.issueChangeDao().selectByIssueKeys(session, Arrays.asList(issueKey));
+    assertThat(changes).extracting(IssueChangeDto::getChangeType).containsExactly(IssueChangeDto.TYPE_COMMENT, IssueChangeDto.TYPE_FIELD_CHANGE);
+    assertThat(context.getStatistics().getAll()).contains(
+      entry("inserts", "1"), entry("updates", "0"), entry("merged", "0"));
+  }
+
+  @Test
+  public void insert_copied_issue_with_minimal_info() {
+    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "master", null));
+
+    RuleDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01"));
+    db.rules().insert(rule);
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project, null));
+    when(system2.now()).thenReturn(NOW);
+    String issueKey = "ISSUE-2";
+
+    protoIssueCache.newAppender().append(new DefaultIssue()
+        .setKey(issueKey)
+        .setType(RuleType.CODE_SMELL)
+        .setRuleKey(rule.getKey())
+        .setComponentUuid(file.uuid())
+        .setComponentKey(file.getKey())
+        .setProjectUuid(project.uuid())
+        .setProjectKey(project.getKey())
+        .setSeverity(BLOCKER)
+        .setStatus(STATUS_OPEN)
+        .setNew(false)
+        .setCopied(true)
+        .setType(RuleType.BUG)
+        .setCreationDate(new Date(NOW))
+        .setSelectedAt(NOW))
+      .close();
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    IssueDto result = dbClient.issueDao().selectOrFailByKey(session, issueKey);
+    assertThat(result.getKey()).isEqualTo(issueKey);
+    assertThat(result.getRuleKey()).isEqualTo(rule.getKey());
+    assertThat(result.getComponentUuid()).isEqualTo(file.uuid());
+    assertThat(result.getProjectUuid()).isEqualTo(project.uuid());
+    assertThat(result.getSeverity()).isEqualTo(BLOCKER);
+    assertThat(result.getStatus()).isEqualTo(STATUS_OPEN);
+    assertThat(result.getType()).isEqualTo(RuleType.BUG.getDbConstant());
+    assertThat(result.getTags()).isEmpty();
+    assertThat(result.isNewCodeReferenceIssue()).isFalse();
+
+    assertThat(dbClient.issueChangeDao().selectByIssueKeys(session, Arrays.asList(issueKey))).isEmpty();
+    assertThat(context.getStatistics().getAll()).contains(
+      entry("inserts", "1"), entry("updates", "0"), entry("merged", "0"));
+  }
+
+  @Test
+  public void insert_merged_issue() {
+    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "master", null));
+    RuleDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01"));
+    db.rules().insert(rule);
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project, null));
+    when(system2.now()).thenReturn(NOW);
+    String issueKey = "ISSUE-3";
+
+    protoIssueCache.newAppender().append(new DefaultIssue()
+        .setKey(issueKey)
+        .setType(RuleType.CODE_SMELL)
+        .setRuleKey(rule.getKey())
+        .setComponentUuid(file.uuid())
+        .setComponentKey(file.getKey())
+        .setProjectUuid(project.uuid())
+        .setProjectKey(project.getKey())
+        .setSeverity(BLOCKER)
+        .setStatus(STATUS_OPEN)
+        .setNew(true)
+        .setIsOnChangedLine(true)
+        .setCopied(true)
+        .setType(RuleType.BUG)
+        .setCreationDate(new Date(NOW))
+        .setSelectedAt(NOW)
+        .addComment(new DefaultIssueComment()
+          .setKey("COMMENT")
+          .setIssueKey(issueKey)
+          .setUserUuid("john_uuid")
+          .setMarkdownText("Some text")
+          .setUpdatedAt(new Date(NOW))
+          .setCreatedAt(new Date(NOW))
+          .setNew(true))
+        .setCurrentChange(new FieldDiffs()
+          .setIssueKey(issueKey)
+          .setUserUuid("john_uuid")
+          .setDiff("technicalDebt", null, 1L)
+          .setCreationDate(new Date(NOW))))
+      .close();
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    IssueDto result = dbClient.issueDao().selectOrFailByKey(session, issueKey);
+    assertThat(result.getKey()).isEqualTo(issueKey);
+    assertThat(result.getRuleKey()).isEqualTo(rule.getKey());
+    assertThat(result.getComponentUuid()).isEqualTo(file.uuid());
+    assertThat(result.getProjectUuid()).isEqualTo(project.uuid());
+    assertThat(result.getSeverity()).isEqualTo(BLOCKER);
+    assertThat(result.getStatus()).isEqualTo(STATUS_OPEN);
+    assertThat(result.getType()).isEqualTo(RuleType.BUG.getDbConstant());
+    assertThat(result.isNewCodeReferenceIssue()).isTrue();
+
+    List<IssueChangeDto> changes = dbClient.issueChangeDao().selectByIssueKeys(session, Arrays.asList(issueKey));
+    assertThat(changes).extracting(IssueChangeDto::getChangeType).containsExactly(IssueChangeDto.TYPE_COMMENT, IssueChangeDto.TYPE_FIELD_CHANGE);
+    assertThat(context.getStatistics().getAll()).contains(
+      entry("inserts", "1"), entry("updates", "0"), entry("merged", "0"));
+  }
+
+  @Test
+  public void update_conflicting_issue() {
+    RuleDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01"));
+    db.rules().insert(rule);
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project, null));
+    IssueDto issue = db.issues().insert(rule, project, file,
+      i -> i.setStatus(STATUS_OPEN)
+        .setResolution(null)
+        .setCreatedAt(NOW - 1_000_000_000L)
+        // simulate the issue has been updated after the analysis ran
+        .setUpdatedAt(NOW + 1_000_000_000L));
+    issue = dbClient.issueDao().selectByKey(db.getSession(), issue.getKey()).get();
+    DiskCache.CacheAppender issueCacheAppender = protoIssueCache.newAppender();
+    when(system2.now()).thenReturn(NOW);
+
+    DefaultIssue defaultIssue = issue.toDefaultIssue()
+      .setStatus(STATUS_CLOSED)
+      .setResolution(RESOLUTION_FIXED)
+      .setSelectedAt(NOW)
+      .setNew(false)
+      .setChanged(true);
+    issueCacheAppender.append(defaultIssue).close();
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    ArgumentCaptor<IssueDto> issueDtoCaptor = ArgumentCaptor.forClass(IssueDto.class);
+    verify(conflictResolver).resolve(eq(defaultIssue), issueDtoCaptor.capture(), any(IssueMapper.class));
+    assertThat(issueDtoCaptor.getValue().getKey()).isEqualTo(issue.getKey());
+    assertThat(context.getStatistics().getAll()).contains(
+      entry("inserts", "0"), entry("updates", "1"), entry("merged", "1"));
+  }
+
+  @Test
+  public void insert_new_issue() {
+    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "master", null));
+    RuleDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01"));
+    db.rules().insert(rule);
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project, null));
+    session.commit();
+    String issueKey = "ISSUE-4";
+
+    protoIssueCache.newAppender().append(new DefaultIssue()
+      .setKey(issueKey)
+      .setType(RuleType.CODE_SMELL)
+      .setRuleKey(rule.getKey())
+      .setComponentUuid(file.uuid())
+      .setComponentKey(file.getKey())
+      .setProjectUuid(project.uuid())
+      .setProjectKey(project.getKey())
+      .setSeverity(BLOCKER)
+      .setStatus(STATUS_OPEN)
+      .setCreationDate(new Date(NOW))
+      .setNew(true)
+      .setIsOnChangedLine(true)
+      .setType(RuleType.BUG)).close();
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    IssueDto result = dbClient.issueDao().selectOrFailByKey(session, issueKey);
+    assertThat(result.getKey()).isEqualTo(issueKey);
+    assertThat(result.getRuleKey()).isEqualTo(rule.getKey());
+    assertThat(result.getComponentUuid()).isEqualTo(file.uuid());
+    assertThat(result.getProjectUuid()).isEqualTo(project.uuid());
+    assertThat(result.getSeverity()).isEqualTo(BLOCKER);
+    assertThat(result.getStatus()).isEqualTo(STATUS_OPEN);
+    assertThat(result.getType()).isEqualTo(RuleType.BUG.getDbConstant());
+    assertThat(context.getStatistics().getAll()).contains(
+      entry("inserts", "1"), entry("updates", "0"), entry("merged", "0"));
+    assertThat(result.isNewCodeReferenceIssue()).isTrue();
+  }
+
+  @Test
+  public void close_issue() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
+    RuleDto rule = db.rules().insert();
+    IssueDto issue = db.issues().insert(rule, project, file,
+      i -> i.setStatus(STATUS_OPEN)
+        .setResolution(null)
+        .setCreatedAt(NOW - 1_000_000_000L)
+        .setUpdatedAt(NOW - 1_000_000_000L));
+    DiskCache.CacheAppender issueCacheAppender = protoIssueCache.newAppender();
+
+    issueCacheAppender.append(
+        issue.toDefaultIssue()
+          .setStatus(STATUS_CLOSED)
+          .setResolution(RESOLUTION_FIXED)
+          .setSelectedAt(NOW)
+          .setNew(false)
+          .setChanged(true))
+      .close();
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    IssueDto issueReloaded = db.getDbClient().issueDao().selectByKey(db.getSession(), issue.getKey()).get();
+    assertThat(issueReloaded.getStatus()).isEqualTo(STATUS_CLOSED);
+    assertThat(issueReloaded.getResolution()).isEqualTo(RESOLUTION_FIXED);
+    assertThat(context.getStatistics().getAll()).contains(
+      entry("inserts", "0"), entry("updates", "1"), entry("merged", "0"));
+  }
+
+  @Test
+  public void handle_no_longer_new_issue() {
+    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "master", null));
+    RuleDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01"));
+    db.rules().insert(rule);
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project, null));
+    when(system2.now()).thenReturn(NOW);
+    String issueKey = "ISSUE-5";
+
+    DefaultIssue defaultIssue = new DefaultIssue()
+      .setKey(issueKey)
+      .setType(RuleType.CODE_SMELL)
+      .setRuleKey(rule.getKey())
+      .setComponentUuid(file.uuid())
+      .setComponentKey(file.getKey())
+      .setProjectUuid(project.uuid())
+      .setProjectKey(project.getKey())
+      .setSeverity(BLOCKER)
+      .setStatus(STATUS_OPEN)
+      .setNew(true)
+      .setIsOnChangedLine(true)
+      .setIsNewCodeReferenceIssue(true)
+      .setIsNoLongerNewCodeReferenceIssue(false)
+      .setCopied(false)
+      .setType(RuleType.BUG)
+      .setCreationDate(new Date(NOW))
+      .setSelectedAt(NOW);
+
+    IssueDto issueDto = IssueDto.toDtoForComputationInsert(defaultIssue, rule.getUuid(), NOW);
+    dbClient.issueDao().insert(session, issueDto);
+    dbClient.issueDao().insertAsNewCodeOnReferenceBranch(session, newCodeReferenceIssue(issueDto));
+    session.commit();
+
+    IssueDto result = dbClient.issueDao().selectOrFailByKey(session, issueKey);
+    assertThat(result.isNewCodeReferenceIssue()).isTrue();
+
+    protoIssueCache.newAppender().append(defaultIssue.setNew(false)
+        .setIsOnChangedLine(false)
+        .setIsNewCodeReferenceIssue(false)
+        .setIsNoLongerNewCodeReferenceIssue(true))
+      .close();
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(context.getStatistics().getAll()).contains(
+      entry("inserts", "0"), entry("updates", "1"), entry("merged", "0"));
+
+    result = dbClient.issueDao().selectOrFailByKey(session, issueKey);
+    assertThat(result.isNewCodeReferenceIssue()).isFalse();
+  }
+
+  @Test
+  public void handle_existing_new_code_issue_migration() {
+    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "master", null));
+    RuleDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01"));
+    db.rules().insert(rule);
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project, null));
+    when(system2.now()).thenReturn(NOW);
+    String issueKey = "ISSUE-6";
+
+    DefaultIssue defaultIssue = new DefaultIssue()
+      .setKey(issueKey)
+      .setType(RuleType.CODE_SMELL)
+      .setRuleKey(rule.getKey())
+      .setComponentUuid(file.uuid())
+      .setComponentKey(file.getKey())
+      .setProjectUuid(project.uuid())
+      .setProjectKey(project.getKey())
+      .setSeverity(BLOCKER)
+      .setStatus(STATUS_OPEN)
+      .setNew(true)
+      .setCopied(false)
+      .setType(RuleType.BUG)
+      .setCreationDate(new Date(NOW))
+      .setSelectedAt(NOW);
+
+    IssueDto issueDto = IssueDto.toDtoForComputationInsert(defaultIssue, rule.getUuid(), NOW);
+    dbClient.issueDao().insert(session, issueDto);
+    session.commit();
+
+    IssueDto result = dbClient.issueDao().selectOrFailByKey(session, issueKey);
+    assertThat(result.isNewCodeReferenceIssue()).isFalse();
+
+    protoIssueCache.newAppender().append(defaultIssue.setNew(false)
+        .setIsOnChangedLine(true)
+        .setIsNewCodeReferenceIssue(false)
+        .setIsNoLongerNewCodeReferenceIssue(false))
+      .close();
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(context.getStatistics().getAll()).contains(
+      entry("inserts", "0"), entry("updates", "1"), entry("merged", "0"));
+
+    result = dbClient.issueDao().selectOrFailByKey(session, issueKey);
+    assertThat(result.isNewCodeReferenceIssue()).isTrue();
+  }
+
+  @Test
+  public void handle_existing_without_need_for_new_code_issue_migration() {
+    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "master", null));
+    RuleDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01"));
+    db.rules().insert(rule);
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project, null));
+    when(system2.now()).thenReturn(NOW);
+    String issueKey = "ISSUE-7";
+
+    DefaultIssue defaultIssue = new DefaultIssue()
+      .setKey(issueKey)
+      .setType(RuleType.CODE_SMELL)
+      .setRuleKey(rule.getKey())
+      .setComponentUuid(file.uuid())
+      .setComponentKey(file.getKey())
+      .setProjectUuid(project.uuid())
+      .setProjectKey(project.getKey())
+      .setSeverity(BLOCKER)
+      .setStatus(STATUS_OPEN)
+      .setNew(true)
+      .setIsOnChangedLine(true)
+      .setIsNewCodeReferenceIssue(true)
+      .setIsNoLongerNewCodeReferenceIssue(false)
+      .setCopied(false)
+      .setType(RuleType.BUG)
+      .setCreationDate(new Date(NOW))
+      .setSelectedAt(NOW);
+
+    IssueDto issueDto = IssueDto.toDtoForComputationInsert(defaultIssue, rule.getUuid(), NOW);
+    dbClient.issueDao().insert(session, issueDto);
+    dbClient.issueDao().insertAsNewCodeOnReferenceBranch(session, newCodeReferenceIssue(issueDto));
+    session.commit();
+
+    IssueDto result = dbClient.issueDao().selectOrFailByKey(session, issueKey);
+    assertThat(result.isNewCodeReferenceIssue()).isTrue();
+
+    protoIssueCache.newAppender().append(defaultIssue.setNew(false)
+        .setIsOnChangedLine(false)
+        .setIsNewCodeReferenceIssue(true)
+        .setIsOnChangedLine(true)
+        .setIsNoLongerNewCodeReferenceIssue(false))
+      .close();
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    assertThat(context.getStatistics().getAll()).contains(
+      entry("inserts", "0"), entry("updates", "0"), entry("merged", "0"));
+
+    result = dbClient.issueDao().selectOrFailByKey(session, issueKey);
+    assertThat(result.isNewCodeReferenceIssue()).isTrue();
+  }
+
+  @Test
+  public void add_comment() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
+    RuleDto rule = db.rules().insert();
+    IssueDto issue = db.issues().insert(rule, project, file,
+      i -> i.setStatus(STATUS_OPEN)
+        .setResolution(null)
+        .setCreatedAt(NOW - 1_000_000_000L)
+        .setUpdatedAt(NOW - 1_000_000_000L));
+    DiskCache.CacheAppender issueCacheAppender = protoIssueCache.newAppender();
+
+    issueCacheAppender.append(
+        issue.toDefaultIssue()
+          .setStatus(STATUS_CLOSED)
+          .setResolution(RESOLUTION_FIXED)
+          .setSelectedAt(NOW)
+          .setNew(false)
+          .setChanged(true)
+          .addComment(new DefaultIssueComment()
+            .setKey("COMMENT")
+            .setIssueKey(issue.getKey())
+            .setUserUuid("john_uuid")
+            .setMarkdownText("Some text")
+            .setCreatedAt(new Date(NOW))
+            .setUpdatedAt(new Date(NOW))
+            .setNew(true)))
+      .close();
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    IssueChangeDto issueChangeDto = db.getDbClient().issueChangeDao().selectByIssueKeys(db.getSession(), singletonList(issue.getKey())).get(0);
+    assertThat(issueChangeDto)
+      .extracting(IssueChangeDto::getChangeType, IssueChangeDto::getUserUuid, IssueChangeDto::getChangeData, IssueChangeDto::getIssueKey,
+        IssueChangeDto::getIssueChangeCreationDate)
+      .containsOnly(IssueChangeDto.TYPE_COMMENT, "john_uuid", "Some text", issue.getKey(), NOW);
+    assertThat(context.getStatistics().getAll()).contains(
+      entry("inserts", "0"), entry("updates", "1"), entry("merged", "0"));
+  }
+
+  @Test
+  public void add_change() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project));
+    RuleDto rule = db.rules().insert();
+    IssueDto issue = db.issues().insert(rule, project, file,
+      i -> i.setStatus(STATUS_OPEN)
+        .setResolution(null)
+        .setCreatedAt(NOW - 1_000_000_000L)
+        .setUpdatedAt(NOW - 1_000_000_000L));
+    DiskCache.CacheAppender issueCacheAppender = protoIssueCache.newAppender();
+
+    issueCacheAppender.append(
+        issue.toDefaultIssue()
+          .setStatus(STATUS_CLOSED)
+          .setResolution(RESOLUTION_FIXED)
+          .setSelectedAt(NOW)
+          .setNew(false)
+          .setChanged(true)
+          .setIsOnChangedLine(false)
+          .setIsNewCodeReferenceIssue(false)
+          .setCurrentChange(new FieldDiffs()
+            .setIssueKey("ISSUE")
+            .setUserUuid("john_uuid")
+            .setDiff("technicalDebt", null, 1L)
+            .setCreationDate(new Date(NOW))))
+      .close();
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    IssueChangeDto issueChangeDto = db.getDbClient().issueChangeDao().selectByIssueKeys(db.getSession(), singletonList(issue.getKey())).get(0);
+    assertThat(issueChangeDto)
+      .extracting(IssueChangeDto::getChangeType, IssueChangeDto::getUserUuid, IssueChangeDto::getChangeData, IssueChangeDto::getIssueKey,
+        IssueChangeDto::getIssueChangeCreationDate)
+      .containsOnly(IssueChangeDto.TYPE_FIELD_CHANGE, "john_uuid", "technicalDebt=1", issue.getKey(), NOW);
+    assertThat(context.getStatistics().getAll()).contains(
+      entry("inserts", "0"), entry("updates", "1"), entry("merged", "0"));
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistLiveMeasuresStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistLiveMeasuresStepIT.java
new file mode 100644 (file)
index 0000000..f4829de
--- /dev/null
@@ -0,0 +1,323 @@
+/*
+ * 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.Optional;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.projectanalysis.analysis.MutableAnalysisMetadataHolderRule;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.FileStatuses;
+import org.sonar.ce.task.projectanalysis.component.ReportComponent;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
+import org.sonar.ce.task.projectanalysis.component.ViewsComponent;
+import org.sonar.ce.task.projectanalysis.measure.MeasureRepositoryRule;
+import org.sonar.ce.task.projectanalysis.measure.MeasureToMeasureDto;
+import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule;
+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.db.component.ComponentDto;
+import org.sonar.db.measure.LiveMeasureDto;
+import org.sonar.db.metric.MetricDto;
+import org.sonar.server.project.Project;
+
+import static java.util.Collections.emptyList;
+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.api.measures.CoreMetrics.BUGS;
+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.Component.Type.PROJECT_VIEW;
+import static org.sonar.ce.task.projectanalysis.component.Component.Type.SUBVIEW;
+import static org.sonar.ce.task.projectanalysis.component.Component.Type.VIEW;
+import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilder;
+import static org.sonar.db.measure.MeasureTesting.newLiveMeasure;
+
+public class PersistLiveMeasuresStepIT extends BaseStepTest {
+
+  private static final Metric STRING_METRIC = new Metric.Builder("string-metric", "String metric", Metric.ValueType.STRING).create();
+  private static final Metric INT_METRIC = new Metric.Builder("int-metric", "int metric", Metric.ValueType.INT).create();
+  private static final Metric METRIC_WITH_BEST_VALUE = new Metric.Builder("best-value-metric", "best value metric", Metric.ValueType.INT)
+    .setBestValue(0.0)
+    .setOptimizedBestValue(true)
+    .create();
+
+  private static final int REF_1 = 1;
+  private static final int REF_2 = 2;
+  private static final int REF_3 = 3;
+  private static final int REF_4 = 4;
+
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+  @Rule
+  public MetricRepositoryRule metricRepository = new MetricRepositoryRule();
+  @Rule
+  public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
+  @Rule
+  public MutableAnalysisMetadataHolderRule analysisMetadataHolder = new MutableAnalysisMetadataHolderRule();
+
+  private final FileStatuses fileStatuses = mock(FileStatuses.class);
+  private final DbClient dbClient = db.getDbClient();
+  private final TestComputationStepContext context = new TestComputationStepContext();
+
+  @Before
+  public void setUp() {
+    MetricDto stringMetricDto = db.measures().insertMetric(m -> m.setKey(STRING_METRIC.getKey()).setValueType(Metric.ValueType.STRING.name()));
+    MetricDto intMetricDto = db.measures().insertMetric(m -> m.setKey(INT_METRIC.getKey()).setValueType(Metric.ValueType.INT.name()));
+    MetricDto bestValueMMetricDto = db.measures()
+      .insertMetric(m -> m.setKey(METRIC_WITH_BEST_VALUE.getKey()).setValueType(Metric.ValueType.INT.name()).setOptimizedBestValue(true).setBestValue(0.0));
+    MetricDto bugs = db.measures().insertMetric(m -> m.setKey(BUGS.getKey()));
+    metricRepository.add(stringMetricDto.getUuid(), STRING_METRIC);
+    metricRepository.add(intMetricDto.getUuid(), INT_METRIC);
+    metricRepository.add(bestValueMMetricDto.getUuid(), METRIC_WITH_BEST_VALUE);
+    metricRepository.add(bugs.getUuid(), BUGS);
+  }
+
+  @Test
+  public void persist_live_measures_of_project_analysis() {
+    prepareProject();
+
+    // the computed measures
+    measureRepository.addRawMeasure(REF_1, STRING_METRIC.getKey(), newMeasureBuilder().create("project-value"));
+    measureRepository.addRawMeasure(REF_3, STRING_METRIC.getKey(), newMeasureBuilder().create("dir-value"));
+    measureRepository.addRawMeasure(REF_4, STRING_METRIC.getKey(), newMeasureBuilder().create("file-value"));
+
+    step().execute(context);
+
+    // all measures are persisted, from project to file
+    assertThat(db.countRowsOfTable("live_measures")).isEqualTo(3);
+    assertThat(selectMeasure("project-uuid", STRING_METRIC).get().getDataAsString()).isEqualTo("project-value");
+    assertThat(selectMeasure("dir-uuid", STRING_METRIC).get().getDataAsString()).isEqualTo("dir-value");
+    assertThat(selectMeasure("file-uuid", STRING_METRIC).get().getDataAsString()).isEqualTo("file-value");
+    verifyStatistics(context, 3);
+  }
+
+  @Test
+  public void measures_without_value_are_not_persisted() {
+    prepareProject();
+    measureRepository.addRawMeasure(REF_1, STRING_METRIC.getKey(), newMeasureBuilder().createNoValue());
+    measureRepository.addRawMeasure(REF_1, INT_METRIC.getKey(), newMeasureBuilder().createNoValue());
+
+    step().execute(context);
+
+    assertThatMeasureIsNotPersisted("project-uuid", STRING_METRIC);
+    assertThatMeasureIsNotPersisted("project-uuid", INT_METRIC);
+    verifyStatistics(context, 0);
+  }
+
+  @Test
+  public void measures_on_new_code_period_are_persisted() {
+    prepareProject();
+    measureRepository.addRawMeasure(REF_1, INT_METRIC.getKey(), newMeasureBuilder().create(42.0));
+
+    step().execute(context);
+
+    LiveMeasureDto persistedMeasure = selectMeasure("project-uuid", INT_METRIC).get();
+    assertThat(persistedMeasure.getValue()).isEqualTo(42.0);
+    verifyStatistics(context, 1);
+  }
+
+  @Test
+  public void delete_measures_from_db_if_no_longer_computed() {
+    prepareProject();
+    // measure to be updated
+    LiveMeasureDto measureOnFileInProject = insertMeasure("file-uuid", "project-uuid", INT_METRIC);
+    // measure to be deleted because not computed anymore
+    LiveMeasureDto otherMeasureOnFileInProject = insertMeasure("file-uuid", "project-uuid", STRING_METRIC);
+    // measure in another project, not touched
+    LiveMeasureDto measureInOtherProject = insertMeasure("other-file-uuid", "other-project-uuid", INT_METRIC);
+    db.commit();
+
+    measureRepository.addRawMeasure(REF_4, INT_METRIC.getKey(), newMeasureBuilder().create(42));
+
+    step().execute(context);
+
+    assertThatMeasureHasValue(measureOnFileInProject, 42);
+    assertThatMeasureDoesNotExist(otherMeasureOnFileInProject);
+    assertThatMeasureHasValue(measureInOtherProject, (int) measureInOtherProject.getValue().doubleValue());
+    verifyStatistics(context, 1);
+  }
+
+  @Test
+  public void do_not_persist_file_measures_with_best_value() {
+    prepareProject();
+    // measure to be deleted because new value matches the metric best value
+    LiveMeasureDto oldMeasure = insertMeasure("file-uuid", "project-uuid", INT_METRIC);
+    db.commit();
+
+    // project measure with metric best value -> persist with value 0
+    measureRepository.addRawMeasure(REF_1, METRIC_WITH_BEST_VALUE.getKey(), newMeasureBuilder().create(0));
+    // file measure with metric best value -> do not persist
+    measureRepository.addRawMeasure(REF_4, METRIC_WITH_BEST_VALUE.getKey(), newMeasureBuilder().create(0));
+
+    step().execute(context);
+
+    assertThatMeasureDoesNotExist(oldMeasure);
+    assertThatMeasureHasValue("project-uuid", METRIC_WITH_BEST_VALUE, 0);
+    verifyStatistics(context, 1);
+  }
+
+  @Test
+  public void keep_measures_for_unchanged_files() {
+    prepareProject();
+    LiveMeasureDto oldMeasure = insertMeasure("file-uuid", "project-uuid", BUGS);
+    db.commit();
+    when(fileStatuses.isDataUnchanged(any(Component.class))).thenReturn(true);
+    // this new value won't be persisted
+    measureRepository.addRawMeasure(REF_4, BUGS.getKey(), newMeasureBuilder().create(oldMeasure.getValue() + 1, 0));
+    step().execute(context);
+    assertThat(selectMeasure("file-uuid", BUGS).get().getValue()).isEqualTo(oldMeasure.getValue());
+  }
+
+  @Test
+  public void dont_keep_measures_for_unchanged_files() {
+    prepareProject();
+    LiveMeasureDto oldMeasure = insertMeasure("file-uuid", "project-uuid", BUGS);
+    db.commit();
+    when(fileStatuses.isDataUnchanged(any(Component.class))).thenReturn(false);
+    // this new value will be persisted
+    measureRepository.addRawMeasure(REF_4, BUGS.getKey(), newMeasureBuilder().create(oldMeasure.getValue() + 1, 0));
+    step().execute(context);
+    assertThat(selectMeasure("file-uuid", BUGS).get().getValue()).isEqualTo(oldMeasure.getValue() + 1);
+  }
+
+  @Test
+  public void persist_live_measures_of_portfolio_analysis() {
+    preparePortfolio();
+
+    // the computed measures
+    measureRepository.addRawMeasure(REF_1, STRING_METRIC.getKey(), newMeasureBuilder().create("view-value"));
+    measureRepository.addRawMeasure(REF_2, STRING_METRIC.getKey(), newMeasureBuilder().create("subview-value"));
+    measureRepository.addRawMeasure(REF_3, STRING_METRIC.getKey(), newMeasureBuilder().create("project-value"));
+
+    step().execute(context);
+
+    assertThat(db.countRowsOfTable("live_measures")).isEqualTo(3);
+    assertThat(selectMeasure("view-uuid", STRING_METRIC).get().getDataAsString()).isEqualTo("view-value");
+    assertThat(selectMeasure("subview-uuid", STRING_METRIC).get().getDataAsString()).isEqualTo("subview-value");
+    assertThat(selectMeasure("project-uuid", STRING_METRIC).get().getDataAsString()).isEqualTo("project-value");
+    verifyStatistics(context, 3);
+  }
+
+  private LiveMeasureDto insertMeasure(String componentUuid, String projectUuid, Metric metric) {
+    LiveMeasureDto measure = newLiveMeasure()
+      .setComponentUuid(componentUuid)
+      .setProjectUuid(projectUuid)
+      .setMetricUuid(metricRepository.getByKey(metric.getKey()).getUuid());
+    dbClient.liveMeasureDao().insertOrUpdate(db.getSession(), measure);
+    return measure;
+  }
+
+  private void assertThatMeasureHasValue(LiveMeasureDto template, int expectedValue) {
+    Optional<LiveMeasureDto> persisted = dbClient.liveMeasureDao().selectMeasure(db.getSession(),
+      template.getComponentUuid(), metricRepository.getByUuid(template.getMetricUuid()).getKey());
+    assertThat(persisted).isPresent();
+    assertThat(persisted.get().getValue()).isEqualTo(expectedValue);
+  }
+
+  private void assertThatMeasureHasValue(String componentUuid, Metric metric, int expectedValue) {
+    Optional<LiveMeasureDto> persisted = dbClient.liveMeasureDao().selectMeasure(db.getSession(),
+      componentUuid, metric.getKey());
+    assertThat(persisted).isPresent();
+    assertThat(persisted.get().getValue()).isEqualTo(expectedValue);
+  }
+
+  private void assertThatMeasureDoesNotExist(LiveMeasureDto template) {
+    assertThat(dbClient.liveMeasureDao().selectMeasure(db.getSession(),
+      template.getComponentUuid(), metricRepository.getByUuid(template.getMetricUuid()).getKey()))
+      .isEmpty();
+  }
+
+  private void prepareProject() {
+    // tree of components as defined by scanner report
+    Component project = ReportComponent.builder(PROJECT, REF_1).setUuid("project-uuid")
+      .addChildren(
+        ReportComponent.builder(DIRECTORY, REF_3).setUuid("dir-uuid")
+          .addChildren(
+            ReportComponent.builder(FILE, REF_4).setUuid("file-uuid")
+              .build())
+          .build())
+      .build();
+    treeRootHolder.setRoot(project);
+    analysisMetadataHolder.setProject(new Project(project.getUuid(), project.getKey(), project.getName(), project.getDescription(), emptyList()));
+
+    // components as persisted in db
+    ComponentDto projectDto = insertComponent("project-key", "project-uuid");
+    ComponentDto dirDto = insertComponent("dir-key", "dir-uuid");
+    ComponentDto fileDto = insertComponent("file-key", "file-uuid");
+  }
+
+  private void preparePortfolio() {
+    // tree of components
+    Component portfolio = ViewsComponent.builder(VIEW, REF_1).setUuid("view-uuid")
+      .addChildren(
+        ViewsComponent.builder(SUBVIEW, REF_2).setUuid("subview-uuid")
+          .addChildren(
+            ViewsComponent.builder(PROJECT_VIEW, REF_3).setUuid("project-uuid")
+              .build())
+          .build())
+      .build();
+    treeRootHolder.setRoot(portfolio);
+
+    // components as persisted in db
+    ComponentDto portfolioDto = insertComponent("view-key", "view-uuid");
+    ComponentDto subViewDto = insertComponent("subview-key", "subview-uuid");
+    ComponentDto projectDto = insertComponent("project-key", "project-uuid");
+    analysisMetadataHolder.setProject(Project.from(portfolioDto));
+  }
+
+  private void assertThatMeasureIsNotPersisted(String componentUuid, Metric metric) {
+    assertThat(selectMeasure(componentUuid, metric)).isEmpty();
+  }
+
+  private Optional<LiveMeasureDto> selectMeasure(String componentUuid, Metric metric) {
+    return dbClient.liveMeasureDao().selectMeasure(db.getSession(), componentUuid, metric.getKey());
+  }
+
+  private ComponentDto insertComponent(String key, String uuid) {
+    ComponentDto componentDto = new ComponentDto()
+      .setKey(key)
+      .setUuid(uuid)
+      .setUuidPath(uuid + ".")
+      .setBranchUuid(uuid);
+    db.components().insertComponent(componentDto);
+    return componentDto;
+  }
+
+  @Override
+  protected ComputationStep step() {
+    return new PersistLiveMeasuresStep(dbClient, metricRepository, new MeasureToMeasureDto(analysisMetadataHolder, treeRootHolder), treeRootHolder, measureRepository,
+      Optional.of(fileStatuses));
+  }
+
+  private static void verifyStatistics(TestComputationStepContext context, int expectedInsertsOrUpdates) {
+    context.getStatistics().assertValue("insertsOrUpdates", expectedInsertsOrUpdates);
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistMeasuresStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistMeasuresStepIT.java
new file mode 100644 (file)
index 0000000..6064b72
--- /dev/null
@@ -0,0 +1,241 @@
+/*
+ * 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.Optional;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.projectanalysis.analysis.MutableAnalysisMetadataHolderRule;
+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.component.ViewsComponent;
+import org.sonar.ce.task.projectanalysis.measure.MeasureRepositoryRule;
+import org.sonar.ce.task.projectanalysis.measure.MeasureToMeasureDto;
+import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule;
+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.db.component.ComponentDto;
+import org.sonar.db.measure.MeasureDto;
+import org.sonar.db.metric.MetricDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+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.Component.Type.PROJECT_VIEW;
+import static org.sonar.ce.task.projectanalysis.component.Component.Type.SUBVIEW;
+import static org.sonar.ce.task.projectanalysis.component.Component.Type.VIEW;
+import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilder;
+
+public class PersistMeasuresStepIT extends BaseStepTest {
+
+  private static final Metric STRING_METRIC = new Metric.Builder("string-metric", "String metric", Metric.ValueType.STRING).create();
+  private static final Metric INT_METRIC = new Metric.Builder("int-metric", "int metric", Metric.ValueType.INT).create();
+  private static final Metric NON_HISTORICAL_METRIC = new Metric.Builder("nh-metric", "nh metric", Metric.ValueType.INT).setDeleteHistoricalData(true).create();
+
+  private static final String ANALYSIS_UUID = "a1";
+
+  private static final int REF_1 = 1;
+  private static final int REF_2 = 2;
+  private static final int REF_3 = 3;
+  private static final int REF_4 = 4;
+
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+  @Rule
+  public MetricRepositoryRule metricRepository = new MetricRepositoryRule();
+  @Rule
+  public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
+  @Rule
+  public MutableAnalysisMetadataHolderRule analysisMetadataHolder = new MutableAnalysisMetadataHolderRule();
+
+  private DbClient dbClient = db.getDbClient();
+
+  @Before
+  public void setUp() {
+    analysisMetadataHolder.setUuid(ANALYSIS_UUID);
+    MetricDto stringMetricDto = db.measures().insertMetric(m -> m.setKey(STRING_METRIC.getKey()).setValueType(Metric.ValueType.STRING.name()));
+    MetricDto intMetricDto = db.measures().insertMetric(m -> m.setKey(INT_METRIC.getKey()).setValueType(Metric.ValueType.INT.name()));
+    MetricDto nhMetricDto = db.measures().insertMetric(m -> m.setKey(NON_HISTORICAL_METRIC.getKey()).setValueType(Metric.ValueType.INT.name()));
+    metricRepository.add(stringMetricDto.getUuid(), STRING_METRIC);
+    metricRepository.add(intMetricDto.getUuid(), INT_METRIC);
+    metricRepository.add(nhMetricDto.getUuid(), NON_HISTORICAL_METRIC);
+  }
+
+  @Test
+  public void measures_on_non_historical_metrics_are_not_persisted() {
+    prepareProject();
+    measureRepository.addRawMeasure(REF_1, NON_HISTORICAL_METRIC.getKey(), newMeasureBuilder().create(1));
+    measureRepository.addRawMeasure(REF_1, INT_METRIC.getKey(), newMeasureBuilder().create(2));
+
+    TestComputationStepContext context = execute();
+
+    assertThatMeasureIsNotPersisted("project-uuid", NON_HISTORICAL_METRIC);
+    MeasureDto persistedMeasure = selectMeasure("project-uuid", INT_METRIC).get();
+    assertThat(persistedMeasure.getValue()).isEqualTo(2);
+    assertNbOfInserts(context, 1);
+  }
+
+  @Test
+  public void persist_measures_of_project_analysis_excluding_directories() {
+    prepareProject();
+
+    // the computed measures
+    measureRepository.addRawMeasure(REF_1, STRING_METRIC.getKey(), newMeasureBuilder().create("project-value"));
+    measureRepository.addRawMeasure(REF_3, STRING_METRIC.getKey(), newMeasureBuilder().create("dir-value"));
+    measureRepository.addRawMeasure(REF_4, STRING_METRIC.getKey(), newMeasureBuilder().create("file-value"));
+
+    TestComputationStepContext context = execute();
+
+    // project and dir measures are persisted, but not file measures
+    assertThat(db.countRowsOfTable("project_measures")).isOne();
+    assertThat(selectMeasure("project-uuid", STRING_METRIC).get().getData()).isEqualTo("project-value");
+    assertThatMeasuresAreNotPersisted("dir-uuid");
+    assertThatMeasuresAreNotPersisted("file-uuid");
+    assertNbOfInserts(context, 1);
+  }
+
+  @Test
+  public void measures_without_value_are_not_persisted() {
+    prepareProject();
+    measureRepository.addRawMeasure(REF_1, STRING_METRIC.getKey(), newMeasureBuilder().createNoValue());
+    measureRepository.addRawMeasure(REF_1, INT_METRIC.getKey(), newMeasureBuilder().createNoValue());
+
+    TestComputationStepContext context = execute();
+
+    assertThatMeasureIsNotPersisted("project-uuid", STRING_METRIC);
+    assertThatMeasureIsNotPersisted("project-uuid", INT_METRIC);
+    assertNbOfInserts(context, 0);
+  }
+
+  @Test
+  public void measures_on_new_code_period_are_persisted() {
+    prepareProject();
+    measureRepository.addRawMeasure(REF_1, INT_METRIC.getKey(), newMeasureBuilder().create(42.0));
+
+    TestComputationStepContext context = execute();
+
+    MeasureDto persistedMeasure = selectMeasure("project-uuid", INT_METRIC).get();
+    assertThat(persistedMeasure.getValue()).isEqualTo(42.0);
+    assertNbOfInserts(context, 1);
+  }
+
+  @Test
+  public void persist_all_measures_of_portfolio_analysis() {
+    preparePortfolio();
+
+    // the computed measures
+    measureRepository.addRawMeasure(REF_1, STRING_METRIC.getKey(), newMeasureBuilder().create("view-value"));
+    measureRepository.addRawMeasure(REF_2, STRING_METRIC.getKey(), newMeasureBuilder().create("subview-value"));
+    measureRepository.addRawMeasure(REF_3, STRING_METRIC.getKey(), newMeasureBuilder().create("project-value"));
+
+    TestComputationStepContext context = execute();
+
+    assertThat(db.countRowsOfTable("project_measures")).isEqualTo(2);
+    assertThat(selectMeasure("view-uuid", STRING_METRIC).get().getData()).isEqualTo("view-value");
+    assertThat(selectMeasure("subview-uuid", STRING_METRIC).get().getData()).isEqualTo("subview-value");
+    assertNbOfInserts(context, 2);
+  }
+
+  private void prepareProject() {
+    // tree of components as defined by scanner report
+    Component project = ReportComponent.builder(PROJECT, REF_1).setUuid("project-uuid")
+      .addChildren(
+        ReportComponent.builder(DIRECTORY, REF_3).setUuid("dir-uuid")
+          .addChildren(
+            ReportComponent.builder(FILE, REF_4).setUuid("file-uuid")
+              .build())
+          .build())
+      .build();
+    treeRootHolder.setRoot(project);
+
+    // components as persisted in db
+    ComponentDto projectDto = insertComponent("project-key", "project-uuid");
+    ComponentDto dirDto = insertComponent("dir-key", "dir-uuid");
+    ComponentDto fileDto = insertComponent("file-key", "file-uuid");
+    db.components().insertSnapshot(projectDto, s -> s.setUuid(ANALYSIS_UUID));
+  }
+
+  private void preparePortfolio() {
+    // tree of components
+    Component portfolio = ViewsComponent.builder(VIEW, REF_1).setUuid("view-uuid")
+      .addChildren(
+        ViewsComponent.builder(SUBVIEW, REF_2).setUuid("subview-uuid")
+          .addChildren(
+            ViewsComponent.builder(PROJECT_VIEW, REF_3).setUuid("project-uuid")
+              .build())
+          .build())
+      .build();
+    treeRootHolder.setRoot(portfolio);
+
+    // components as persisted in db
+    ComponentDto viewDto = insertComponent("view-key", "view-uuid");
+    ComponentDto subViewDto = insertComponent("subview-key", "subview-uuid");
+    ComponentDto projectDto = insertComponent("project-key", "project-uuid");
+    db.components().insertSnapshot(viewDto, s -> s.setUuid(ANALYSIS_UUID));
+  }
+
+  private void assertThatMeasureIsNotPersisted(String componentUuid, Metric metric) {
+    assertThat(selectMeasure(componentUuid, metric)).isEmpty();
+  }
+
+  private void assertThatMeasuresAreNotPersisted(String componentUuid) {
+    assertThatMeasureIsNotPersisted(componentUuid, STRING_METRIC);
+    assertThatMeasureIsNotPersisted(componentUuid, INT_METRIC);
+  }
+
+  private TestComputationStepContext execute() {
+    TestComputationStepContext context = new TestComputationStepContext();
+    new PersistMeasuresStep(dbClient, metricRepository, new MeasureToMeasureDto(analysisMetadataHolder, treeRootHolder), treeRootHolder, measureRepository)
+      .execute(context);
+    return context;
+  }
+
+  private Optional<MeasureDto> selectMeasure(String componentUuid, Metric metric) {
+    return dbClient.measureDao().selectMeasure(db.getSession(), ANALYSIS_UUID, componentUuid, metric.getKey());
+  }
+
+  private ComponentDto insertComponent(String key, String uuid) {
+    ComponentDto componentDto = new ComponentDto()
+      .setKey(key)
+      .setUuid(uuid)
+      .setUuidPath(uuid + ".")
+      .setBranchUuid(uuid);
+    db.components().insertComponent(componentDto);
+    return componentDto;
+  }
+
+  private static void assertNbOfInserts(TestComputationStepContext context, int expected) {
+    context.getStatistics().assertValue("inserts", expected);
+  }
+
+  @Override
+  protected ComputationStep step() {
+    return new PersistMeasuresStep(dbClient, metricRepository, new MeasureToMeasureDto(analysisMetadataHolder, treeRootHolder), treeRootHolder, measureRepository);
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistProjectLinksStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistProjectLinksStepIT.java
new file mode 100644 (file)
index 0000000..2537a63
--- /dev/null
@@ -0,0 +1,258 @@
+/*
+ * 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 org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mockito;
+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.ce.task.projectanalysis.batch.BatchReportReader;
+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.TreeRootHolder;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
+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.UuidFactoryFast;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ProjectLinkDto;
+import org.sonar.scanner.protocol.output.ScannerReport;
+import org.sonar.scanner.protocol.output.ScannerReport.Component.ComponentType;
+
+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.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.scanner.protocol.output.ScannerReport.ComponentLink.ComponentLinkType.CI;
+import static org.sonar.scanner.protocol.output.ScannerReport.ComponentLink.ComponentLinkType.HOME;
+import static org.sonar.scanner.protocol.output.ScannerReport.ComponentLink.ComponentLinkType.ISSUE;
+import static org.sonar.scanner.protocol.output.ScannerReport.ComponentLink.ComponentLinkType.SCM;
+
+public class PersistProjectLinksStepIT extends BaseStepTest {
+
+  @Rule
+  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+  @Rule
+  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
+
+  private PersistProjectLinksStep underTest = new PersistProjectLinksStep(analysisMetadataHolder, db.getDbClient(), treeRootHolder, reportReader, UuidFactoryFast.getInstance());
+
+  @Override
+  protected ComputationStep step() {
+    return underTest;
+  }
+
+  @Test
+  public void no_effect_if_branch_is_not_main() {
+    DbClient dbClient = mock(DbClient.class);
+    TreeRootHolder treeRootHolder = mock(TreeRootHolder.class);
+    BatchReportReader reportReader = mock(BatchReportReader.class);
+    UuidFactory uuidFactory = mock(UuidFactory.class);
+    mockBranch(false);
+    PersistProjectLinksStep underTest = new PersistProjectLinksStep(analysisMetadataHolder, dbClient, treeRootHolder, reportReader, uuidFactory);
+
+    underTest.execute(new TestComputationStepContext());
+
+    verifyNoInteractions(uuidFactory, reportReader, treeRootHolder, dbClient);
+  }
+
+  @Test
+  public void add_links_on_project() {
+    mockBranch(true);
+    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").build());
+
+    // project
+    reportReader.putComponent(ScannerReport.Component.newBuilder()
+      .setRef(1)
+      .setType(ComponentType.PROJECT)
+      .addChildRef(2)
+      .addLink(ScannerReport.ComponentLink.newBuilder().setType(HOME).setHref("http://www.sonarqube.org").build())
+      .addLink(ScannerReport.ComponentLink.newBuilder().setType(SCM).setHref("https://github.com/SonarSource/sonar").build())
+      .addLink(ScannerReport.ComponentLink.newBuilder().setType(ISSUE).setHref("http://jira.sonarsource.com/").build())
+      .addLink(ScannerReport.ComponentLink.newBuilder().setType(CI).setHref("http://bamboo.ci.codehaus.org/browse/SONAR").build())
+      .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(db.getDbClient().projectLinkDao().selectByProjectUuid(db.getSession(), "ABCD"))
+      .extracting(ProjectLinkDto::getType, ProjectLinkDto::getHref, ProjectLinkDto::getName)
+      .containsExactlyInAnyOrder(
+        tuple("homepage", "http://www.sonarqube.org", null),
+        tuple("scm", "https://github.com/SonarSource/sonar", null),
+        tuple("issue", "http://jira.sonarsource.com/", null),
+        tuple("ci", "http://bamboo.ci.codehaus.org/browse/SONAR", null));
+  }
+
+  @Test
+  public void nothing_to_do_when_link_already_exists() {
+    mockBranch(true);
+    ComponentDto project = db.components().insertPrivateProject(p -> p.setUuid("ABCD"));
+    db.componentLinks().insertProvidedLink(project, l -> l.setType("homepage").setName("Home").setHref("http://www.sonarqube.org"));
+
+    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").build());
+
+    reportReader.putComponent(ScannerReport.Component.newBuilder()
+      .setRef(1)
+      .setType(ComponentType.PROJECT)
+      .addLink(ScannerReport.ComponentLink.newBuilder().setType(HOME).setHref("http://www.sonarqube.org").build())
+      .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(db.getDbClient().projectLinkDao().selectByProjectUuid(db.getSession(), "ABCD"))
+      .extracting(ProjectLinkDto::getType, ProjectLinkDto::getHref)
+      .containsExactlyInAnyOrder(tuple("homepage", "http://www.sonarqube.org"));
+  }
+
+  @Test
+  public void do_not_add_links_on_module() {
+    mockBranch(true);
+    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").build());
+    reportReader.putComponent(ScannerReport.Component.newBuilder()
+      .setRef(1)
+      .setType(ComponentType.PROJECT)
+      .addChildRef(2)
+      .build());
+    reportReader.putComponent(ScannerReport.Component.newBuilder()
+      .setRef(2)
+      .setType(ComponentType.MODULE)
+      .addLink(ScannerReport.ComponentLink.newBuilder().setType(HOME).setHref("http://www.sonarqube.org").build())
+      .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(db.countRowsOfTable("project_links")).isZero();
+  }
+
+  @Test
+  public void do_not_add_links_on_file() {
+    mockBranch(true);
+    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").addChildren(
+      ReportComponent.builder(Component.Type.FILE, 2).setUuid("BCDE").build())
+      .build());
+
+    reportReader.putComponent(ScannerReport.Component.newBuilder()
+      .setRef(1)
+      .setType(ComponentType.PROJECT)
+      .addChildRef(2)
+      .build());
+    reportReader.putComponent(ScannerReport.Component.newBuilder()
+      .setRef(2)
+      .setType(ComponentType.FILE)
+      .addLink(ScannerReport.ComponentLink.newBuilder().setType(HOME).setHref("http://www.sonarqube.org").build())
+      .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(db.countRowsOfTable("project_links")).isZero();
+  }
+
+  @Test
+  public void update_link() {
+    mockBranch(true);
+    ComponentDto project = db.components().insertPrivateProject(p -> p.setUuid("ABCD"));
+    db.componentLinks().insertProvidedLink(project, l -> l.setType("homepage").setName("Home").setHref("http://www.sonar.org"));
+
+    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").build());
+
+    reportReader.putComponent(ScannerReport.Component.newBuilder()
+      .setRef(1)
+      .setType(ComponentType.PROJECT)
+      .addLink(ScannerReport.ComponentLink.newBuilder().setType(HOME).setHref("http://www.sonarqube.org").build())
+      .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(db.getDbClient().projectLinkDao().selectByProjectUuid(db.getSession(), "ABCD"))
+      .extracting(ProjectLinkDto::getType, ProjectLinkDto::getHref)
+      .containsExactlyInAnyOrder(tuple("homepage", "http://www.sonarqube.org"));
+  }
+
+  @Test
+  public void delete_link() {
+    mockBranch(true);
+    ComponentDto project = db.components().insertPrivateProject(p -> p.setUuid("ABCD"));
+    db.componentLinks().insertProvidedLink(project, l -> l.setType("homepage").setName("Home").setHref("http://www.sonar.org"));
+
+    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").build());
+
+    reportReader.putComponent(ScannerReport.Component.newBuilder()
+      .setRef(1)
+      .setType(ComponentType.PROJECT)
+      .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(db.countRowsOfTable("project_links")).isZero();
+  }
+
+  @Test
+  public void not_delete_custom_link() {
+    mockBranch(true);
+    ComponentDto project = db.components().insertPrivateProject(p -> p.setUuid("ABCD"));
+    db.componentLinks().insertCustomLink(project);
+
+    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").build());
+
+    reportReader.putComponent(ScannerReport.Component.newBuilder()
+      .setRef(1)
+      .setType(ComponentType.PROJECT)
+      .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(db.countRowsOfTable("project_links")).isOne();
+  }
+
+  @Test
+  public void fail_when_trying_to_add_same_link_type_multiple_times() {
+    mockBranch(true);
+    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").build());
+
+    reportReader.putComponent(ScannerReport.Component.newBuilder()
+      .setRef(1)
+      .setType(ComponentType.PROJECT)
+      .addLink(ScannerReport.ComponentLink.newBuilder().setType(HOME).setHref("http://www.sonarqube.org").build())
+      .addLink(ScannerReport.ComponentLink.newBuilder().setType(HOME).setHref("http://www.sonarqube.org").build())
+      .build());
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Link of type 'homepage' has already been declared on component 'ABCD'");
+  }
+
+  private void mockBranch(boolean isMain) {
+    Branch branch = Mockito.mock(Branch.class);
+    when(branch.isMain()).thenReturn(isMain);
+    analysisMetadataHolder.setBranch(branch);
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistPushEventsStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistPushEventsStepIT.java
new file mode 100644 (file)
index 0000000..6973956
--- /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.step;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.Optional;
+import java.util.stream.IntStream;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.impl.utils.TestSystem2;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.RuleType;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.MutableTreeRootHolderRule;
+import org.sonar.ce.task.projectanalysis.component.ReportComponent;
+import org.sonar.ce.task.projectanalysis.issue.ProtoIssueCache;
+import org.sonar.ce.task.projectanalysis.pushevent.PushEventFactory;
+import org.sonar.ce.task.step.ComputationStep;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.db.DbTester;
+import org.sonar.db.pushevent.PushEventDto;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class PersistPushEventsStepIT {
+
+  private final TestSystem2 system2 = new TestSystem2().setNow(1L);
+
+  @Rule
+  public DbTester db = DbTester.create(system2);
+
+  public final PushEventFactory pushEventFactory = mock(PushEventFactory.class);
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+  @Rule
+  public MutableTreeRootHolderRule treeRootHolder = new MutableTreeRootHolderRule();
+  private ProtoIssueCache protoIssueCache;
+  private PersistPushEventsStep underTest;
+
+  @Before
+  public void before() throws IOException {
+    protoIssueCache = new ProtoIssueCache(temp.newFile(), System2.INSTANCE);
+    buildComponentTree();
+    underTest = new PersistPushEventsStep(db.getDbClient(), protoIssueCache, pushEventFactory, treeRootHolder);
+  }
+
+  @Test
+  public void description() {
+    assertThat(underTest.getDescription()).isEqualTo("Publishing taint vulnerabilities events");
+  }
+
+  @Test
+  public void do_nothing_if_no_issues() {
+    underTest.execute(mock(ComputationStep.Context.class));
+
+    assertThat(db.countSql(db.getSession(), "SELECT count(uuid) FROM push_events")).isZero();
+  }
+
+  @Test
+  public void resilient_to_failure() {
+    protoIssueCache.newAppender().append(
+      createIssue("key1").setType(RuleType.VULNERABILITY))
+      .close();
+
+    when(pushEventFactory.raiseEventOnIssue(any(), any())).thenThrow(new RuntimeException("I have a bad feelings about this"));
+
+    assertThatCode(() -> underTest.execute(mock(ComputationStep.Context.class)))
+      .doesNotThrowAnyException();
+
+    assertThat(db.countSql(db.getSession(), "SELECT count(uuid) FROM push_events")).isZero();
+  }
+
+  @Test
+  public void skip_persist_if_no_push_events() {
+    protoIssueCache.newAppender().append(
+      createIssue("key1").setType(RuleType.VULNERABILITY))
+      .close();
+
+    underTest.execute(mock(ComputationStep.Context.class));
+
+    assertThat(db.countSql(db.getSession(), "SELECT count(uuid) FROM push_events")).isZero();
+  }
+
+  @Test
+  public void do_nothing_if_issue_does_not_have_component() {
+    protoIssueCache.newAppender().append(
+      createIssue("key1").setType(RuleType.VULNERABILITY)
+        .setComponentUuid(null))
+      .close();
+
+    underTest.execute(mock(ComputationStep.Context.class));
+
+    assertThat(db.countSql(db.getSession(), "SELECT count(uuid) FROM push_events")).isZero();
+  }
+
+  @Test
+  public void store_push_events() {
+    protoIssueCache.newAppender()
+      .append(createIssue("key1").setType(RuleType.VULNERABILITY)
+        .setComponentUuid("cu1")
+        .setComponentKey("ck1"))
+      .append(createIssue("key2").setType(RuleType.VULNERABILITY)
+        .setComponentUuid("cu2")
+        .setComponentKey("ck2"))
+      .close();
+
+    when(pushEventFactory.raiseEventOnIssue(eq("uuid_1"), any(DefaultIssue.class))).thenReturn(
+      Optional.of(createPushEvent()),
+      Optional.of(createPushEvent()));
+
+    underTest.execute(mock(ComputationStep.Context.class));
+
+    assertThat(db.countSql(db.getSession(), "SELECT count(uuid) FROM push_events")).isEqualTo(2);
+  }
+
+  @Test
+  public void store_push_events_for_branch() {
+    var project = db.components().insertPrivateProject();
+    db.components().insertProjectBranch(project, b -> b.setUuid("uuid_1"));
+
+    protoIssueCache.newAppender()
+      .append(createIssue("key1").setType(RuleType.VULNERABILITY)
+        .setComponentUuid("cu1")
+        .setComponentKey("ck1"))
+      .append(createIssue("key2").setType(RuleType.VULNERABILITY)
+        .setComponentUuid("cu2")
+        .setComponentKey("ck2"))
+      .close();
+
+    when(pushEventFactory.raiseEventOnIssue(eq(project.uuid()), any(DefaultIssue.class))).thenReturn(
+      Optional.of(createPushEvent()),
+      Optional.of(createPushEvent()));
+
+    underTest.execute(mock(ComputationStep.Context.class));
+
+    assertThat(db.countSql(db.getSession(), "SELECT count(uuid) FROM push_events")).isEqualTo(2);
+  }
+
+  @Test
+  public void store_push_events_in_batches() {
+    var appender = protoIssueCache.newAppender();
+
+    IntStream.range(1, 252)
+      .forEach(value -> {
+        var defaultIssue = createIssue("key-" + value).setType(RuleType.VULNERABILITY)
+          .setComponentUuid("cu" + value)
+          .setComponentKey("ck" + value);
+        appender.append(defaultIssue);
+        when(pushEventFactory.raiseEventOnIssue(anyString(), eq(defaultIssue))).thenReturn(Optional.of(createPushEvent()));
+      });
+
+    appender.close();
+
+    underTest.execute(mock(ComputationStep.Context.class));
+
+    assertThat(db.countSql(db.getSession(), "SELECT count(uuid) FROM push_events")).isEqualTo(251);
+  }
+
+  private DefaultIssue createIssue(String key) {
+    return new DefaultIssue()
+      .setKey(key)
+      .setProjectKey("p")
+      .setStatus("OPEN")
+      .setProjectUuid("project-uuid")
+      .setComponentKey("c")
+      .setRuleKey(RuleKey.of("r", "r"))
+      .setCreationDate(new Date());
+  }
+
+  private PushEventDto createPushEvent() {
+    return new PushEventDto().setProjectUuid("project-uuid").setName("event").setPayload("test".getBytes(UTF_8));
+  }
+
+  private void buildComponentTree() {
+    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1)
+      .setUuid("uuid_1")
+      .addChildren(ReportComponent.builder(Component.Type.FILE, 2)
+        .setUuid("issue-component-uuid")
+        .build())
+      .addChildren(ReportComponent.builder(Component.Type.FILE, 3)
+        .setUuid("location-component-uuid")
+        .build())
+      .build());
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistScannerAnalysisCacheStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistScannerAnalysisCacheStepIT.java
new file mode 100644 (file)
index 0000000..b550160
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * 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.io.ByteArrayInputStream;
+import java.io.IOException;
+import org.apache.commons.io.IOUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
+import org.sonar.ce.task.step.ComputationStep;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbInputStream;
+import org.sonar.db.DbTester;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class PersistScannerAnalysisCacheStepIT {
+  @Rule
+  public BatchReportReaderRule reader = new BatchReportReaderRule();
+  @Rule
+  public DbTester dbTester = DbTester.create();
+  private final DbClient client = dbTester.getDbClient();
+  private final TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+  private final PersistScannerAnalysisCacheStep step = new PersistScannerAnalysisCacheStep(reader, dbTester.getDbClient(), treeRootHolder);
+
+  @Test
+  public void inserts_cache() throws IOException {
+    reader.setAnalysisCache("test".getBytes(UTF_8));
+
+    Component root = mock(Component.class);
+    when(root.getUuid()).thenReturn("branch");
+    treeRootHolder.setRoot(root);
+
+    step.execute(mock(ComputationStep.Context.class));
+    assertThat(dbTester.countRowsOfTable("scanner_analysis_cache")).isOne();
+    try (DbInputStream data = client.scannerAnalysisCacheDao().selectData(dbTester.getSession(), "branch")) {
+      assertThat(IOUtils.toString(data, UTF_8)).isEqualTo("test");
+    }
+  }
+
+  @Test
+  public void updates_cache() throws IOException {
+    client.scannerAnalysisCacheDao().insert(dbTester.getSession(), "branch", new ByteArrayInputStream("test".getBytes(UTF_8)));
+    inserts_cache();
+  }
+
+  @Test
+  public void do_nothing_if_no_analysis_cache() {
+    step.execute(mock(ComputationStep.Context.class));
+    assertThat(dbTester.countRowsOfTable("scanner_analysis_cache")).isZero();
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistScannerContextStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistScannerContextStepIT.java
new file mode 100644 (file)
index 0000000..d85d967
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * 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 org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.CeTask;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.core.util.CloseableIterator;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class PersistScannerContextStepIT {
+  private static final String ANALYSIS_UUID = "UUID";
+
+  @ClassRule
+  public static final DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  @Rule
+  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
+  @Rule
+  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule()
+    .setUuid(ANALYSIS_UUID);
+
+  private DbClient dbClient = dbTester.getDbClient();
+  private CeTask ceTask = mock(CeTask.class);
+  private PersistScannerContextStep underTest = new PersistScannerContextStep(reportReader, dbClient, ceTask);
+
+  @Test
+  public void getDescription() {
+    assertThat(underTest.getDescription()).isEqualTo("Persist scanner context");
+  }
+
+  @Test
+  public void executes_persist_lines_of_reportReader() {
+    String taskUuid = "task uuid";
+    when(ceTask.getUuid()).thenReturn(taskUuid);
+    reportReader.setScannerLogs(asList("log1", "log2"));
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbClient.ceScannerContextDao().selectScannerContext(dbTester.getSession(), taskUuid))
+      .contains("log1" + '\n' + "log2");
+  }
+
+  @Test
+  public void executes_persist_does_not_persist_any_scanner_context_if_iterator_is_empty() {
+    reportReader.setScannerLogs(emptyList());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbClient.ceScannerContextDao().selectScannerContext(dbTester.getSession(), ANALYSIS_UUID))
+      .isEmpty();
+  }
+
+  /**
+   * SONAR-8306
+   */
+  @Test
+  public void execute_does_not_fail_if_scanner_context_has_already_been_persisted() {
+    dbClient.ceScannerContextDao().insert(dbTester.getSession(), ANALYSIS_UUID, CloseableIterator.from(Arrays.asList("a", "b", "c").iterator()));
+    dbTester.commit();
+    reportReader.setScannerLogs(asList("1", "2", "3"));
+    when(ceTask.getUuid()).thenReturn(ANALYSIS_UUID);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbClient.ceScannerContextDao().selectScannerContext(dbTester.getSession(), ANALYSIS_UUID))
+      .contains("1" + '\n' + "2" + '\n' + "3");
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ReportPersistAnalysisStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ReportPersistAnalysisStepIT.java
new file mode 100644 (file)
index 0000000..0bdeef1
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+ * 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.List;
+import java.util.Optional;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.DateUtils;
+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.period.Period;
+import org.sonar.ce.task.projectanalysis.period.PeriodHolderRule;
+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.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.component.SnapshotQuery;
+import org.sonar.db.component.SnapshotTesting;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ReportPersistAnalysisStepIT extends BaseStepTest {
+
+  private static final String PROJECT_KEY = "PROJECT_KEY";
+  private static final String ANALYSIS_UUID = "U1";
+  private static final String REVISION_ID = "5f6432a1";
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+  @Rule
+  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
+  @Rule
+  public PeriodHolderRule periodsHolder = new PeriodHolderRule();
+
+  private System2 system2 = mock(System2.class);
+  private DbClient dbClient = dbTester.getDbClient();
+  private long analysisDate;
+  private long now;
+  private PersistAnalysisStep underTest;
+
+  @Before
+  public void setup() {
+    analysisDate = DateUtils.parseDateQuietly("2015-06-01").getTime();
+    analysisMetadataHolder.setUuid(ANALYSIS_UUID);
+    analysisMetadataHolder.setAnalysisDate(analysisDate);
+    analysisMetadataHolder.setScmRevision(REVISION_ID);
+
+    now = DateUtils.parseDateQuietly("2015-06-02").getTime();
+
+    when(system2.now()).thenReturn(now);
+
+    underTest = new PersistAnalysisStep(system2, dbClient, treeRootHolder, analysisMetadataHolder, periodsHolder);
+
+    // initialize PeriodHolder to empty by default
+    periodsHolder.setPeriod(null);
+  }
+
+  @Override
+  protected ComputationStep step() {
+    return underTest;
+  }
+
+  @Test
+  public void persist_analysis() {
+    String projectVersion = randomAlphabetic(10);
+    ComponentDto projectDto = ComponentTesting.newPrivateProjectDto("ABCD").setKey(PROJECT_KEY).setName("Project");
+    dbTester.components().insertComponent(projectDto);
+    ComponentDto directoryDto = ComponentTesting.newDirectory(projectDto, "CDEF", "src/main/java/dir").setKey("PROJECT_KEY:src/main/java/dir");
+    dbTester.components().insertComponent(directoryDto);
+    ComponentDto fileDto = ComponentTesting.newFileDto(projectDto, directoryDto, "DEFG").setKey("PROJECT_KEY:src/main/java/dir/Foo.java");
+    dbTester.components().insertComponent(fileDto);
+    dbTester.getSession().commit();
+
+    Component file = ReportComponent.builder(Component.Type.FILE, 3).setUuid("DEFG").setKey("PROJECT_KEY:src/main/java/dir/Foo.java").build();
+    Component directory = ReportComponent.builder(Component.Type.DIRECTORY, 2).setUuid("CDEF").setKey("PROJECT_KEY:src/main/java/dir").addChildren(file).build();
+    String buildString = Optional.ofNullable(projectVersion).map(v -> randomAlphabetic(7)).orElse(null);
+    Component project = ReportComponent.builder(Component.Type.PROJECT, 1)
+      .setUuid("ABCD")
+      .setKey(PROJECT_KEY)
+      .setProjectVersion(projectVersion)
+      .setBuildString(buildString)
+      .setScmRevisionId(REVISION_ID)
+      .addChildren(directory)
+      .build();
+    treeRootHolder.setRoot(project);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbTester.countRowsOfTable("snapshots")).isOne();
+
+    SnapshotDto projectSnapshot = getUnprocessedSnapshot(projectDto.uuid());
+    assertThat(projectSnapshot.getUuid()).isEqualTo(ANALYSIS_UUID);
+    assertThat(projectSnapshot.getComponentUuid()).isEqualTo(project.getUuid());
+    assertThat(projectSnapshot.getProjectVersion()).isEqualTo(projectVersion);
+    assertThat(projectSnapshot.getBuildString()).isEqualTo(buildString);
+    assertThat(projectSnapshot.getLast()).isFalse();
+    assertThat(projectSnapshot.getStatus()).isEqualTo("U");
+    assertThat(projectSnapshot.getCreatedAt()).isEqualTo(analysisDate);
+    assertThat(projectSnapshot.getBuildDate()).isEqualTo(now);
+    assertThat(projectSnapshot.getRevision()).isEqualTo(REVISION_ID);
+  }
+
+  @Test
+  public void persist_snapshots_with_new_code_period() {
+    ComponentDto projectDto = ComponentTesting.newPrivateProjectDto("ABCD").setKey(PROJECT_KEY).setName("Project");
+    dbTester.components().insertComponent(projectDto);
+    SnapshotDto snapshotDto = SnapshotTesting.newAnalysis(projectDto).setCreatedAt(DateUtils.parseDateQuietly("2015-01-01").getTime());
+    dbClient.snapshotDao().insert(dbTester.getSession(), snapshotDto);
+    dbTester.getSession().commit();
+    periodsHolder.setPeriod(new Period("NUMBER_OF_DAYS", "10", analysisDate));
+
+    Component project = ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").setKey(PROJECT_KEY).build();
+    treeRootHolder.setRoot(project);
+
+    underTest.execute(new TestComputationStepContext());
+
+    SnapshotDto projectSnapshot = getUnprocessedSnapshot(projectDto.uuid());
+    assertThat(projectSnapshot.getPeriodMode()).isEqualTo("NUMBER_OF_DAYS");
+    assertThat(projectSnapshot.getPeriodDate()).isEqualTo(analysisDate);
+    assertThat(projectSnapshot.getPeriodModeParameter()).isNotNull();
+  }
+
+  @Test
+  public void only_persist_snapshots_with_new_code_period_on_project_and_module() {
+    periodsHolder.setPeriod(new Period("PREVIOUS_VERSION", null, analysisDate));
+
+    ComponentDto projectDto = ComponentTesting.newPrivateProjectDto("ABCD").setKey(PROJECT_KEY).setName("Project");
+    dbTester.components().insertComponent(projectDto);
+    SnapshotDto projectSnapshot = SnapshotTesting.newAnalysis(projectDto);
+    dbClient.snapshotDao().insert(dbTester.getSession(), projectSnapshot);
+
+    ComponentDto directoryDto = ComponentTesting.newDirectory(projectDto, "CDEF", "MODULE_KEY:src/main/java/dir").setKey("MODULE_KEY:src/main/java/dir");
+    dbTester.components().insertComponent(directoryDto);
+
+    ComponentDto fileDto = ComponentTesting.newFileDto(projectDto, directoryDto, "DEFG").setKey("MODULE_KEY:src/main/java/dir/Foo.java");
+    dbTester.components().insertComponent(fileDto);
+
+    dbTester.getSession().commit();
+
+    Component file = ReportComponent.builder(Component.Type.FILE, 3).setUuid("DEFG").setKey("MODULE_KEY:src/main/java/dir/Foo.java").build();
+    Component directory = ReportComponent.builder(Component.Type.DIRECTORY, 2).setUuid("CDEF").setKey("MODULE_KEY:src/main/java/dir").addChildren(file).build();
+    Component project = ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").setKey(PROJECT_KEY).addChildren(directory).build();
+    treeRootHolder.setRoot(project);
+
+    underTest.execute(new TestComputationStepContext());
+
+    SnapshotDto newProjectSnapshot = getUnprocessedSnapshot(projectDto.uuid());
+    assertThat(newProjectSnapshot.getPeriodMode()).isEqualTo("PREVIOUS_VERSION");
+  }
+
+  @Test
+  public void set_no_period_on_snapshots_when_no_period() {
+    ComponentDto projectDto = ComponentTesting.newPrivateProjectDto("ABCD").setKey(PROJECT_KEY).setName("Project");
+    dbTester.components().insertComponent(projectDto);
+    SnapshotDto snapshotDto = SnapshotTesting.newAnalysis(projectDto);
+    dbClient.snapshotDao().insert(dbTester.getSession(), snapshotDto);
+    dbTester.getSession().commit();
+
+    Component project = ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").setKey(PROJECT_KEY).build();
+    treeRootHolder.setRoot(project);
+
+    underTest.execute(new TestComputationStepContext());
+
+    SnapshotDto projectSnapshot = getUnprocessedSnapshot(projectDto.uuid());
+    assertThat(projectSnapshot.getPeriodMode()).isNull();
+    assertThat(projectSnapshot.getPeriodDate()).isNull();
+    assertThat(projectSnapshot.getPeriodModeParameter()).isNull();
+  }
+
+  private SnapshotDto getUnprocessedSnapshot(String componentUuid) {
+    List<SnapshotDto> projectSnapshots = dbClient.snapshotDao().selectAnalysesByQuery(dbTester.getSession(),
+      new SnapshotQuery().setComponentUuid(componentUuid).setIsLast(false).setStatus(SnapshotDto.STATUS_UNPROCESSED));
+    assertThat(projectSnapshots).hasSize(1);
+    return projectSnapshots.get(0);
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ReportPersistComponentsStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ReportPersistComponentsStepIT.java
new file mode 100644 (file)
index 0000000..1a4c4c0
--- /dev/null
@@ -0,0 +1,522 @@
+/*
+ * 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.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.ce.task.projectanalysis.analysis.TestBranch;
+import org.sonar.ce.task.projectanalysis.component.BranchPersister;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.DefaultBranchImpl;
+import org.sonar.ce.task.projectanalysis.component.FileAttributes;
+import org.sonar.ce.task.projectanalysis.component.MutableDisabledComponentsHolder;
+import org.sonar.ce.task.projectanalysis.component.ProjectPersister;
+import org.sonar.ce.task.projectanalysis.component.ReportComponent;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
+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.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.server.project.Project;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+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.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME;
+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.ComponentTesting.newDirectory;
+
+public class ReportPersistComponentsStepIT extends BaseStepTest {
+
+  private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
+  private static final String PROJECT_KEY = "PROJECT_KEY";
+
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+  @Rule
+  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
+
+  private final System2 system2 = mock(System2.class);
+  private final DbClient dbClient = db.getDbClient();
+  private Date now;
+  private final MutableDisabledComponentsHolder disabledComponentsHolder = mock(MutableDisabledComponentsHolder.class, RETURNS_DEEP_STUBS);
+  private PersistComponentsStep underTest;
+
+  @Before
+  public void setup() throws Exception {
+    now = DATE_FORMAT.parse("2015-06-02");
+    when(system2.now()).thenReturn(now.getTime());
+
+    BranchPersister branchPersister = mock(BranchPersister.class);
+    ProjectPersister projectPersister = mock(ProjectPersister.class);
+    underTest = new PersistComponentsStep(dbClient, treeRootHolder, system2, disabledComponentsHolder, analysisMetadataHolder, branchPersister, projectPersister);
+  }
+
+  @Override
+  protected ComputationStep step() {
+    return underTest;
+  }
+
+  @Test
+  public void persist_components() {
+    ComponentDto projectDto = prepareProject();
+    Component file = builder(FILE, 4).setUuid("DEFG").setKey("PROJECT_KEY:src/main/java/dir/Foo.java")
+      .setName("src/main/java/dir/Foo.java")
+      .setShortName("Foo.java")
+      .setFileAttributes(new FileAttributes(false, "java", 1))
+      .build();
+    Component directory = builder(DIRECTORY, 3).setUuid("CDEF").setKey("PROJECT_KEY:src/main/java/dir")
+      .setName("src/main/java/dir")
+      .setShortName("dir")
+      .addChildren(file)
+      .build();
+    Component treeRoot = asTreeRoot(projectDto)
+      .addChildren(directory)
+      .build();
+    treeRootHolder.setRoot(treeRoot);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(db.countRowsOfTable("components")).isEqualTo(3);
+
+    ComponentDto directoryDto = dbClient.componentDao().selectByKey(db.getSession(), "PROJECT_KEY:src/main/java/dir").get();
+    assertThat(directoryDto.name()).isEqualTo("dir");
+    assertThat(directoryDto.longName()).isEqualTo("src/main/java/dir");
+    assertThat(directoryDto.description()).isNull();
+    assertThat(directoryDto.path()).isEqualTo("src/main/java/dir");
+    assertThat(directoryDto.uuid()).isEqualTo("CDEF");
+    assertThat(directoryDto.getUuidPath()).isEqualTo(UUID_PATH_SEPARATOR + projectDto.uuid() + UUID_PATH_SEPARATOR);
+    assertThat(directoryDto.getMainBranchProjectUuid()).isNull();
+    assertThat(directoryDto.branchUuid()).isEqualTo(projectDto.uuid());
+    assertThat(directoryDto.qualifier()).isEqualTo("DIR");
+    assertThat(directoryDto.scope()).isEqualTo("DIR");
+    assertThat(directoryDto.getCreatedAt()).isEqualTo(now);
+
+    ComponentDto fileDto = dbClient.componentDao().selectByKey(db.getSession(), "PROJECT_KEY:src/main/java/dir/Foo.java").get();
+    assertThat(fileDto.name()).isEqualTo("Foo.java");
+    assertThat(fileDto.longName()).isEqualTo("src/main/java/dir/Foo.java");
+    assertThat(fileDto.description()).isNull();
+    assertThat(fileDto.path()).isEqualTo("src/main/java/dir/Foo.java");
+    assertThat(fileDto.language()).isEqualTo("java");
+    assertThat(fileDto.uuid()).isEqualTo("DEFG");
+    assertThat(fileDto.getUuidPath()).isEqualTo(directoryDto.getUuidPath() + directoryDto.uuid() + UUID_PATH_SEPARATOR);
+    assertThat(fileDto.getMainBranchProjectUuid()).isNull();
+    assertThat(fileDto.branchUuid()).isEqualTo(projectDto.uuid());
+    assertThat(fileDto.qualifier()).isEqualTo("FIL");
+    assertThat(fileDto.scope()).isEqualTo("FIL");
+    assertThat(fileDto.getCreatedAt()).isEqualTo(now);
+  }
+
+  @Test
+  public void persist_components_of_existing_branch() {
+    ComponentDto branch = prepareBranch("feature/foo");
+    Component file = builder(FILE, 4).setUuid("DEFG").setKey("PROJECT_KEY:src/main/java/dir/Foo.java")
+      .setName("src/main/java/dir/Foo.java")
+      .setShortName("Foo.java")
+      .setFileAttributes(new FileAttributes(false, "java", 1))
+      .build();
+    Component directory = builder(DIRECTORY, 3).setUuid("CDEF")
+      .setKey("PROJECT_KEY:src/main/java/dir")
+      .setName("src/main/java/dir")
+      .setShortName("dir")
+      .addChildren(file)
+      .build();
+    Component treeRoot = asTreeRoot(branch)
+      .addChildren(directory)
+      .build();
+    treeRootHolder.setRoot(treeRoot);
+
+    underTest.execute(new TestComputationStepContext());
+
+    // 3 in this branch plus the project
+    assertThat(db.countRowsOfTable("components")).isEqualTo(4);
+
+    ComponentDto directoryDto = dbClient.componentDao().selectByKeyAndBranch(db.getSession(), "PROJECT_KEY:src/main/java/dir", "feature/foo").get();
+    assertThat(directoryDto.name()).isEqualTo("dir");
+    assertThat(directoryDto.longName()).isEqualTo("src/main/java/dir");
+    assertThat(directoryDto.description()).isNull();
+    assertThat(directoryDto.path()).isEqualTo("src/main/java/dir");
+    assertThat(directoryDto.uuid()).isEqualTo("CDEF");
+    assertThat(directoryDto.getUuidPath()).isEqualTo(UUID_PATH_SEPARATOR + branch.uuid() + UUID_PATH_SEPARATOR);
+    assertThat(directoryDto.getMainBranchProjectUuid()).isEqualTo(analysisMetadataHolder.getProject().getUuid());
+    assertThat(directoryDto.branchUuid()).isEqualTo(branch.uuid());
+    assertThat(directoryDto.qualifier()).isEqualTo("DIR");
+    assertThat(directoryDto.scope()).isEqualTo("DIR");
+    assertThat(directoryDto.getCreatedAt()).isEqualTo(now);
+
+    ComponentDto fileDto = dbClient.componentDao().selectByKeyAndBranch(db.getSession(), "PROJECT_KEY:src/main/java/dir/Foo.java", "feature/foo").get();
+    assertThat(fileDto.name()).isEqualTo("Foo.java");
+    assertThat(fileDto.longName()).isEqualTo("src/main/java/dir/Foo.java");
+    assertThat(fileDto.description()).isNull();
+    assertThat(fileDto.path()).isEqualTo("src/main/java/dir/Foo.java");
+    assertThat(fileDto.language()).isEqualTo("java");
+    assertThat(fileDto.uuid()).isEqualTo("DEFG");
+    assertThat(fileDto.getUuidPath()).isEqualTo(directoryDto.getUuidPath() + directoryDto.uuid() + UUID_PATH_SEPARATOR);
+    assertThat(fileDto.getMainBranchProjectUuid()).isEqualTo(analysisMetadataHolder.getProject().getUuid());
+    assertThat(fileDto.branchUuid()).isEqualTo(branch.uuid());
+    assertThat(fileDto.qualifier()).isEqualTo("FIL");
+    assertThat(fileDto.scope()).isEqualTo("FIL");
+    assertThat(fileDto.getCreatedAt()).isEqualTo(now);
+  }
+
+  @Test
+  public void persist_file_directly_attached_on_root_directory() {
+    ComponentDto projectDto = prepareProject();
+    treeRootHolder.setRoot(
+      asTreeRoot(projectDto)
+        .addChildren(
+          builder(FILE, 2).setUuid("DEFG").setKey(projectDto.getKey() + ":pom.xml")
+            .setName("pom.xml")
+            .build())
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbClient.componentDao().selectByKey(db.getSession(), projectDto.getKey() + ":/")).isNotPresent();
+
+    ComponentDto file = dbClient.componentDao().selectByKey(db.getSession(), projectDto.getKey() + ":pom.xml").get();
+    assertThat(file.name()).isEqualTo("pom.xml");
+    assertThat(file.path()).isEqualTo("pom.xml");
+  }
+
+  @Test
+  public void persist_unit_test() {
+    ComponentDto projectDto = prepareProject();
+    treeRootHolder.setRoot(
+      asTreeRoot(projectDto)
+        .addChildren(
+          builder(DIRECTORY, 2).setUuid("CDEF").setKey(PROJECT_KEY + ":src/test/java/dir")
+            .setName("src/test/java/dir")
+            .addChildren(
+              builder(FILE, 3).setUuid("DEFG").setKey(PROJECT_KEY + ":src/test/java/dir/FooTest.java")
+                .setName("src/test/java/dir/FooTest.java")
+                .setShortName("FooTest.java")
+                .setFileAttributes(new FileAttributes(true, null, 1))
+                .build())
+            .build())
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    ComponentDto file = dbClient.componentDao().selectByKey(db.getSession(), PROJECT_KEY + ":src/test/java/dir/FooTest.java").get();
+    assertThat(file.name()).isEqualTo("FooTest.java");
+    assertThat(file.longName()).isEqualTo("src/test/java/dir/FooTest.java");
+    assertThat(file.path()).isEqualTo("src/test/java/dir/FooTest.java");
+    assertThat(file.qualifier()).isEqualTo("UTS");
+    assertThat(file.scope()).isEqualTo("FIL");
+  }
+
+  @Test
+  public void update_file_to_directory_change_scope() {
+    ComponentDto project = prepareProject();
+    ComponentDto directory = ComponentTesting.newDirectory(project, "src").setUuid("CDEF").setKey("PROJECT_KEY:src");
+    ComponentDto file = ComponentTesting.newFileDto(project, directory, "DEFG").setPath("src/foo").setName("foo")
+      .setKey("PROJECT_KEY:src/foo");
+    dbClient.componentDao().insert(db.getSession(), directory, file);
+    db.getSession().commit();
+
+    assertThat(dbClient.componentDao().selectByKey(db.getSession(), PROJECT_KEY + ":src/foo").get().scope()).isEqualTo("FIL");
+
+    treeRootHolder.setRoot(
+      asTreeRoot(project)
+        .addChildren(
+          builder(DIRECTORY, 2).setUuid("CDEF").setKey(PROJECT_KEY + ":src")
+            .setName("src")
+            .addChildren(
+              builder(DIRECTORY, 3).setUuid("DEFG").setKey(PROJECT_KEY + ":src/foo")
+                .setName("foo")
+                .addChildren(
+                  builder(FILE, 4).setUuid("HIJK").setKey(PROJECT_KEY + ":src/foo/FooTest.java")
+                    .setName("src/foo/FooTest.java")
+                    .setShortName("FooTest.java")
+                    .setFileAttributes(new FileAttributes(false, null, 1))
+                    .build())
+                .build())
+            .build())
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    // commit the functional transaction
+    dbClient.componentDao().applyBChangesForBranchUuid(db.getSession(), project.uuid());
+    db.commit();
+
+    assertThat(dbClient.componentDao().selectByKey(db.getSession(), PROJECT_KEY + ":src/foo").get().scope()).isEqualTo("DIR");
+  }
+
+  @Test
+  public void persist_only_new_components() {
+    // Project and module already exists
+    ComponentDto project = prepareProject();
+    db.getSession().commit();
+
+    treeRootHolder.setRoot(
+      builder(PROJECT, 1).setUuid(project.uuid()).setKey(project.getKey())
+        .setName("Project")
+        .addChildren(
+          builder(DIRECTORY, 3).setUuid("CDEF").setKey("PROJECT_KEY:src/main/java/dir")
+            .setName("src/main/java/dir")
+            .addChildren(
+              builder(FILE, 4).setUuid("DEFG").setKey("PROJECT_KEY:src/main/java/dir/Foo.java")
+                .setName("src/main/java/dir/Foo.java")
+                .setShortName("Foo.java")
+                .build())
+            .build())
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(db.countRowsOfTable("components")).isEqualTo(3);
+
+    ComponentDto projectReloaded = dbClient.componentDao().selectByKey(db.getSession(), project.getKey()).get();
+    assertThat(projectReloaded.uuid()).isEqualTo(project.uuid());
+    assertThat(projectReloaded.getUuidPath()).isEqualTo(UUID_PATH_OF_ROOT);
+    assertThat(projectReloaded.getMainBranchProjectUuid()).isNull();
+
+    ComponentDto directory = dbClient.componentDao().selectByKey(db.getSession(), "PROJECT_KEY:src/main/java/dir").get();
+    assertThat(directory.getUuidPath()).isEqualTo(directory.getUuidPath());
+    assertThat(directory.branchUuid()).isEqualTo(project.uuid());
+    assertThat(directory.getMainBranchProjectUuid()).isNull();
+
+    ComponentDto file = dbClient.componentDao().selectByKey(db.getSession(), "PROJECT_KEY:src/main/java/dir/Foo.java").get();
+    assertThat(file.getUuidPath()).isEqualTo(file.getUuidPath());
+    assertThat(file.branchUuid()).isEqualTo(project.uuid());
+    assertThat(file.getMainBranchProjectUuid()).isNull();
+  }
+
+  @Test
+  public void nothing_to_persist() {
+    ComponentDto project = prepareProject();
+    ComponentDto directory = ComponentTesting.newDirectory(project, "src/main/java/dir").setUuid("CDEF").setKey("PROJECT_KEY:src/main/java/dir");
+    ComponentDto file = ComponentTesting.newFileDto(project, directory, "DEFG").setPath("src/main/java/dir/Foo.java").setName("Foo.java")
+      .setKey("PROJECT_KEY:src/main/java/dir/Foo.java");
+    dbClient.componentDao().insert(db.getSession(), directory, file);
+    db.getSession().commit();
+
+    treeRootHolder.setRoot(
+      builder(PROJECT, 1).setUuid(project.uuid()).setKey(project.getKey())
+        .setName("Project")
+        .addChildren(
+          builder(DIRECTORY, 3).setUuid("CDEF").setKey("PROJECT_KEY:src/main/java/dir")
+            .setName("src/main/java/dir")
+            .addChildren(
+              builder(FILE, 4).setUuid("DEFG").setKey("PROJECT_KEY:src/main/java/dir/Foo.java")
+                .setName("src/main/java/dir/Foo.java")
+                .build())
+            .build())
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(db.countRowsOfTable("components")).isEqualTo(3);
+    assertThat(dbClient.componentDao().selectByKey(db.getSession(), project.getKey()).get().uuid()).isEqualTo(project.uuid());
+    assertThat(dbClient.componentDao().selectByKey(db.getSession(), "PROJECT_KEY:src/main/java/dir").get().uuid()).isEqualTo(directory.uuid());
+    assertThat(dbClient.componentDao().selectByKey(db.getSession(), "PROJECT_KEY:src/main/java/dir/Foo.java").get().uuid()).isEqualTo(file.uuid());
+
+    ComponentDto projectReloaded = dbClient.componentDao().selectByKey(db.getSession(), project.getKey()).get();
+    assertThat(projectReloaded.uuid()).isEqualTo(project.uuid());
+    assertThat(projectReloaded.branchUuid()).isEqualTo(project.branchUuid());
+
+    ComponentDto directoryReloaded = dbClient.componentDao().selectByKey(db.getSession(), "PROJECT_KEY:src/main/java/dir").get();
+    assertThat(directoryReloaded.uuid()).isEqualTo(directory.uuid());
+    assertThat(directoryReloaded.getUuidPath()).isEqualTo(directory.getUuidPath());
+    assertThat(directoryReloaded.branchUuid()).isEqualTo(directory.branchUuid());
+    assertThat(directoryReloaded.name()).isEqualTo(directory.name());
+    assertThat(directoryReloaded.path()).isEqualTo(directory.path());
+
+    ComponentDto fileReloaded = dbClient.componentDao().selectByKey(db.getSession(), "PROJECT_KEY:src/main/java/dir/Foo.java").get();
+    assertThat(fileReloaded.uuid()).isEqualTo(file.uuid());
+    assertThat(fileReloaded.getUuidPath()).isEqualTo(file.getUuidPath());
+    assertThat(fileReloaded.branchUuid()).isEqualTo(file.branchUuid());
+    assertThat(fileReloaded.name()).isEqualTo(file.name());
+    assertThat(fileReloaded.path()).isEqualTo(file.path());
+  }
+
+  @Test
+  public void do_not_update_created_at_on_existing_component() {
+    Date oldDate = DateUtils.parseDate("2015-01-01");
+    ComponentDto project = prepareProject(p -> p.setCreatedAt(oldDate));
+    db.getSession().commit();
+
+    treeRootHolder.setRoot(
+      builder(PROJECT, 1).setUuid(project.uuid()).setKey(project.getKey())
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    Optional<ComponentDto> projectReloaded = dbClient.componentDao().selectByUuid(db.getSession(), project.uuid());
+    assertThat(projectReloaded.get().getCreatedAt()).isNotEqualTo(now);
+  }
+
+  @Test
+  public void persist_components_that_were_previously_removed() {
+    ComponentDto project = prepareProject();
+    ComponentDto removedDirectory = ComponentTesting.newDirectory(project, "src/main/java/dir")
+      .setLongName("src/main/java/dir")
+      .setName("dir")
+      .setUuid("CDEF")
+      .setKey("PROJECT_KEY:src/main/java/dir")
+      .setEnabled(false);
+    ComponentDto removedFile = ComponentTesting.newFileDto(project, removedDirectory, "DEFG")
+      .setPath("src/main/java/dir/Foo.java")
+      .setLongName("src/main/java/dir/Foo.java")
+      .setName("Foo.java")
+      .setKey("PROJECT_KEY:src/main/java/dir/Foo.java")
+      .setEnabled(false);
+    dbClient.componentDao().insert(db.getSession(), removedDirectory, removedFile);
+    db.getSession().commit();
+
+    treeRootHolder.setRoot(
+      builder(PROJECT, 1).setUuid(project.uuid()).setKey(project.getKey())
+        .setName("Project")
+        .addChildren(
+          builder(DIRECTORY, 3).setUuid("CDEF").setKey("PROJECT_KEY:src/main/java/dir")
+            .setName("src/main/java/dir")
+            .setShortName("dir")
+            .addChildren(
+              builder(FILE, 4).setUuid("DEFG").setKey("PROJECT_KEY:src/main/java/dir/Foo.java")
+                .setName("src/main/java/dir/Foo.java")
+                .setShortName("Foo.java")
+                .build())
+            .build())
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(db.countRowsOfTable("components")).isEqualTo(3);
+    assertThat(dbClient.componentDao().selectByKey(db.getSession(), project.getKey()).get().uuid()).isEqualTo(project.uuid());
+    assertThat(dbClient.componentDao().selectByKey(db.getSession(), "PROJECT_KEY:src/main/java/dir").get().uuid()).isEqualTo(removedDirectory.uuid());
+    assertThat(dbClient.componentDao().selectByKey(db.getSession(), "PROJECT_KEY:src/main/java/dir/Foo.java").get().uuid()).isEqualTo(removedFile.uuid());
+    assertExistButDisabled(removedDirectory.getKey(), removedFile.getKey());
+
+    // commit the functional transaction
+    dbClient.componentDao().applyBChangesForBranchUuid(db.getSession(), project.uuid());
+
+    ComponentDto projectReloaded = dbClient.componentDao().selectByKey(db.getSession(), project.getKey()).get();
+    assertThat(projectReloaded.uuid()).isEqualTo(project.uuid());
+    assertThat(projectReloaded.getUuidPath()).isEqualTo(project.getUuidPath());
+    assertThat(projectReloaded.branchUuid()).isEqualTo(project.branchUuid());
+    assertThat(projectReloaded.isEnabled()).isTrue();
+
+    ComponentDto directoryReloaded = dbClient.componentDao().selectByKey(db.getSession(), "PROJECT_KEY:src/main/java/dir").get();
+    assertThat(directoryReloaded.uuid()).isEqualTo(removedDirectory.uuid());
+    assertThat(directoryReloaded.getUuidPath()).isEqualTo(removedDirectory.getUuidPath());
+    assertThat(directoryReloaded.branchUuid()).isEqualTo(removedDirectory.branchUuid());
+    assertThat(directoryReloaded.name()).isEqualTo(removedDirectory.name());
+    assertThat(directoryReloaded.longName()).isEqualTo(removedDirectory.longName());
+    assertThat(directoryReloaded.path()).isEqualTo(removedDirectory.path());
+    assertThat(directoryReloaded.isEnabled()).isTrue();
+
+    ComponentDto fileReloaded = dbClient.componentDao().selectByKey(db.getSession(), "PROJECT_KEY:src/main/java/dir/Foo.java").get();
+    assertThat(fileReloaded.uuid()).isEqualTo(removedFile.uuid());
+    assertThat(fileReloaded.getUuidPath()).isEqualTo(removedFile.getUuidPath());
+    assertThat(fileReloaded.branchUuid()).isEqualTo(removedFile.branchUuid());
+    assertThat(fileReloaded.name()).isEqualTo(removedFile.name());
+    assertThat(fileReloaded.path()).isEqualTo(removedFile.path());
+    assertThat(fileReloaded.isEnabled()).isTrue();
+  }
+
+  private void assertExistButDisabled(String... keys) {
+    for (String key : keys) {
+      ComponentDto dto = dbClient.componentDao().selectByKey(db.getSession(), key).get();
+      assertThat(dto.isEnabled()).isFalse();
+    }
+  }
+
+  @Test
+  public void persists_existing_components_with_visibility_of_root_in_db_out_of_functional_transaction() {
+    ComponentDto project = prepareProject(p -> p.setPrivate(true));
+    ComponentDto dir = db.components().insertComponent(newDirectory(project, "DEFG", "Directory").setKey("DIR").setPrivate(true));
+    treeRootHolder.setRoot(createSampleProjectComponentTree(project));
+
+    underTest.execute(new TestComputationStepContext());
+
+    Stream.of(project.uuid(), dir.uuid())
+      .forEach(uuid -> assertThat(dbClient.componentDao().selectByUuid(db.getSession(), uuid).get().isPrivate())
+        .describedAs("for uuid " + uuid)
+        .isEqualTo(true));
+  }
+
+  private ReportComponent createSampleProjectComponentTree(ComponentDto project) {
+    return createSampleProjectComponentTree(project.uuid(), project.getKey());
+  }
+
+  private ReportComponent createSampleProjectComponentTree(String projectUuid, String projectKey) {
+    return builder(PROJECT, 1).setUuid(projectUuid).setKey(projectKey)
+      .setName("Project")
+      .addChildren(
+        builder(Component.Type.DIRECTORY, 3).setUuid("DEFG").setKey("DIR")
+          .setName("Directory")
+          .addChildren(
+            builder(FILE, 4).setUuid("CDEF").setKey("FILE")
+              .setName("file")
+              .build())
+          .build())
+      .build();
+  }
+
+  private ReportComponent.Builder asTreeRoot(ComponentDto project) {
+    return builder(PROJECT, 1).setUuid(project.uuid()).setKey(project.getKey()).setName(project.name());
+  }
+
+  private ComponentDto prepareProject() {
+    return prepareProject(defaults());
+  }
+
+  private ComponentDto prepareProject(Consumer<ComponentDto> populators) {
+    ComponentDto dto = db.components().insertPrivateProject(populators);
+    analysisMetadataHolder.setProject(Project.from(dto));
+    analysisMetadataHolder.setBranch(new DefaultBranchImpl(DEFAULT_MAIN_BRANCH_NAME));
+    return dto;
+  }
+
+  private ComponentDto prepareBranch(String branchName) {
+    ComponentDto projectDto = db.components().insertPublicProject();
+    ComponentDto branchDto = db.components().insertProjectBranch(projectDto, b -> b.setKey(branchName));
+    analysisMetadataHolder.setProject(Project.from(projectDto));
+    analysisMetadataHolder.setBranch(new TestBranch(branchName));
+    return branchDto;
+  }
+
+  private static <T> Consumer<T> defaults() {
+    return t -> {
+    };
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/SendIssueNotificationsStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/SendIssueNotificationsStepIT.java
new file mode 100644 (file)
index 0000000..5b315e9
--- /dev/null
@@ -0,0 +1,721 @@
+/*
+ * 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.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.function.Supplier;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.assertj.core.groups.Tuple;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.RuleType;
+import org.sonar.api.utils.Duration;
+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.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.DefaultBranchImpl;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
+import org.sonar.ce.task.projectanalysis.issue.ProtoIssueCache;
+import org.sonar.ce.task.projectanalysis.notification.NotificationFactory;
+import org.sonar.ce.task.projectanalysis.util.cache.DiskCache;
+import org.sonar.ce.task.step.ComputationStep;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.issue.notification.DistributedMetricStatsInt;
+import org.sonar.server.issue.notification.IssuesChangesNotification;
+import org.sonar.server.issue.notification.MyNewIssuesNotification;
+import org.sonar.server.issue.notification.NewIssuesNotification;
+import org.sonar.server.issue.notification.NewIssuesStatistics;
+import org.sonar.server.notification.NotificationService;
+import org.sonar.server.project.Project;
+
+import static java.util.Arrays.stream;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.shuffle;
+import static java.util.Collections.singleton;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Stream.concat;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.apache.commons.lang.math.RandomUtils.nextInt;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.groups.Tuple.tuple;
+import static org.mockito.ArgumentCaptor.forClass;
+import static org.mockito.ArgumentMatchers.anyCollection;
+import static org.mockito.ArgumentMatchers.anyMap;
+import static org.mockito.ArgumentMatchers.anySet;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
+import static org.sonar.ce.task.projectanalysis.component.Component.Type;
+import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
+import static org.sonar.ce.task.projectanalysis.step.SendIssueNotificationsStep.NOTIF_TYPES;
+import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME;
+import static org.sonar.db.component.BranchType.BRANCH;
+import static org.sonar.db.component.BranchType.PULL_REQUEST;
+import static org.sonar.db.component.ComponentTesting.newBranchComponent;
+import static org.sonar.db.component.ComponentTesting.newBranchDto;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
+import static org.sonar.db.issue.IssueTesting.newIssue;
+import static org.sonar.db.rule.RuleTesting.newRule;
+
+public class SendIssueNotificationsStepIT extends BaseStepTest {
+
+  private static final String BRANCH_NAME = "feature";
+  private static final String PULL_REQUEST_ID = "pr-123";
+
+  private static final long ANALYSE_DATE = 123L;
+  private static final int FIVE_MINUTES_IN_MS = 1000 * 60 * 5;
+
+  private static final Duration ISSUE_DURATION = Duration.create(100L);
+
+  private static final Component FILE = builder(Type.FILE, 11).build();
+  private static final Component PROJECT = builder(Type.PROJECT, 1)
+    .setProjectVersion(randomAlphanumeric(10))
+    .addChildren(FILE).build();
+
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule()
+    .setRoot(PROJECT);
+  @Rule
+  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule()
+    .setBranch(new DefaultBranchImpl(DEFAULT_MAIN_BRANCH_NAME))
+    .setAnalysisDate(new Date(ANALYSE_DATE));
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+
+  private final Random random = new Random();
+  private final RuleType[] RULE_TYPES_EXCEPT_HOTSPOTS = Stream.of(RuleType.values()).filter(r -> r != SECURITY_HOTSPOT).toArray(RuleType[]::new);
+  private final RuleType randomRuleType = RULE_TYPES_EXCEPT_HOTSPOTS[random.nextInt(RULE_TYPES_EXCEPT_HOTSPOTS.length)];
+  @SuppressWarnings("unchecked")
+  private Class<Map<String, UserDto>> assigneeCacheType = (Class<Map<String, UserDto>>) (Object) Map.class;
+  @SuppressWarnings("unchecked")
+  private Class<Set<DefaultIssue>> setType = (Class<Set<DefaultIssue>>) (Class<?>) Set.class;
+  @SuppressWarnings("unchecked")
+  private Class<Map<String, UserDto>> mapType = (Class<Map<String, UserDto>>) (Class<?>) Map.class;
+  private ArgumentCaptor<Map<String, UserDto>> assigneeCacheCaptor = ArgumentCaptor.forClass(assigneeCacheType);
+  private ArgumentCaptor<Set<DefaultIssue>> issuesSetCaptor = forClass(setType);
+  private ArgumentCaptor<Map<String, UserDto>> assigneeByUuidCaptor = forClass(mapType);
+  private NotificationService notificationService = mock(NotificationService.class);
+  private NotificationFactory notificationFactory = mock(NotificationFactory.class);
+  private NewIssuesNotification newIssuesNotificationMock = createNewIssuesNotificationMock();
+  private MyNewIssuesNotification myNewIssuesNotificationMock = createMyNewIssuesNotificationMock();
+
+  private ProtoIssueCache protoIssueCache;
+  private SendIssueNotificationsStep underTest;
+
+  @Before
+  public void setUp() throws Exception {
+    protoIssueCache = new ProtoIssueCache(temp.newFile(), System2.INSTANCE);
+    underTest = new SendIssueNotificationsStep(protoIssueCache, treeRootHolder, notificationService, analysisMetadataHolder,
+      notificationFactory, db.getDbClient());
+    when(notificationFactory.newNewIssuesNotification(any(assigneeCacheType))).thenReturn(newIssuesNotificationMock);
+    when(notificationFactory.newMyNewIssuesNotification(any(assigneeCacheType))).thenReturn(myNewIssuesNotificationMock);
+  }
+
+  @Test
+  public void do_not_send_notifications_if_no_subscribers() {
+    analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
+    when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(false);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    verify(notificationService, never()).deliver(any(Notification.class));
+    verify(notificationService, never()).deliverEmails(anyCollection());
+    verifyStatistics(context, 0, 0, 0);
+  }
+
+  @Test
+  public void send_global_new_issues_notification() {
+    analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
+    protoIssueCache.newAppender().append(
+      createIssue().setType(randomRuleType).setEffort(ISSUE_DURATION)
+        .setCreationDate(new Date(ANALYSE_DATE)))
+      .close();
+    when(notificationService.hasProjectSubscribersForTypes(eq(PROJECT.getUuid()), any())).thenReturn(true);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    verify(notificationService).deliver(newIssuesNotificationMock);
+    verify(newIssuesNotificationMock).setProject(PROJECT.getKey(), PROJECT.getName(), null, null);
+    verify(newIssuesNotificationMock).setAnalysisDate(new Date(ANALYSE_DATE));
+    verify(newIssuesNotificationMock).setStatistics(eq(PROJECT.getName()), any());
+    verify(newIssuesNotificationMock).setDebt(ISSUE_DURATION);
+    verifyStatistics(context, 1, 0, 0);
+  }
+
+  @Test
+  public void send_global_new_issues_notification_only_for_non_backdated_issues() {
+    Random random = new Random();
+    Integer[] efforts = IntStream.range(0, 1 + random.nextInt(10)).mapToObj(i -> 10_000 * i).toArray(Integer[]::new);
+    Integer[] backDatedEfforts = IntStream.range(0, 1 + random.nextInt(10)).mapToObj(i -> 10 + random.nextInt(100)).toArray(Integer[]::new);
+    Duration expectedEffort = Duration.create(stream(efforts).mapToInt(i -> i).sum());
+    List<DefaultIssue> issues = concat(stream(efforts)
+        .map(effort -> createIssue().setType(randomRuleType).setEffort(Duration.create(effort))
+          .setCreationDate(new Date(ANALYSE_DATE))),
+      stream(backDatedEfforts)
+        .map(effort -> createIssue().setType(randomRuleType).setEffort(Duration.create(effort))
+          .setCreationDate(new Date(ANALYSE_DATE - FIVE_MINUTES_IN_MS))))
+      .collect(toList());
+    shuffle(issues);
+    DiskCache.CacheAppender issueCache = this.protoIssueCache.newAppender();
+    issues.forEach(issueCache::append);
+    issueCache.close();
+    analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
+    when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    verify(notificationService).deliver(newIssuesNotificationMock);
+    ArgumentCaptor<NewIssuesStatistics.Stats> statsCaptor = forClass(NewIssuesStatistics.Stats.class);
+    verify(newIssuesNotificationMock).setStatistics(eq(PROJECT.getName()), statsCaptor.capture());
+    verify(newIssuesNotificationMock).setDebt(expectedEffort);
+    NewIssuesStatistics.Stats stats = statsCaptor.getValue();
+    assertThat(stats.hasIssues()).isTrue();
+    // just checking all issues have been added to the stats
+    DistributedMetricStatsInt severity = stats.getDistributedMetricStats(NewIssuesStatistics.Metric.RULE_TYPE);
+    assertThat(severity.getOnCurrentAnalysis()).isEqualTo(efforts.length);
+    assertThat(severity.getTotal()).isEqualTo(backDatedEfforts.length + efforts.length);
+    verifyStatistics(context, 1, 0, 0);
+  }
+
+  @Test
+  public void do_not_send_global_new_issues_notification_if_issue_has_been_backdated() {
+    analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
+    protoIssueCache.newAppender().append(
+      createIssue().setType(randomRuleType).setEffort(ISSUE_DURATION)
+        .setCreationDate(new Date(ANALYSE_DATE - FIVE_MINUTES_IN_MS)))
+      .close();
+    when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    verify(notificationService, never()).deliver(any(Notification.class));
+    verify(notificationService, never()).deliverEmails(anyCollection());
+    verifyStatistics(context, 0, 0, 0);
+  }
+
+  @Test
+  public void send_global_new_issues_notification_on_branch() {
+    ComponentDto project = newPrivateProjectDto();
+    ComponentDto branch = setUpBranch(project, BRANCH);
+    protoIssueCache.newAppender().append(
+      createIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setCreationDate(new Date(ANALYSE_DATE))).close();
+    when(notificationService.hasProjectSubscribersForTypes(branch.uuid(), NOTIF_TYPES)).thenReturn(true);
+    analysisMetadataHolder.setProject(Project.from(project));
+    analysisMetadataHolder.setBranch(newBranch(BranchType.BRANCH));
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    verify(notificationService).deliver(newIssuesNotificationMock);
+    verify(newIssuesNotificationMock).setProject(branch.getKey(), branch.longName(), BRANCH_NAME, null);
+    verify(newIssuesNotificationMock).setAnalysisDate(new Date(ANALYSE_DATE));
+    verify(newIssuesNotificationMock).setStatistics(eq(branch.longName()), any(NewIssuesStatistics.Stats.class));
+    verify(newIssuesNotificationMock).setDebt(ISSUE_DURATION);
+    verifyStatistics(context, 1, 0, 0);
+  }
+
+  @Test
+  public void do_not_send_global_new_issues_notification_on_pull_request() {
+    ComponentDto project = newPrivateProjectDto();
+    ComponentDto branch = setUpBranch(project, PULL_REQUEST);
+    protoIssueCache.newAppender().append(
+      createIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setCreationDate(new Date(ANALYSE_DATE))).close();
+    when(notificationService.hasProjectSubscribersForTypes(project.uuid(), NOTIF_TYPES)).thenReturn(true);
+    analysisMetadataHolder.setProject(Project.from(project));
+    analysisMetadataHolder.setBranch(newPullRequest());
+    analysisMetadataHolder.setPullRequestKey(PULL_REQUEST_ID);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    verifyNoInteractions(notificationService, newIssuesNotificationMock);
+  }
+
+  private DefaultIssue createIssue() {
+    return new DefaultIssue().setKey("k").setProjectKey("p").setStatus("OPEN").setProjectUuid("uuid").setComponentKey("c").setRuleKey(RuleKey.of("r", "r"));
+  }
+
+  @Test
+  public void do_not_send_global_new_issues_notification_on_branch_if_issue_has_been_backdated() {
+    ComponentDto project = newPrivateProjectDto();
+    ComponentDto branch = setUpBranch(project, BRANCH);
+    protoIssueCache.newAppender().append(
+      createIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setCreationDate(new Date(ANALYSE_DATE - FIVE_MINUTES_IN_MS))).close();
+    when(notificationService.hasProjectSubscribersForTypes(branch.uuid(), NOTIF_TYPES)).thenReturn(true);
+    analysisMetadataHolder.setProject(Project.from(project));
+    analysisMetadataHolder.setBranch(newBranch(BranchType.BRANCH));
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    verify(notificationService, never()).deliver(any(Notification.class));
+    verify(notificationService, never()).deliverEmails(anyCollection());
+    verifyStatistics(context, 0, 0, 0);
+  }
+
+  @Test
+  public void send_new_issues_notification_to_user() {
+    UserDto user = db.users().insertUser();
+    analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
+
+    protoIssueCache.newAppender().append(
+      createIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setAssigneeUuid(user.getUuid()).setCreationDate(new Date(ANALYSE_DATE)))
+      .close();
+    when(notificationService.hasProjectSubscribersForTypes(eq(PROJECT.getUuid()), any())).thenReturn(true);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    verify(notificationService).deliverEmails(ImmutableSet.of(newIssuesNotificationMock));
+    verify(notificationService).deliverEmails(ImmutableSet.of(myNewIssuesNotificationMock));
+    // old API compatibility call
+    verify(notificationService).deliver(newIssuesNotificationMock);
+    verify(notificationService).deliver(myNewIssuesNotificationMock);
+    verify(myNewIssuesNotificationMock).setAssignee(any(UserDto.class));
+    verify(myNewIssuesNotificationMock).setProject(PROJECT.getKey(), PROJECT.getName(), null, null);
+    verify(myNewIssuesNotificationMock).setAnalysisDate(new Date(ANALYSE_DATE));
+    verify(myNewIssuesNotificationMock).setStatistics(eq(PROJECT.getName()), any(NewIssuesStatistics.Stats.class));
+    verify(myNewIssuesNotificationMock).setDebt(ISSUE_DURATION);
+    verifyStatistics(context, 1, 1, 0);
+  }
+
+  @Test
+  public void send_new_issues_notification_to_user_only_for_those_assigned_to_her() throws IOException {
+    UserDto perceval = db.users().insertUser(u -> u.setLogin("perceval"));
+    Integer[] assigned = IntStream.range(0, 5).mapToObj(i -> 10_000 * i).toArray(Integer[]::new);
+    Duration expectedEffort = Duration.create(stream(assigned).mapToInt(i -> i).sum());
+
+    UserDto arthur = db.users().insertUser(u -> u.setLogin("arthur"));
+    Integer[] assignedToOther = IntStream.range(0, 3).mapToObj(i -> 10).toArray(Integer[]::new);
+
+    List<DefaultIssue> issues = concat(stream(assigned)
+        .map(effort -> createIssue().setType(randomRuleType).setEffort(Duration.create(effort))
+          .setAssigneeUuid(perceval.getUuid())
+          .setNew(true)
+          .setCreationDate(new Date(ANALYSE_DATE))),
+      stream(assignedToOther)
+        .map(effort -> createIssue().setType(randomRuleType).setEffort(Duration.create(effort))
+          .setAssigneeUuid(arthur.getUuid())
+          .setNew(true)
+          .setCreationDate(new Date(ANALYSE_DATE))))
+      .collect(toList());
+    shuffle(issues);
+    ProtoIssueCache protoIssueCache = new ProtoIssueCache(temp.newFile(), System2.INSTANCE);
+    DiskCache.CacheAppender newIssueCache = protoIssueCache.newAppender();
+    issues.forEach(newIssueCache::append);
+    newIssueCache.close();
+    analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
+    when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
+
+    NotificationFactory notificationFactory = mock(NotificationFactory.class);
+    NewIssuesNotification newIssuesNotificationMock = createNewIssuesNotificationMock();
+    when(notificationFactory.newNewIssuesNotification(assigneeCacheCaptor.capture()))
+      .thenReturn(newIssuesNotificationMock);
+
+    MyNewIssuesNotification myNewIssuesNotificationMock1 = createMyNewIssuesNotificationMock();
+    MyNewIssuesNotification myNewIssuesNotificationMock2 = createMyNewIssuesNotificationMock();
+    doReturn(myNewIssuesNotificationMock1).doReturn(myNewIssuesNotificationMock2).when(notificationFactory).newMyNewIssuesNotification(any(assigneeCacheType));
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    new SendIssueNotificationsStep(protoIssueCache, treeRootHolder, notificationService, analysisMetadataHolder, notificationFactory, db.getDbClient())
+      .execute(context);
+
+    verify(notificationService).deliverEmails(ImmutableSet.of(myNewIssuesNotificationMock1, myNewIssuesNotificationMock2));
+    // old API compatibility
+    verify(notificationService).deliver(myNewIssuesNotificationMock1);
+    verify(notificationService).deliver(myNewIssuesNotificationMock2);
+
+    verify(notificationFactory).newNewIssuesNotification(assigneeCacheCaptor.capture());
+    verify(notificationFactory, times(2)).newMyNewIssuesNotification(assigneeCacheCaptor.capture());
+    verifyNoMoreInteractions(notificationFactory);
+    verifyAssigneeCache(assigneeCacheCaptor, perceval, arthur);
+
+    Map<String, MyNewIssuesNotification> myNewIssuesNotificationMocksByUsersName = new HashMap<>();
+    ArgumentCaptor<UserDto> userCaptor1 = forClass(UserDto.class);
+    verify(myNewIssuesNotificationMock1).setAssignee(userCaptor1.capture());
+    myNewIssuesNotificationMocksByUsersName.put(userCaptor1.getValue().getLogin(), myNewIssuesNotificationMock1);
+
+    ArgumentCaptor<UserDto> userCaptor2 = forClass(UserDto.class);
+    verify(myNewIssuesNotificationMock2).setAssignee(userCaptor2.capture());
+    myNewIssuesNotificationMocksByUsersName.put(userCaptor2.getValue().getLogin(), myNewIssuesNotificationMock2);
+
+    MyNewIssuesNotification myNewIssuesNotificationMock = myNewIssuesNotificationMocksByUsersName.get("perceval");
+    ArgumentCaptor<NewIssuesStatistics.Stats> statsCaptor = forClass(NewIssuesStatistics.Stats.class);
+    verify(myNewIssuesNotificationMock).setStatistics(eq(PROJECT.getName()), statsCaptor.capture());
+    verify(myNewIssuesNotificationMock).setDebt(expectedEffort);
+
+    NewIssuesStatistics.Stats stats = statsCaptor.getValue();
+    assertThat(stats.hasIssues()).isTrue();
+    // just checking all issues have been added to the stats
+    DistributedMetricStatsInt severity = stats.getDistributedMetricStats(NewIssuesStatistics.Metric.RULE_TYPE);
+    assertThat(severity.getOnCurrentAnalysis()).isEqualTo(assigned.length);
+    assertThat(severity.getTotal()).isEqualTo(assigned.length);
+
+    verifyStatistics(context, 1, 2, 0);
+  }
+
+  @Test
+  public void send_new_issues_notification_to_user_only_for_non_backdated_issues() {
+    UserDto user = db.users().insertUser();
+    Random random = new Random();
+    Integer[] efforts = IntStream.range(0, 1 + random.nextInt(10)).mapToObj(i -> 10_000 * i).toArray(Integer[]::new);
+    Integer[] backDatedEfforts = IntStream.range(0, 1 + random.nextInt(10)).mapToObj(i -> 10 + random.nextInt(100)).toArray(Integer[]::new);
+    Duration expectedEffort = Duration.create(stream(efforts).mapToInt(i -> i).sum());
+    List<DefaultIssue> issues = concat(stream(efforts)
+        .map(effort -> createIssue().setType(randomRuleType).setEffort(Duration.create(effort))
+          .setAssigneeUuid(user.getUuid())
+          .setCreationDate(new Date(ANALYSE_DATE))),
+      stream(backDatedEfforts)
+        .map(effort -> createIssue().setType(randomRuleType).setEffort(Duration.create(effort))
+          .setAssigneeUuid(user.getUuid())
+          .setCreationDate(new Date(ANALYSE_DATE - FIVE_MINUTES_IN_MS))))
+      .collect(toList());
+    shuffle(issues);
+    DiskCache.CacheAppender issueCache = this.protoIssueCache.newAppender();
+    issues.forEach(issueCache::append);
+    issueCache.close();
+    analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
+    when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    verify(notificationService).deliver(newIssuesNotificationMock);
+    verify(notificationService).deliverEmails(ImmutableSet.of(myNewIssuesNotificationMock));
+    // old API compatibility
+    verify(notificationService).deliver(myNewIssuesNotificationMock);
+
+    verify(notificationFactory).newNewIssuesNotification(assigneeCacheCaptor.capture());
+    verify(notificationFactory).newMyNewIssuesNotification(assigneeCacheCaptor.capture());
+    verifyNoMoreInteractions(notificationFactory);
+    verifyAssigneeCache(assigneeCacheCaptor, user);
+
+    verify(myNewIssuesNotificationMock).setAssignee(any(UserDto.class));
+    ArgumentCaptor<NewIssuesStatistics.Stats> statsCaptor = forClass(NewIssuesStatistics.Stats.class);
+    verify(myNewIssuesNotificationMock).setStatistics(eq(PROJECT.getName()), statsCaptor.capture());
+    verify(myNewIssuesNotificationMock).setDebt(expectedEffort);
+    NewIssuesStatistics.Stats stats = statsCaptor.getValue();
+    assertThat(stats.hasIssues()).isTrue();
+    // just checking all issues have been added to the stats
+    DistributedMetricStatsInt severity = stats.getDistributedMetricStats(NewIssuesStatistics.Metric.RULE_TYPE);
+    assertThat(severity.getOnCurrentAnalysis()).isEqualTo(efforts.length);
+    assertThat(severity.getTotal()).isEqualTo(backDatedEfforts.length + efforts.length);
+
+    verifyStatistics(context, 1, 1, 0);
+  }
+
+  private static void verifyAssigneeCache(ArgumentCaptor<Map<String, UserDto>> assigneeCacheCaptor, UserDto... users) {
+    Map<String, UserDto> cache = assigneeCacheCaptor.getAllValues().iterator().next();
+    assertThat(assigneeCacheCaptor.getAllValues())
+      .filteredOn(t -> t != cache)
+      .isEmpty();
+    Tuple[] expected = stream(users).map(user -> tuple(user.getUuid(), user.getUuid(), user.getUuid(), user.getLogin())).toArray(Tuple[]::new);
+    assertThat(cache.entrySet())
+      .extracting(Map.Entry::getKey, t -> t.getValue().getUuid(), t -> t.getValue().getUuid(), t -> t.getValue().getLogin())
+      .containsOnly(expected);
+  }
+
+  @Test
+  public void do_not_send_new_issues_notification_to_user_if_issue_is_backdated() {
+    analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
+    UserDto user = db.users().insertUser();
+    protoIssueCache.newAppender().append(
+      createIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setAssigneeUuid(user.getUuid())
+        .setCreationDate(new Date(ANALYSE_DATE - FIVE_MINUTES_IN_MS)))
+      .close();
+    when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    verify(notificationService, never()).deliver(any(Notification.class));
+    verify(notificationService, never()).deliverEmails(anyCollection());
+    verifyStatistics(context, 0, 0, 0);
+  }
+
+  @Test
+  public void send_issues_change_notification() {
+    sendIssueChangeNotification(ANALYSE_DATE);
+  }
+
+  @Test
+  public void do_not_send_new_issues_notifications_for_hotspot() {
+    UserDto user = db.users().insertUser();
+    ComponentDto project = newPrivateProjectDto().setKey(PROJECT.getKey()).setLongName(PROJECT.getName());
+    ComponentDto file = newFileDto(project).setKey(FILE.getKey()).setLongName(FILE.getName());
+    RuleDto ruleDefinitionDto = newRule();
+    prepareIssue(ANALYSE_DATE, user, project, file, ruleDefinitionDto, RuleType.SECURITY_HOTSPOT);
+    analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
+    when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
+
+    TestComputationStepContext context = new TestComputationStepContext();
+    underTest.execute(context);
+
+    verify(notificationService, never()).deliver(any(Notification.class));
+    verify(notificationService, never()).deliverEmails(anyCollection());
+    verifyStatistics(context, 0, 0, 0);
+  }
+
+  @Test
+  public void send_issues_change_notification_even_if_issue_is_backdated() {
+    sendIssueChangeNotification(ANALYSE_DATE - FIVE_MINUTES_IN_MS);
+  }
+
+  private void sendIssueChangeNotification(long issueCreatedAt) {
+    UserDto user = db.users().insertUser();
+    ComponentDto project = newPrivateProjectDto().setKey(PROJECT.getKey()).setLongName(PROJECT.getName());
+    analysisMetadataHolder.setProject(Project.from(project));
+    ComponentDto file = newFileDto(project).setKey(FILE.getKey()).setLongName(FILE.getName());
+    treeRootHolder.setRoot(builder(Type.PROJECT, 2).setKey(project.getKey()).setName(project.longName()).setUuid(project.uuid())
+      .addChildren(
+        builder(Type.FILE, 11).setKey(file.getKey()).setName(file.longName()).build())
+      .build());
+    RuleDto ruleDefinitionDto = newRule();
+    RuleType randomTypeExceptHotspot = RuleType.values()[nextInt(RuleType.values().length - 1)];
+    DefaultIssue issue = prepareIssue(issueCreatedAt, user, project, file, ruleDefinitionDto, randomTypeExceptHotspot);
+    IssuesChangesNotification issuesChangesNotification = mock(IssuesChangesNotification.class);
+    when(notificationService.hasProjectSubscribersForTypes(project.uuid(), NOTIF_TYPES)).thenReturn(true);
+    when(notificationFactory.newIssuesChangesNotification(anySet(), anyMap())).thenReturn(issuesChangesNotification);
+
+    underTest.execute(new TestComputationStepContext());
+
+    verify(notificationFactory).newIssuesChangesNotification(issuesSetCaptor.capture(), assigneeByUuidCaptor.capture());
+    assertThat(issuesSetCaptor.getValue()).hasSize(1);
+    assertThat(issuesSetCaptor.getValue().iterator().next()).isEqualTo(issue);
+    assertThat(assigneeByUuidCaptor.getValue()).hasSize(1);
+    assertThat(assigneeByUuidCaptor.getValue().get(user.getUuid())).isNotNull();
+    verify(notificationService).hasProjectSubscribersForTypes(project.uuid(), NOTIF_TYPES);
+    verify(notificationService).deliverEmails(singleton(issuesChangesNotification));
+    verify(notificationService).deliver(issuesChangesNotification);
+    verifyNoMoreInteractions(notificationService);
+  }
+
+  private DefaultIssue prepareIssue(long issueCreatedAt, UserDto user, ComponentDto project, ComponentDto file, RuleDto ruleDefinitionDto, RuleType type) {
+    DefaultIssue issue = newIssue(ruleDefinitionDto, project, file).setType(type).toDefaultIssue()
+      .setNew(false).setChanged(true).setSendNotifications(true).setCreationDate(new Date(issueCreatedAt)).setAssigneeUuid(user.getUuid());
+    protoIssueCache.newAppender().append(issue).close();
+    when(notificationService.hasProjectSubscribersForTypes(project.branchUuid(), NOTIF_TYPES)).thenReturn(true);
+    return issue;
+  }
+
+  @Test
+  public void send_issues_change_notification_on_branch() {
+    sendIssueChangeNotificationOnBranch(ANALYSE_DATE);
+  }
+
+  @Test
+  public void send_issues_change_notification_on_branch_even_if_issue_is_backdated() {
+    sendIssueChangeNotificationOnBranch(ANALYSE_DATE - FIVE_MINUTES_IN_MS);
+  }
+
+  private void sendIssueChangeNotificationOnBranch(long issueCreatedAt) {
+    ComponentDto project = newPrivateProjectDto();
+    ComponentDto branch = newBranchComponent(project, newBranchDto(project).setKey(BRANCH_NAME));
+    ComponentDto file = newFileDto(branch);
+    treeRootHolder.setRoot(builder(Type.PROJECT, 2).setKey(branch.getKey()).setName(branch.longName()).setUuid(branch.uuid()).addChildren(
+      builder(Type.FILE, 11).setKey(file.getKey()).setName(file.longName()).build()).build());
+    analysisMetadataHolder.setProject(Project.from(project));
+    RuleDto ruleDefinitionDto = newRule();
+    RuleType randomTypeExceptHotspot = RuleType.values()[nextInt(RuleType.values().length - 1)];
+    DefaultIssue issue = newIssue(ruleDefinitionDto, branch, file).setType(randomTypeExceptHotspot).toDefaultIssue()
+      .setNew(false)
+      .setChanged(true)
+      .setSendNotifications(true)
+      .setCreationDate(new Date(issueCreatedAt));
+    protoIssueCache.newAppender().append(issue).close();
+    when(notificationService.hasProjectSubscribersForTypes(project.uuid(), NOTIF_TYPES)).thenReturn(true);
+    IssuesChangesNotification issuesChangesNotification = mock(IssuesChangesNotification.class);
+    when(notificationFactory.newIssuesChangesNotification(anySet(), anyMap())).thenReturn(issuesChangesNotification);
+    analysisMetadataHolder.setBranch(newBranch(BranchType.BRANCH));
+
+    underTest.execute(new TestComputationStepContext());
+
+    verify(notificationFactory).newIssuesChangesNotification(issuesSetCaptor.capture(), assigneeByUuidCaptor.capture());
+    assertThat(issuesSetCaptor.getValue()).hasSize(1);
+    assertThat(issuesSetCaptor.getValue().iterator().next()).isEqualTo(issue);
+    assertThat(assigneeByUuidCaptor.getValue()).isEmpty();
+    verify(notificationService).hasProjectSubscribersForTypes(project.uuid(), NOTIF_TYPES);
+    verify(notificationService).deliverEmails(singleton(issuesChangesNotification));
+    verify(notificationService).deliver(issuesChangesNotification);
+    verifyNoMoreInteractions(notificationService);
+  }
+
+  @Test
+  public void sends_one_issue_change_notification_every_1000_issues() {
+    UserDto user = db.users().insertUser();
+    ComponentDto project = newPrivateProjectDto().setKey(PROJECT.getKey()).setLongName(PROJECT.getName());
+    ComponentDto file = newFileDto(project).setKey(FILE.getKey()).setLongName(FILE.getName());
+    RuleDto ruleDefinitionDto = newRule();
+    RuleType randomTypeExceptHotspot = RuleType.values()[nextInt(RuleType.values().length - 1)];
+    List<DefaultIssue> issues = IntStream.range(0, 2001 + new Random().nextInt(10))
+      .mapToObj(i -> newIssue(ruleDefinitionDto, project, file).setKee("uuid_" + i).setType(randomTypeExceptHotspot).toDefaultIssue()
+        .setNew(false).setChanged(true).setSendNotifications(true).setAssigneeUuid(user.getUuid()))
+      .toList();
+    DiskCache.CacheAppender cacheAppender = protoIssueCache.newAppender();
+    issues.forEach(cacheAppender::append);
+    cacheAppender.close();
+    analysisMetadataHolder.setProject(Project.from(project));
+    NewIssuesFactoryCaptor newIssuesFactoryCaptor = new NewIssuesFactoryCaptor(() -> mock(IssuesChangesNotification.class));
+    when(notificationFactory.newIssuesChangesNotification(anySet(), anyMap())).thenAnswer(newIssuesFactoryCaptor);
+    when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
+    when(notificationService.hasProjectSubscribersForTypes(project.uuid(), NOTIF_TYPES)).thenReturn(true);
+
+    underTest.execute(new TestComputationStepContext());
+
+    verify(notificationFactory, times(3)).newIssuesChangesNotification(anySet(), anyMap());
+    assertThat(newIssuesFactoryCaptor.issuesSetCaptor).hasSize(3);
+    assertThat(newIssuesFactoryCaptor.issuesSetCaptor.get(0)).hasSize(1000);
+    assertThat(newIssuesFactoryCaptor.issuesSetCaptor.get(1)).hasSize(1000);
+    assertThat(newIssuesFactoryCaptor.issuesSetCaptor.get(2)).hasSize(issues.size() - 2000);
+    assertThat(newIssuesFactoryCaptor.assigneeCacheCaptor)
+      .hasSize(3)
+      .containsOnly(newIssuesFactoryCaptor.assigneeCacheCaptor.iterator().next());
+    ArgumentCaptor<Collection> collectionCaptor = forClass(Collection.class);
+    verify(notificationService, times(3)).deliverEmails(collectionCaptor.capture());
+    assertThat(collectionCaptor.getAllValues()).hasSize(3);
+    assertThat(collectionCaptor.getAllValues().get(0)).hasSize(1);
+    assertThat(collectionCaptor.getAllValues().get(1)).hasSize(1);
+    assertThat(collectionCaptor.getAllValues().get(2)).hasSize(1);
+    verify(notificationService, times(3)).deliver(any(IssuesChangesNotification.class));
+  }
+
+  /**
+   * Since the very same Set object is passed to {@link NotificationFactory#newIssuesChangesNotification(Set, Map)} and
+   * reset between each call. We must make a copy of each argument to capture what's been passed to the factory.
+   * This is of course not supported by Mockito's {@link ArgumentCaptor} and we implement this ourselves with a
+   * {@link Answer}.
+   */
+  private static class NewIssuesFactoryCaptor implements Answer<Object> {
+    private final Supplier<IssuesChangesNotification> delegate;
+    private final List<Set<DefaultIssue>> issuesSetCaptor = new ArrayList<>();
+    private final List<Map<String, UserDto>> assigneeCacheCaptor = new ArrayList<>();
+
+    private NewIssuesFactoryCaptor(Supplier<IssuesChangesNotification> delegate) {
+      this.delegate = delegate;
+    }
+
+    @Override
+    public Object answer(InvocationOnMock t) {
+      Set<DefaultIssue> issuesSet = t.getArgument(0);
+      Map<String, UserDto> assigneeCatch = t.getArgument(1);
+      issuesSetCaptor.add(ImmutableSet.copyOf(issuesSet));
+      assigneeCacheCaptor.add(ImmutableMap.copyOf(assigneeCatch));
+      return delegate.get();
+    }
+  }
+
+  private NewIssuesNotification createNewIssuesNotificationMock() {
+    NewIssuesNotification notification = mock(NewIssuesNotification.class);
+    when(notification.setProject(any(), any(), any(), any())).thenReturn(notification);
+    when(notification.setProjectVersion(any())).thenReturn(notification);
+    when(notification.setAnalysisDate(any())).thenReturn(notification);
+    when(notification.setStatistics(any(), any())).thenReturn(notification);
+    when(notification.setDebt(any())).thenReturn(notification);
+    return notification;
+  }
+
+  private MyNewIssuesNotification createMyNewIssuesNotificationMock() {
+    MyNewIssuesNotification notification = mock(MyNewIssuesNotification.class);
+    when(notification.setAssignee(any(UserDto.class))).thenReturn(notification);
+    when(notification.setProject(any(), any(), any(), any())).thenReturn(notification);
+    when(notification.setProjectVersion(any())).thenReturn(notification);
+    when(notification.setAnalysisDate(any())).thenReturn(notification);
+    when(notification.setStatistics(any(), any())).thenReturn(notification);
+    when(notification.setDebt(any())).thenReturn(notification);
+    return notification;
+  }
+
+  private static Branch newBranch(BranchType type) {
+    Branch branch = mock(Branch.class);
+    when(branch.isMain()).thenReturn(false);
+    when(branch.getName()).thenReturn(BRANCH_NAME);
+    when(branch.getType()).thenReturn(type);
+    return branch;
+  }
+
+  private static Branch newPullRequest() {
+    Branch branch = mock(Branch.class);
+    when(branch.isMain()).thenReturn(false);
+    when(branch.getType()).thenReturn(PULL_REQUEST);
+    when(branch.getName()).thenReturn(BRANCH_NAME);
+    when(branch.getPullRequestKey()).thenReturn(PULL_REQUEST_ID);
+    return branch;
+  }
+
+  private ComponentDto setUpBranch(ComponentDto project, BranchType branchType) {
+    ComponentDto branch = newBranchComponent(project, newBranchDto(project, branchType).setKey(BRANCH_NAME));
+    ComponentDto file = newFileDto(branch);
+    treeRootHolder.setRoot(builder(Type.PROJECT, 2).setKey(branch.getKey()).setName(branch.longName()).setUuid(branch.uuid()).addChildren(
+      builder(Type.FILE, 11).setKey(file.getKey()).setName(file.longName()).build()).build());
+    return branch;
+  }
+
+  private static void verifyStatistics(TestComputationStepContext context, int expectedNewIssuesNotifications, int expectedMyNewIssuesNotifications,
+    int expectedIssueChangesNotifications) {
+    context.getStatistics().assertValue("newIssuesNotifs", expectedNewIssuesNotifications);
+    context.getStatistics().assertValue("myNewIssuesNotifs", expectedMyNewIssuesNotifications);
+    context.getStatistics().assertValue("changesNotifs", expectedIssueChangesNotifications);
+  }
+
+  @Override
+  protected ComputationStep step() {
+    return underTest;
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/UpdateNeedIssueSyncStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/UpdateNeedIssueSyncStepIT.java
new file mode 100644 (file)
index 0000000..07ca29a
--- /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.ce.task.projectanalysis.step;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+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.step.TestComputationStepContext;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchDto;
+import org.sonar.db.component.ComponentDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class UpdateNeedIssueSyncStepIT {
+  private static final Component PROJECT = ReportComponent.DUMB_PROJECT;
+
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule().setRoot(PROJECT);
+
+  private DbClient dbClient = db.getDbClient();
+
+  UpdateNeedIssueSyncStep underTest = new UpdateNeedIssueSyncStep(dbClient, treeRootHolder);
+
+  @Test
+  public void analysis_step_updates_need_issue_sync_flag() {
+    ComponentDto project = db.components()
+      .insertPrivateProject(c -> c.setUuid(PROJECT.getUuid()).setKey(PROJECT.getKey()));
+    dbClient.branchDao().updateNeedIssueSync(db.getSession(), PROJECT.getUuid(), true);
+    db.getSession().commit();
+
+    assertThat(dbClient.branchDao().selectByUuid(db.getSession(), project.uuid()))
+      .isNotEmpty()
+      .map(BranchDto::isNeedIssueSync)
+      .hasValue(true);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbClient.branchDao().selectByUuid(db.getSession(), project.uuid()))
+      .isNotEmpty()
+      .map(BranchDto::isNeedIssueSync)
+      .hasValue(false);
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/UpdateQualityProfilesLastUsedDateStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/UpdateQualityProfilesLastUsedDateStepIT.java
new file mode 100644 (file)
index 0000000..69bcb33
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ * 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.Date;
+import java.util.stream.Collectors;
+import javax.annotation.CheckForNull;
+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.measure.Measure;
+import org.sonar.ce.task.projectanalysis.measure.MeasureRepositoryRule;
+import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule;
+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.RowNotFoundException;
+import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.db.qualityprofile.QualityProfileDbTester;
+import org.sonar.server.qualityprofile.QPMeasureData;
+import org.sonar.server.qualityprofile.QualityProfile;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.sonar.api.measures.CoreMetrics.QUALITY_PROFILES;
+import static org.sonar.api.measures.CoreMetrics.QUALITY_PROFILES_KEY;
+import static org.sonar.db.qualityprofile.QualityProfileTesting.newQualityProfileDto;
+
+public class UpdateQualityProfilesLastUsedDateStepIT {
+  private static final long ANALYSIS_DATE = 1_123_456_789L;
+  private static final Component PROJECT = ReportComponent.DUMB_PROJECT;
+  private QProfileDto sonarWayJava = newProfile("sonar-way-java");
+  private QProfileDto sonarWayPhp = newProfile("sonar-way-php");
+  private QProfileDto myQualityProfile = newProfile("my-qp");
+
+
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+
+  @Rule
+  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule().setAnalysisDate(ANALYSIS_DATE);
+
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule().setRoot(PROJECT);
+
+  @Rule
+  public MetricRepositoryRule metricRepository = new MetricRepositoryRule().add(QUALITY_PROFILES);
+
+  @Rule
+  public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
+
+  private DbClient dbClient = db.getDbClient();
+  private DbSession dbSession = db.getSession();
+  private QualityProfileDbTester qualityProfileDb = new QualityProfileDbTester(db);
+
+  UpdateQualityProfilesLastUsedDateStep underTest = new UpdateQualityProfilesLastUsedDateStep(dbClient, analysisMetadataHolder, treeRootHolder, metricRepository,
+    measureRepository);
+
+  @Test
+  public void doest_not_update_profiles_when_no_measure() {
+    qualityProfileDb.insert(sonarWayJava, sonarWayPhp, myQualityProfile);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertQualityProfileIsTheSame(sonarWayJava);
+    assertQualityProfileIsTheSame(sonarWayPhp);
+    assertQualityProfileIsTheSame(myQualityProfile);
+  }
+
+  @Test
+  public void update_profiles_defined_in_quality_profiles_measure() {
+    qualityProfileDb.insert(sonarWayJava, sonarWayPhp, myQualityProfile);
+
+    measureRepository.addRawMeasure(1, QUALITY_PROFILES_KEY, Measure.newMeasureBuilder().create(
+      toJson(sonarWayJava.getKee(), myQualityProfile.getKee())));
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertQualityProfileIsTheSame(sonarWayPhp);
+    assertQualityProfileIsUpdated(sonarWayJava);
+    assertQualityProfileIsUpdated(myQualityProfile);
+  }
+
+  @Test
+  public void ancestor_profiles_are_updated() {
+    // Parent profiles should be updated
+    QProfileDto rootProfile = newProfile("root");
+    QProfileDto parentProfile = newProfile("parent").setParentKee(rootProfile.getKee());
+    // Current profile => should be updated
+    QProfileDto currentProfile = newProfile("current").setParentKee(parentProfile.getKee());
+    // Child of current profile => should not be updated
+    QProfileDto childProfile = newProfile("child").setParentKee(currentProfile.getKee());
+    qualityProfileDb.insert(rootProfile, parentProfile, currentProfile, childProfile);
+
+    measureRepository.addRawMeasure(1, QUALITY_PROFILES_KEY, Measure.newMeasureBuilder().create(toJson(currentProfile.getKee())));
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertQualityProfileIsUpdated(rootProfile);
+    assertQualityProfileIsUpdated(parentProfile);
+    assertQualityProfileIsUpdated(currentProfile);
+    assertQualityProfileIsTheSame(childProfile);
+  }
+
+  @Test
+  public void fail_when_profile_is_linked_to_unknown_parent() {
+    QProfileDto currentProfile = newProfile("current").setParentKee("unknown");
+    qualityProfileDb.insert(currentProfile);
+
+    measureRepository.addRawMeasure(1, QUALITY_PROFILES_KEY, Measure.newMeasureBuilder().create(toJson(currentProfile.getKee())));
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(RowNotFoundException.class);
+  }
+
+  @Test
+  public void test_description() {
+    assertThat(underTest.getDescription()).isEqualTo("Update last usage date of quality profiles");
+  }
+
+  private static QProfileDto newProfile(String key) {
+    return newQualityProfileDto().setKee(key)
+      // profile has been used before the analysis
+      .setLastUsed(ANALYSIS_DATE - 10_000);
+  }
+
+  private void assertQualityProfileIsUpdated(QProfileDto qp) {
+    assertThat(selectLastUser(qp.getKee())).withFailMessage("Quality profile '%s' hasn't been updated. Value: %d", qp.getKee(), qp.getLastUsed()).isEqualTo(ANALYSIS_DATE);
+  }
+
+  private void assertQualityProfileIsTheSame(QProfileDto qp) {
+    assertThat(selectLastUser(qp.getKee())).isEqualTo(qp.getLastUsed());
+  }
+
+  @CheckForNull
+  private Long selectLastUser(String qualityProfileKey) {
+    return dbClient.qualityProfileDao().selectByUuid(dbSession, qualityProfileKey).getLastUsed();
+  }
+
+  private static String toJson(String... keys) {
+    return QPMeasureData.toJson(new QPMeasureData(
+      Arrays.stream(keys)
+        .map(key -> new QualityProfile(key, key, key, new Date()))
+        .collect(Collectors.toList())));
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ValidateProjectStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ValidateProjectStepIT.java
new file mode 100644 (file)
index 0000000..d9f5a71
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * 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.Date;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.MessageException;
+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.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.DefaultBranchImpl;
+import org.sonar.ce.task.projectanalysis.component.ReportComponent;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
+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.SnapshotTesting;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME;
+
+public class ValidateProjectStepIT {
+  static long PAST_ANALYSIS_TIME = 1_420_088_400_000L; // 2015-01-01
+  static long DEFAULT_ANALYSIS_TIME = 1_433_131_200_000L; // 2015-06-01
+
+  static final String PROJECT_KEY = "PROJECT_KEY";
+  static final Branch DEFAULT_BRANCH = new DefaultBranchImpl(DEFAULT_MAIN_BRANCH_NAME);
+
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+  @Rule
+  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule()
+    .setAnalysisDate(new Date(DEFAULT_ANALYSIS_TIME))
+    .setBranch(DEFAULT_BRANCH);
+
+  private final DbClient dbClient = db.getDbClient();
+
+  private final ValidateProjectStep underTest = new ValidateProjectStep(dbClient, treeRootHolder, analysisMetadataHolder);
+
+  @Test
+  public void not_fail_if_analysis_date_is_after_last_analysis() {
+    ComponentDto project = db.components().insertPrivateProject("ABCD", c -> c.setKey(PROJECT_KEY));
+    dbClient.snapshotDao().insert(db.getSession(), SnapshotTesting.newAnalysis(project).setCreatedAt(PAST_ANALYSIS_TIME));
+    db.getSession().commit();
+
+    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").setKey(PROJECT_KEY).build());
+
+    underTest.execute(new TestComputationStepContext());
+  }
+
+  @Test
+  public void fail_if_analysis_date_is_before_last_analysis() {
+    analysisMetadataHolder.setAnalysisDate(DateUtils.parseDate("2015-01-01"));
+
+    ComponentDto project = db.components().insertPrivateProject("ABCD", c -> c.setKey(PROJECT_KEY));
+    dbClient.snapshotDao().insert(db.getSession(), SnapshotTesting.newAnalysis(project).setCreatedAt(1433131200000L)); // 2015-06-01
+    db.getSession().commit();
+
+    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").setKey(PROJECT_KEY).build());
+
+    var stepContext = new TestComputationStepContext();
+    assertThatThrownBy(() -> underTest.execute(stepContext))
+      .isInstanceOf(MessageException.class)
+      .hasMessageContainingAll("Validation of project failed:",
+        "Date of analysis cannot be older than the date of the last known analysis on this project. Value: ",
+        "Latest analysis: ");
+  }
+
+  @Test
+  public void fail_when_project_key_is_invalid() {
+    ComponentDto project = db.components().insertPrivateProject(p -> p.setKey("inv$lid!"));
+    db.components().insertSnapshot(project, a -> a.setCreatedAt(PAST_ANALYSIS_TIME));
+    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1)
+      .setUuid(project.uuid())
+      .setKey(project.getKey())
+      .build());
+
+    var stepContext = new TestComputationStepContext();
+    assertThatThrownBy(() -> underTest.execute(stepContext))
+      .isInstanceOf(MessageException.class)
+      .hasMessageContainingAll("Validation of project failed:",
+        "The project key ‘inv$lid!’ contains invalid characters.",
+        "Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.",
+        "You should update the project key with the expected format.");
+  }
+
+  private void setBranch(BranchType type, @Nullable String mergeBranchUuid) {
+    Branch branch = mock(Branch.class);
+    when(branch.getType()).thenReturn(type);
+    when(branch.getReferenceBranchUuid()).thenReturn(mergeBranchUuid);
+    analysisMetadataHolder.setBranch(branch);
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ViewsPersistAnalysisStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ViewsPersistAnalysisStepIT.java
new file mode 100644 (file)
index 0000000..3d0ab97
--- /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.ce.task.projectanalysis.step;
+
+import java.util.List;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.DateUtils;
+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.TreeRootHolderRule;
+import org.sonar.ce.task.projectanalysis.component.ViewsComponent;
+import org.sonar.ce.task.projectanalysis.period.Period;
+import org.sonar.ce.task.projectanalysis.period.PeriodHolderRule;
+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.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.component.SnapshotQuery;
+
+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.PROJECT_VIEW;
+import static org.sonar.ce.task.projectanalysis.component.Component.Type.SUBVIEW;
+import static org.sonar.ce.task.projectanalysis.component.Component.Type.VIEW;
+import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
+
+public class ViewsPersistAnalysisStepIT extends BaseStepTest {
+
+  private static final String ANALYSIS_UUID = "U1";
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+  @Rule
+  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
+  @Rule
+  public PeriodHolderRule periodsHolder = new PeriodHolderRule();
+
+  private System2 system2 = mock(System2.class);
+  private DbClient dbClient = dbTester.getDbClient();
+  private long analysisDate;
+  private long now;
+  private PersistAnalysisStep underTest;
+
+  @Before
+  public void setup() {
+    analysisDate = DateUtils.parseDateQuietly("2015-06-01").getTime();
+    analysisMetadataHolder.setUuid(ANALYSIS_UUID);
+    analysisMetadataHolder.setAnalysisDate(analysisDate);
+
+    now = DateUtils.parseDateQuietly("2015-06-02").getTime();
+
+    when(system2.now()).thenReturn(now);
+
+    underTest = new PersistAnalysisStep(system2, dbClient, treeRootHolder, analysisMetadataHolder, periodsHolder);
+
+    // initialize PeriodHolder to empty by default
+    periodsHolder.setPeriod(null);
+  }
+
+  @Override
+  protected ComputationStep step() {
+    return underTest;
+  }
+
+  @Test
+  public void persist_analysis() {
+    ComponentDto viewDto = save(ComponentTesting.newPortfolio("UUID_VIEW").setKey("KEY_VIEW"));
+    save(ComponentTesting.newSubPortfolio(viewDto, "UUID_SUBVIEW", "KEY_SUBVIEW"));
+    save(newPrivateProjectDto("proj"));
+    dbTester.getSession().commit();
+
+    Component projectView = ViewsComponent.builder(PROJECT_VIEW, "KEY_PROJECT_COPY").setUuid("UUID_PROJECT_COPY").build();
+    Component subView = ViewsComponent.builder(SUBVIEW, "KEY_SUBVIEW").setUuid("UUID_SUBVIEW").addChildren(projectView).build();
+    Component view = ViewsComponent.builder(VIEW, "KEY_VIEW").setUuid("UUID_VIEW").addChildren(subView).build();
+    treeRootHolder.setRoot(view);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dbTester.countRowsOfTable("snapshots")).isOne();
+
+    SnapshotDto viewSnapshot = getUnprocessedSnapshot(viewDto.uuid());
+    assertThat(viewSnapshot.getUuid()).isEqualTo(ANALYSIS_UUID);
+    assertThat(viewSnapshot.getComponentUuid()).isEqualTo(view.getUuid());
+    assertThat(viewSnapshot.getProjectVersion()).isNull();
+    assertThat(viewSnapshot.getLast()).isFalse();
+    assertThat(viewSnapshot.getStatus()).isEqualTo("U");
+    assertThat(viewSnapshot.getCreatedAt()).isEqualTo(analysisDate);
+    assertThat(viewSnapshot.getBuildDate()).isEqualTo(now);
+  }
+
+  @Test
+  public void persist_snapshots_with_new_code_period() {
+    ComponentDto viewDto = save(ComponentTesting.newPortfolio("UUID_VIEW").setKey("KEY_VIEW"));
+    ComponentDto subViewDto = save(ComponentTesting.newSubPortfolio(viewDto, "UUID_SUBVIEW", "KEY_SUBVIEW"));
+    dbTester.getSession().commit();
+
+    Component subView = ViewsComponent.builder(SUBVIEW, "KEY_SUBVIEW").setUuid("UUID_SUBVIEW").build();
+    Component view = ViewsComponent.builder(VIEW, "KEY_VIEW").setUuid("UUID_VIEW").addChildren(subView).build();
+    treeRootHolder.setRoot(view);
+
+    periodsHolder.setPeriod(new Period("NUMBER_OF_DAYS", "30", analysisDate));
+
+    underTest.execute(new TestComputationStepContext());
+
+    SnapshotDto viewSnapshot = getUnprocessedSnapshot(viewDto.uuid());
+    assertThat(viewSnapshot.getPeriodMode()).isEqualTo("NUMBER_OF_DAYS");
+    assertThat(viewSnapshot.getPeriodDate()).isEqualTo(analysisDate);
+    assertThat(viewSnapshot.getPeriodModeParameter()).isNotNull();
+  }
+
+  private ComponentDto save(ComponentDto componentDto) {
+    return dbTester.components().insertComponent(componentDto);
+  }
+
+  private SnapshotDto getUnprocessedSnapshot(String componentUuid) {
+    List<SnapshotDto> projectSnapshots = dbClient.snapshotDao().selectAnalysesByQuery(dbTester.getSession(),
+      new SnapshotQuery().setComponentUuid(componentUuid).setIsLast(false).setStatus(SnapshotDto.STATUS_UNPROCESSED));
+    assertThat(projectSnapshots).hasSize(1);
+    return projectSnapshots.get(0);
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ViewsPersistComponentsStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/ViewsPersistComponentsStepIT.java
new file mode 100644 (file)
index 0000000..2cdb776
--- /dev/null
@@ -0,0 +1,554 @@
+/*
+ * 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.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Random;
+import java.util.stream.Stream;
+import javax.annotation.Nullable;
+import org.junit.Before;
+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.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.ce.task.projectanalysis.component.BranchPersister;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.DefaultBranchImpl;
+import org.sonar.ce.task.projectanalysis.component.MutableDisabledComponentsHolder;
+import org.sonar.ce.task.projectanalysis.component.ProjectPersister;
+import org.sonar.ce.task.projectanalysis.component.ProjectViewAttributes;
+import org.sonar.ce.task.projectanalysis.component.SubViewAttributes;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
+import org.sonar.ce.task.projectanalysis.component.ViewAttributes;
+import org.sonar.ce.task.projectanalysis.component.ViewsComponent;
+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.db.component.ComponentDbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.ce.task.projectanalysis.component.ViewAttributes.Type.APPLICATION;
+import static org.sonar.ce.task.projectanalysis.component.ViewAttributes.Type.PORTFOLIO;
+import static org.sonar.ce.task.projectanalysis.component.ViewsComponent.builder;
+import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME;
+import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
+import static org.sonar.db.component.ComponentTesting.newProjectCopy;
+import static org.sonar.db.component.ComponentTesting.newSubPortfolio;
+
+public class ViewsPersistComponentsStepIT extends BaseStepTest {
+
+  private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
+
+  private static final String VIEW_KEY = "VIEW_KEY";
+  private static final String VIEW_NAME = "VIEW_NAME";
+  private static final String VIEW_DESCRIPTION = "view description";
+  private static final String VIEW_UUID = "VIEW_UUID";
+  private static final String SUBVIEW_1_KEY = "SUBVIEW_1_KEY";
+  private static final String SUBVIEW_1_NAME = "SUBVIEW_1_NAME";
+  private static final String SUBVIEW_1_DESCRIPTION = "subview 1 description";
+  private static final String SUBVIEW_1_UUID = "SUBVIEW_1_UUID";
+  private static final String PROJECT_VIEW_1_KEY = "PV1_KEY";
+  private static final String PROJECT_VIEW_1_NAME = "PV1_NAME";
+  private static final String PROJECT_VIEW_1_UUID = "PV1_UUID";
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+  @Rule
+  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
+
+  private final System2 system2 = mock(System2.class);
+  private final DbClient dbClient = dbTester.getDbClient();
+  private Date now;
+  private final ComponentDbTester componentDbTester = new ComponentDbTester(dbTester);
+  private final MutableDisabledComponentsHolder disabledComponentsHolder = mock(MutableDisabledComponentsHolder.class, RETURNS_DEEP_STUBS);
+  private PersistComponentsStep underTest;
+
+  @Before
+  public void setup() throws Exception {
+    now = DATE_FORMAT.parse("2015-06-02");
+    when(system2.now()).thenReturn(now.getTime());
+
+    analysisMetadataHolder.setBranch(new DefaultBranchImpl(DEFAULT_MAIN_BRANCH_NAME));
+    BranchPersister branchPersister = mock(BranchPersister.class);
+    ProjectPersister projectPersister = mock(ProjectPersister.class);
+    underTest = new PersistComponentsStep(dbClient, treeRootHolder, system2, disabledComponentsHolder, analysisMetadataHolder, branchPersister, projectPersister);
+  }
+
+  @Override
+  protected ComputationStep step() {
+    return underTest;
+  }
+
+  @Test
+  public void persist_empty_view() {
+    treeRootHolder.setRoot(createViewBuilder(PORTFOLIO).build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertRowsCountInTableProjects(1);
+
+    ComponentDto projectDto = getComponentFromDb(VIEW_KEY);
+    assertDtoIsView(projectDto);
+  }
+
+  @Test
+  public void persist_existing_empty_view() {
+    // most of the time view already exists since its supposed to be created when config is uploaded
+    persistComponents(newViewDto());
+
+    treeRootHolder.setRoot(createViewBuilder(PORTFOLIO).build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertRowsCountInTableProjects(1);
+
+    assertDtoNotUpdated(VIEW_KEY);
+  }
+
+  @Test
+  public void persist_view_with_projectView() {
+    ComponentDto project = ComponentTesting.newPrivateProjectDto();
+    persistComponents(project);
+
+    treeRootHolder.setRoot(
+      createViewBuilder(PORTFOLIO)
+        .addChildren(createProjectView1Builder(project, null).build())
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertRowsCountInTableProjects(3);
+
+    ComponentDto viewDto = getComponentFromDb(VIEW_KEY);
+    assertDtoIsView(viewDto);
+
+    ComponentDto pv1Dto = getComponentFromDb(PROJECT_VIEW_1_KEY);
+    assertDtoIsProjectView1(pv1Dto, viewDto, viewDto, project);
+  }
+
+  @Test
+  public void persist_application_with_projectView() {
+    ComponentDto project = ComponentTesting.newPrivateProjectDto();
+    persistComponents(project);
+
+    treeRootHolder.setRoot(
+      createViewBuilder(APPLICATION)
+        .addChildren(createProjectView1Builder(project, null).build())
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertRowsCountInTableProjects(3);
+
+    ComponentDto applicationDto = getComponentFromDb(VIEW_KEY);
+    assertDtoIsApplication(applicationDto);
+
+    ComponentDto pv1Dto = getComponentFromDb(PROJECT_VIEW_1_KEY);
+    assertDtoIsProjectView1(pv1Dto, applicationDto, applicationDto, project);
+  }
+
+  @Test
+  public void persist_empty_subview() {
+    treeRootHolder.setRoot(
+      createViewBuilder(PORTFOLIO)
+        .addChildren(
+          createSubView1Builder(null).build())
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertRowsCountInTableProjects(2);
+
+    ComponentDto viewDto = getComponentFromDb(VIEW_KEY);
+    assertDtoIsView(viewDto);
+
+    ComponentDto sv1Dto = getComponentFromDb(SUBVIEW_1_KEY);
+    assertDtoIsSubView1(viewDto, sv1Dto);
+  }
+
+  @Test
+  public void persist_empty_subview_having_original_view_uuid() {
+    treeRootHolder.setRoot(
+      createViewBuilder(PORTFOLIO)
+        .addChildren(
+          createSubView1Builder("ORIGINAL_UUID").build())
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertRowsCountInTableProjects(2);
+
+    ComponentDto subView = getComponentFromDb(SUBVIEW_1_KEY);
+    assertThat(subView.getCopyComponentUuid()).isEqualTo("ORIGINAL_UUID");
+  }
+
+  @Test
+  public void persist_existing_empty_subview_under_existing_view() {
+    ComponentDto viewDto = newViewDto();
+    persistComponents(viewDto);
+    persistComponents(ComponentTesting.newSubPortfolio(viewDto, SUBVIEW_1_UUID, SUBVIEW_1_KEY).setName(SUBVIEW_1_NAME));
+
+    treeRootHolder.setRoot(
+      createViewBuilder(PORTFOLIO)
+        .addChildren(
+          createSubView1Builder(null).build())
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertRowsCountInTableProjects(2);
+
+    assertDtoNotUpdated(VIEW_KEY);
+    assertDtoNotUpdated(SUBVIEW_1_KEY);
+  }
+
+  @Test
+  public void persist_empty_subview_under_existing_view() {
+    persistComponents(newViewDto());
+
+    treeRootHolder.setRoot(
+      createViewBuilder(PORTFOLIO)
+        .addChildren(
+          createSubView1Builder(null).build())
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertRowsCountInTableProjects(2);
+
+    assertDtoNotUpdated(VIEW_KEY);
+    assertDtoIsSubView1(getComponentFromDb(VIEW_KEY), getComponentFromDb(SUBVIEW_1_KEY));
+  }
+
+  @Test
+  public void persist_project_view_under_subview() {
+    ComponentDto project = ComponentTesting.newPrivateProjectDto();
+    persistComponents(project);
+
+    treeRootHolder.setRoot(
+      createViewBuilder(PORTFOLIO)
+        .addChildren(
+          createSubView1Builder(null)
+            .addChildren(
+              createProjectView1Builder(project, null).build())
+            .build())
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertRowsCountInTableProjects(4);
+
+    ComponentDto viewDto = getComponentFromDb(VIEW_KEY);
+    assertDtoIsView(viewDto);
+    ComponentDto subView1Dto = getComponentFromDb(SUBVIEW_1_KEY);
+    assertDtoIsSubView1(viewDto, subView1Dto);
+    ComponentDto pv1Dto = getComponentFromDb(PROJECT_VIEW_1_KEY);
+    assertDtoIsProjectView1(pv1Dto, viewDto, subView1Dto, project);
+  }
+
+  @Test
+  public void update_view_name_and_longName() {
+    ComponentDto viewDto = newViewDto().setLongName("another long name").setCreatedAt(now);
+    persistComponents(viewDto);
+
+    treeRootHolder.setRoot(createViewBuilder(PORTFOLIO).build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    // commit functional transaction -> copies B-fields to A-fields
+    dbClient.componentDao().applyBChangesForBranchUuid(dbTester.getSession(), viewDto.uuid());
+    dbTester.commit();
+
+    assertRowsCountInTableProjects(1);
+    ComponentDto newViewDto = getComponentFromDb(VIEW_KEY);
+    assertDtoIsView(newViewDto);
+  }
+
+  @Test
+  public void update_project_view() {
+    ComponentDto view = newViewDto();
+    ComponentDto project = ComponentTesting.newPrivateProjectDto();
+    persistComponents(view, project);
+    ComponentDto projectView = ComponentTesting.newProjectCopy(PROJECT_VIEW_1_UUID, project, view)
+      .setKey(PROJECT_VIEW_1_KEY)
+      .setName("Old name")
+      .setCreatedAt(now);
+    persistComponents(projectView);
+
+    treeRootHolder.setRoot(
+      createViewBuilder(PORTFOLIO)
+        .addChildren(createProjectView1Builder(project, null).build())
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    // commit functional transaction -> copies B-fields to A-fields
+    dbClient.componentDao().applyBChangesForBranchUuid(dbTester.getSession(), view.uuid());
+    dbTester.commit();
+
+    assertRowsCountInTableProjects(3);
+    ComponentDto pv1Dto = getComponentFromDb(PROJECT_VIEW_1_KEY);
+    assertDtoIsProjectView1(pv1Dto, view, view, project);
+  }
+
+  @Test
+  public void update_copy_component_uuid_of_project_view() {
+    ComponentDto view = newViewDto();
+    ComponentDto project1 = newPrivateProjectDto("P1");
+    ComponentDto project2 = newPrivateProjectDto("P2");
+    persistComponents(view, project1, project2);
+
+    // Project view in DB is associated to project1
+    ComponentDto projectView = ComponentTesting.newProjectCopy(PROJECT_VIEW_1_UUID, project1, view)
+      .setKey(PROJECT_VIEW_1_KEY)
+      .setCreatedAt(now);
+    persistComponents(projectView);
+
+    treeRootHolder.setRoot(
+      createViewBuilder(PORTFOLIO)
+        // Project view in the View is linked to the first project2
+        .addChildren(createProjectView1Builder(project2, null).build())
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    // commit functional transaction -> copies B-fields to A-fields
+    dbClient.componentDao().applyBChangesForBranchUuid(dbTester.getSession(), view.uuid());
+    dbTester.commit();
+
+    ComponentDto pv1Dto = getComponentFromDb(PROJECT_VIEW_1_KEY);
+    // Project view should now be linked to project2
+    assertDtoIsProjectView1(pv1Dto, view, view, project2);
+  }
+
+  @Test
+  public void update_copy_component_uuid_of_sub_view() {
+    ComponentDto view = newViewDto();
+    ComponentDto subView = newSubViewDto(view).setCopyComponentUuid("OLD_COPY");
+    persistComponents(view, subView);
+
+    treeRootHolder.setRoot(
+      createViewBuilder(PORTFOLIO)
+        .addChildren(
+          createSubView1Builder("NEW_COPY").build())
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    // commit functional transaction -> copies B-fields to A-fields
+    dbClient.componentDao().applyBChangesForBranchUuid(dbTester.getSession(), view.uuid());
+    dbTester.commit();
+
+    ComponentDto subViewReloaded = getComponentFromDb(SUBVIEW_1_KEY);
+    assertThat(subViewReloaded.getCopyComponentUuid()).isEqualTo("NEW_COPY");
+  }
+
+  @Test
+  public void persists_new_components_as_public_if_root_does_not_exist_yet_out_of_functional_transaction() {
+    ComponentDto project = dbTester.components().insertComponent(ComponentTesting.newPrivateProjectDto());
+    treeRootHolder.setRoot(
+      createViewBuilder(PORTFOLIO)
+        .addChildren(
+          createSubView1Builder(null)
+            .addChildren(
+              createProjectView1Builder(project, null).build())
+            .build())
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    Stream.of(VIEW_UUID, SUBVIEW_1_UUID, PROJECT_VIEW_1_UUID)
+      .forEach(uuid -> assertThat(dbClient.componentDao().selectByUuid(dbTester.getSession(), uuid).get().isPrivate()).isFalse());
+  }
+
+  @Test
+  public void persists_new_components_with_visibility_of_root_in_db_out_of_functional_transaction() {
+    boolean isRootPrivate = new Random().nextBoolean();
+    ComponentDto project = dbTester.components().insertComponent(ComponentTesting.newPrivateProjectDto());
+    ComponentDto view = newViewDto().setUuid(VIEW_UUID).setKey(VIEW_KEY).setName("View").setPrivate(isRootPrivate);
+    dbTester.components().insertComponent(view);
+    treeRootHolder.setRoot(
+      createViewBuilder(PORTFOLIO)
+        .addChildren(
+          createSubView1Builder(null)
+            .addChildren(
+              createProjectView1Builder(project, null).build())
+            .build())
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    Stream.of(VIEW_UUID, SUBVIEW_1_UUID, PROJECT_VIEW_1_UUID)
+      .forEach(uuid -> assertThat(dbClient.componentDao().selectByUuid(dbTester.getSession(), uuid).get().isPrivate())
+        .describedAs("for uuid " + uuid)
+        .isEqualTo(isRootPrivate));
+  }
+
+  @Test
+  public void persists_existing_components_with_visibility_of_root_in_db_out_of_functional_transaction() {
+    boolean isRootPrivate = new Random().nextBoolean();
+    ComponentDto project = dbTester.components().insertComponent(ComponentTesting.newPrivateProjectDto());
+    ComponentDto view = newViewDto().setUuid(VIEW_UUID).setKey(VIEW_KEY).setName("View").setPrivate(isRootPrivate);
+    dbTester.components().insertComponent(view);
+    ComponentDto subView = newSubPortfolio(view).setUuid("BCDE").setKey("MODULE").setPrivate(!isRootPrivate);
+    dbTester.components().insertComponent(subView);
+    dbTester.components().insertComponent(newProjectCopy("DEFG", project, view).setKey("DIR").setPrivate(isRootPrivate));
+    treeRootHolder.setRoot(
+      createViewBuilder(PORTFOLIO)
+        .addChildren(
+          createSubView1Builder(null)
+            .addChildren(
+              createProjectView1Builder(project, null).build())
+            .build())
+        .build());
+
+    underTest.execute(new TestComputationStepContext());
+
+    Stream.of(VIEW_UUID, SUBVIEW_1_UUID, PROJECT_VIEW_1_UUID, subView.uuid(), "DEFG")
+      .forEach(uuid -> assertThat(dbClient.componentDao().selectByUuid(dbTester.getSession(), uuid).get().isPrivate())
+        .describedAs("for uuid " + uuid)
+        .isEqualTo(isRootPrivate));
+  }
+
+  private static ViewsComponent.Builder createViewBuilder(ViewAttributes.Type viewType) {
+    return builder(Component.Type.VIEW, VIEW_KEY)
+      .setUuid(VIEW_UUID)
+      .setName(VIEW_NAME)
+      .setDescription(VIEW_DESCRIPTION)
+      .setViewAttributes(new ViewAttributes(viewType));
+  }
+
+  private ViewsComponent.Builder createSubView1Builder(@Nullable String originalViewUuid) {
+    return builder(Component.Type.SUBVIEW, SUBVIEW_1_KEY)
+      .setUuid(SUBVIEW_1_UUID)
+      .setName(SUBVIEW_1_NAME)
+      .setDescription(SUBVIEW_1_DESCRIPTION)
+      .setSubViewAttributes(new SubViewAttributes(originalViewUuid));
+  }
+
+  private static ViewsComponent.Builder createProjectView1Builder(ComponentDto project, Long analysisDate) {
+    return builder(Component.Type.PROJECT_VIEW, PROJECT_VIEW_1_KEY)
+      .setUuid(PROJECT_VIEW_1_UUID)
+      .setName(PROJECT_VIEW_1_NAME)
+      .setDescription("project view description is not persisted")
+      .setProjectViewAttributes(new ProjectViewAttributes(project.uuid(), project.getKey(), analysisDate, null));
+  }
+
+  private void persistComponents(ComponentDto... componentDtos) {
+    componentDbTester.insertComponents(componentDtos);
+  }
+
+  private ComponentDto getComponentFromDb(String componentKey) {
+    return dbClient.componentDao().selectByKey(dbTester.getSession(), componentKey).get();
+  }
+
+  private void assertRowsCountInTableProjects(int rowCount) {
+    assertThat(dbTester.countRowsOfTable("components")).isEqualTo(rowCount);
+  }
+
+  private void assertDtoNotUpdated(String componentKey) {
+    assertThat(getComponentFromDb(componentKey).getCreatedAt()).isNotEqualTo(now);
+  }
+
+  private ComponentDto newViewDto() {
+    return ComponentTesting.newPortfolio(VIEW_UUID)
+      .setKey(VIEW_KEY)
+      .setName(VIEW_NAME);
+  }
+
+  private ComponentDto newSubViewDto(ComponentDto rootView) {
+    return ComponentTesting.newSubPortfolio(rootView, SUBVIEW_1_UUID, SUBVIEW_1_KEY)
+      .setName(SUBVIEW_1_NAME);
+  }
+
+  /**
+   * Assertions to verify the DTO created from {@link #createViewBuilder(ViewAttributes.Type)} ()}
+   */
+  private void assertDtoIsView(ComponentDto dto) {
+    assertThat(dto.name()).isEqualTo(VIEW_NAME);
+    assertThat(dto.longName()).isEqualTo(VIEW_NAME);
+    assertThat(dto.description()).isEqualTo(VIEW_DESCRIPTION);
+    assertThat(dto.path()).isNull();
+    assertThat(dto.uuid()).isEqualTo(VIEW_UUID);
+    assertThat(dto.branchUuid()).isEqualTo(VIEW_UUID);
+    assertThat(dto.qualifier()).isEqualTo(Qualifiers.VIEW);
+    assertThat(dto.scope()).isEqualTo(Scopes.PROJECT);
+    assertThat(dto.getCopyComponentUuid()).isNull();
+    assertThat(dto.getCreatedAt()).isEqualTo(now);
+  }
+
+  /**
+   * Assertions to verify the DTO created from {@link #createViewBuilder(ViewAttributes.Type)} ()}
+   */
+  private void assertDtoIsApplication(ComponentDto dto) {
+    assertThat(dto.name()).isEqualTo(VIEW_NAME);
+    assertThat(dto.longName()).isEqualTo(VIEW_NAME);
+    assertThat(dto.description()).isEqualTo(VIEW_DESCRIPTION);
+    assertThat(dto.path()).isNull();
+    assertThat(dto.uuid()).isEqualTo(VIEW_UUID);
+    assertThat(dto.branchUuid()).isEqualTo(VIEW_UUID);
+    assertThat(dto.qualifier()).isEqualTo(Qualifiers.APP);
+    assertThat(dto.scope()).isEqualTo(Scopes.PROJECT);
+    assertThat(dto.getCopyComponentUuid()).isNull();
+    assertThat(dto.getCreatedAt()).isEqualTo(now);
+  }
+
+  /**
+   * Assertions to verify the DTO created from {@link #createProjectView1Builder(ComponentDto, Long)}
+   */
+  private void assertDtoIsSubView1(ComponentDto viewDto, ComponentDto sv1Dto) {
+    assertThat(sv1Dto.name()).isEqualTo(SUBVIEW_1_NAME);
+    assertThat(sv1Dto.longName()).isEqualTo(SUBVIEW_1_NAME);
+    assertThat(sv1Dto.description()).isEqualTo(SUBVIEW_1_DESCRIPTION);
+    assertThat(sv1Dto.path()).isNull();
+    assertThat(sv1Dto.uuid()).isEqualTo(SUBVIEW_1_UUID);
+    assertThat(sv1Dto.branchUuid()).isEqualTo(viewDto.uuid());
+    assertThat(sv1Dto.qualifier()).isEqualTo(Qualifiers.SUBVIEW);
+    assertThat(sv1Dto.scope()).isEqualTo(Scopes.PROJECT);
+    assertThat(sv1Dto.getCopyComponentUuid()).isNull();
+    assertThat(sv1Dto.getCreatedAt()).isEqualTo(now);
+  }
+
+  private void assertDtoIsProjectView1(ComponentDto pv1Dto, ComponentDto viewDto, ComponentDto parentViewDto, ComponentDto project) {
+    assertThat(pv1Dto.name()).isEqualTo(PROJECT_VIEW_1_NAME);
+    assertThat(pv1Dto.longName()).isEqualTo(PROJECT_VIEW_1_NAME);
+    assertThat(pv1Dto.description()).isNull();
+    assertThat(pv1Dto.path()).isNull();
+    assertThat(pv1Dto.uuid()).isEqualTo(PROJECT_VIEW_1_UUID);
+    assertThat(pv1Dto.branchUuid()).isEqualTo(viewDto.uuid());
+    assertThat(pv1Dto.qualifier()).isEqualTo(Qualifiers.PROJECT);
+    assertThat(pv1Dto.scope()).isEqualTo(Scopes.FILE);
+    assertThat(pv1Dto.getCopyComponentUuid()).isEqualTo(project.uuid());
+    assertThat(pv1Dto.getCreatedAt()).isEqualTo(now);
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/taskprocessor/AuditHousekeepingFrequencyHelperIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/taskprocessor/AuditHousekeepingFrequencyHelperIT.java
new file mode 100644 (file)
index 0000000..6591b6d
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * 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 com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.sonar.api.impl.utils.TestSystem2;
+import org.sonar.api.utils.System2;
+import org.sonar.core.config.Frequency;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.property.PropertiesDao;
+import org.sonar.db.property.PropertyDto;
+
+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.core.config.PurgeConstants.AUDIT_HOUSEKEEPING_FREQUENCY;
+import static org.sonar.core.config.PurgeProperties.DEFAULT_FREQUENCY;
+
+@RunWith(DataProviderRunner.class)
+public class AuditHousekeepingFrequencyHelperIT {
+  private static final long NOW = 10_000_000_000L;
+
+  private final DbClient dbClient = mock(DbClient.class);
+  private final DbSession dbSession = mock(DbSession.class);
+  private final PropertiesDao propertiesDao = mock(PropertiesDao.class);
+  private final System2 system2 = new TestSystem2().setNow(NOW);
+  private final AuditHousekeepingFrequencyHelper underTest = new AuditHousekeepingFrequencyHelper(system2);
+
+  @Test
+  @UseDataProvider("frequencyOptions")
+  public void getThresholdDate(Frequency frequency) {
+    long result = underTest.getThresholdDate(frequency.getDescription());
+
+
+    long expected = Instant.ofEpochMilli(system2.now())
+      .minus(frequency.getDays(), ChronoUnit.DAYS)
+      .toEpochMilli();
+
+    assertThat(result).isEqualTo(expected);
+  }
+
+  @Test
+  public void getThresholdDateForUnknownFrequencyFails() {
+    assertThatThrownBy(() -> underTest.getThresholdDate("Lalala"))
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Unsupported frequency: Lalala");
+  }
+
+  @Test
+  public void getHouseKeepingFrequency() {
+    String value = "Weekly";
+    PropertyDto propertyDto = new PropertyDto().setKey(AUDIT_HOUSEKEEPING_FREQUENCY).setValue(value);
+    when(dbClient.propertiesDao()).thenReturn(propertiesDao);
+    when(propertiesDao
+      .selectGlobalProperty(dbSession, AUDIT_HOUSEKEEPING_FREQUENCY))
+      .thenReturn(propertyDto);
+    assertThat(underTest.getHouseKeepingFrequency(dbClient, dbSession).getValue()).isEqualTo(value);
+  }
+
+  @Test
+  public void getDefaultHouseKeepingFrequencyWhenNotSet() {
+    when(dbClient.propertiesDao()).thenReturn(propertiesDao);
+    when(propertiesDao
+      .selectGlobalProperty(dbSession, AUDIT_HOUSEKEEPING_FREQUENCY))
+      .thenReturn(null);
+    assertThat(underTest.getHouseKeepingFrequency(dbClient, dbSession).getValue())
+      .isEqualTo(DEFAULT_FREQUENCY);
+  }
+
+  @DataProvider
+  public static Object[][] frequencyOptions() {
+    return new Object[][] {
+      {Frequency.WEEKLY},
+      {Frequency.MONTHLY},
+      {Frequency.TRIMESTRIAL},
+      {Frequency.YEARLY}
+    };
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/taskprocessor/AuditPurgeStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/taskprocessor/AuditPurgeStepIT.java
new file mode 100644 (file)
index 0000000..e24e304
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * 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.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import org.junit.Before;
+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.audit.AuditDto;
+import org.sonar.db.audit.AuditTesting;
+import org.sonar.db.property.PropertyDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.core.config.Frequency.MONTHLY;
+import static org.sonar.core.config.PurgeConstants.AUDIT_HOUSEKEEPING_FREQUENCY;
+
+public class AuditPurgeStepIT {
+  private final static long NOW = 1_400_000_000_000L;
+  private final static long BEFORE = 1_300_000_000_000L;
+  private final static long LATER = 1_500_000_000_000L;
+  private final static ZonedDateTime thresholdDate = Instant.ofEpochMilli(NOW)
+    .atZone(ZoneId.systemDefault());
+  private final static PropertyDto FREQUENCY_PROPERTY = new PropertyDto()
+    .setKey(AUDIT_HOUSEKEEPING_FREQUENCY)
+    .setValue(MONTHLY.name());
+
+  @Rule
+  public final DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private final DbClient dbClient = dbTester.getDbClient();
+
+  private final System2 system2 = new System2();
+
+  @Rule
+  public final DbTester db = DbTester.create(system2);
+
+  private final AuditHousekeepingFrequencyHelper auditHousekeepingFrequencyHelper = mock(AuditHousekeepingFrequencyHelper.class);
+
+  private final AuditPurgeStep underTest = new AuditPurgeStep(auditHousekeepingFrequencyHelper, dbClient);
+
+  @Before
+  public void setUp() {
+    when(auditHousekeepingFrequencyHelper.getHouseKeepingFrequency(any(), any())).thenReturn(FREQUENCY_PROPERTY);
+    when(auditHousekeepingFrequencyHelper.getThresholdDate(anyString())).thenReturn(NOW);
+  }
+
+  @Test
+  public void executeDeletesOlderAudits() {
+    prepareRowsWithDeterministicCreatedAt();
+    assertThat(dbClient.auditDao().selectOlderThan(db.getSession(), LATER + 1)).hasSize(3);
+
+    underTest.execute(() -> null);
+
+    assertThat(dbClient.auditDao().selectOlderThan(db.getSession(), LATER + 1)).hasSize(2);
+  }
+
+  @Test
+  public void getDescription() {
+    assertThat(underTest.getDescription()).isEqualTo("Purge Audit Logs");
+  }
+
+  private void prepareRowsWithDeterministicCreatedAt() {
+    insertAudit(BEFORE);
+    insertAudit(NOW);
+    insertAudit(LATER);
+    db.getSession().commit();
+  }
+
+  private void insertAudit(long timestamp) {
+    AuditDto auditDto = AuditTesting.newAuditDto(timestamp);
+    dbClient.auditDao().insert(db.getSession(), auditDto);
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/taskprocessor/IndexIssuesStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/taskprocessor/IndexIssuesStepIT.java
new file mode 100644 (file)
index 0000000..4264f71
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * 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 org.sonar.server.es.EsTester;
+import org.sonar.server.issue.index.IssueIndexer;
+
+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.times;
+import static org.mockito.Mockito.verify;
+import static org.sonar.db.component.BranchType.BRANCH;
+
+public class IndexIssuesStepIT {
+
+  private String BRANCH_UUID = "branch_uuid";
+
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private DbClient dbClient = dbTester.getDbClient();
+
+  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();
+
+  @Rule
+  public EsTester es = EsTester.create();
+  private System2 system2 = new System2();
+  @Rule
+  public DbTester db = DbTester.create(system2);
+
+  private IssueIndexer issueIndexer = mock(IssueIndexer.class);
+
+  private IndexIssuesStep underTest = new IndexIssuesStep(ceTask, dbClient, issueIndexer);
+
+  @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);
+
+    verify(issueIndexer, times(1)).indexOnAnalysis(BRANCH_UUID);
+    Optional<BranchDto> branch = dbClient.branchDao().selectByUuid(dbTester.getSession(), BRANCH_UUID);
+    assertThat(branch.get().isNeedIssueSync()).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);
+
+    verify(issueIndexer, times(0)).indexOnAnalysis(BRANCH_UUID);
+  }
+
+  @Test
+  public void fail_if_missing_component_in_task() {
+    CeTask ceTask = new CeTask.Builder()
+      .setType("type")
+      .setUuid("uuid")
+      .setComponent(null)
+      .setMainComponent(null)
+      .build();
+    IndexIssuesStep underTest = new IndexIssuesStep(ceTask, dbClient, issueIndexer);
+
+    assertThatThrownBy(() -> underTest.execute(() -> null))
+      .isInstanceOf(UnsupportedOperationException.class)
+      .hasMessage("component not found in task");
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/analysis/ExportAnalysesStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/analysis/ExportAnalysesStepIT.java
new file mode 100644 (file)
index 0000000..c69ac17
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * 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.analysis;
+
+import com.sonarsource.governance.projectdump.protobuf.ProjectDump;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+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.component.ComponentRepositoryImpl;
+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 org.sonar.db.component.SnapshotDto;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.apache.commons.lang.StringUtils.defaultString;
+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.db.component.ComponentDto.UUID_PATH_OF_ROOT;
+import static org.sonar.db.component.ComponentDto.UUID_PATH_SEPARATOR;
+
+@RunWith(DataProviderRunner.class)
+public class ExportAnalysesStepIT {
+
+  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)
+    .setBranchUuid(PROJECT_UUID);
+
+  private static final String DIR_UUID = "DIR_UUID";
+  private static final String UUID_PATH = UUID_PATH_OF_ROOT + UUID_PATH_SEPARATOR + DIR_UUID;
+  private static final ComponentDto DIR = new ComponentDto()
+    // no id yet
+    .setScope(Scopes.PROJECT)
+    .setQualifier(Qualifiers.DIRECTORY)
+    .setKey("the_dir")
+    .setName("The Dir")
+    .setDescription("description of dir")
+    .setEnabled(true)
+    .setUuid(DIR_UUID)
+    .setUuidPath(UUID_PATH)
+    .setBranchUuid(PROJECT_UUID);
+
+  private static final String FILE_UUID = "FILE_UUID";
+  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(UUID_PATH + UUID_PATH_SEPARATOR + FILE_UUID)
+    .setEnabled(true)
+    .setBranchUuid(PROJECT_UUID);
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  private final ComponentRepositoryImpl componentRepository = new ComponentRepositoryImpl();
+  private final FakeDumpWriter dumpWriter = new FakeDumpWriter();
+  private final ProjectHolder projectHolder = mock(ProjectHolder.class);
+  private final ExportAnalysesStep underTest = new ExportAnalysesStep(dbTester.getDbClient(), projectHolder, componentRepository, dumpWriter);
+
+  @Before
+  public void setUp() {
+    ComponentDto projectDto = dbTester.components().insertPublicProject(PROJECT);
+    componentRepository.register(1, projectDto.uuid(), false);
+    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), DIR, FILE);
+    dbTester.commit();
+    when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(projectDto));
+  }
+
+  @Test
+  public void getDescription_is_defined() {
+    assertThat(underTest.getDescription()).isEqualTo("Export analyses");
+  }
+
+  @Test
+  @UseDataProvider("versionAndBuildStringCombinations")
+  public void export_analyses(@Nullable String version, @Nullable String buildString) {
+    SnapshotDto firstAnalysis = newAnalysis("U_1", 1_450_000_000_000L, PROJECT.uuid(), "1.0", false, "1.0.2.3", 1_450_000_000_000L);
+    SnapshotDto secondAnalysis = newAnalysis("U_4", 1_460_000_000_000L, PROJECT.uuid(), "1.1", true, "1.1.3.4", 1_460_000_000_000L);
+    SnapshotDto thirdAnalysis = newAnalysis("U_7", 1_460_000_000_000L, PROJECT.uuid(), version, true, buildString, 1_470_000_000_000L);
+    dbTester.getDbClient().snapshotDao().insert(dbTester.getSession(), firstAnalysis, secondAnalysis, thirdAnalysis);
+    dbTester.commit();
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("3 analyses exported");
+    List<ProjectDump.Analysis> analyses = dumpWriter.getWrittenMessagesOf(DumpElement.ANALYSES);
+    assertThat(analyses).hasSize(3);
+    assertAnalysis(analyses.get(0), PROJECT, firstAnalysis);
+    assertAnalysis(analyses.get(1), PROJECT, secondAnalysis);
+    assertAnalysis(analyses.get(2), PROJECT, thirdAnalysis);
+  }
+
+  @DataProvider
+  public static Object[][] versionAndBuildStringCombinations() {
+    String version = randomAlphabetic(7);
+    String buildString = randomAlphabetic(12);
+    return new Object[][] {
+      {null, null},
+      {version, null},
+      {null, buildString},
+      {version, buildString},
+      {"", ""},
+      {version, ""},
+      {"", buildString},
+    };
+  }
+
+  @Test
+  public void export_analyses_by_ordering_by_technical_creation_date() {
+    SnapshotDto firstAnalysis = newAnalysis("U_1", 1_450_000_000_000L, PROJECT.uuid(), "1.0", false, "1.0.2.3", 3_000_000_000_000L);
+    SnapshotDto secondAnalysis = newAnalysis("U_4", 1_460_000_000_000L, PROJECT.uuid(), "1.1", true, "1.1.3.4", 1_000_000_000_000L);
+    SnapshotDto thirdAnalysis = newAnalysis("U_7", 1_460_500_000_000L, PROJECT.uuid(), null, true, null, 2_000_000_000_000L);
+    dbTester.getDbClient().snapshotDao().insert(dbTester.getSession(), firstAnalysis, secondAnalysis, thirdAnalysis);
+    dbTester.commit();
+
+    underTest.execute(new TestComputationStepContext());
+
+    List<ProjectDump.Analysis> analyses = dumpWriter.getWrittenMessagesOf(DumpElement.ANALYSES);
+    assertAnalysis(analyses.get(0), PROJECT, secondAnalysis);
+    assertAnalysis(analyses.get(1), PROJECT, thirdAnalysis);
+    assertAnalysis(analyses.get(2), PROJECT, firstAnalysis);
+  }
+
+  @Test
+  public void export_provisioned_projects_without_any_analyses() {
+    underTest.execute(new TestComputationStepContext());
+
+    List<ProjectDump.Analysis> analyses = dumpWriter.getWrittenMessagesOf(DumpElement.ANALYSES);
+    assertThat(analyses).isEmpty();
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("0 analyses exported");
+  }
+
+  @Test
+  public void throws_ISE_if_error() {
+    SnapshotDto firstAnalysis = newAnalysis("U_1", 1_450_000_000_000L, PROJECT.uuid(), "1.0", false, "1.0.2.3", 1);
+    SnapshotDto secondAnalysis = newAnalysis("U_4", 1_460_000_000_000L, PROJECT.uuid(), "1.1", true, "1.1.3.4", 2);
+    dbTester.getDbClient().snapshotDao().insert(dbTester.getSession(), firstAnalysis, secondAnalysis);
+    dbTester.commit();
+    dumpWriter.failIfMoreThan(1, DumpElement.ANALYSES);
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Analysis Export failed after processing 1 analyses successfully");
+  }
+
+  private static SnapshotDto newAnalysis(String uuid, long date, String componentUuid, @Nullable String version, boolean isLast, @Nullable String buildString, long buildDate) {
+    return new SnapshotDto()
+      .setUuid(uuid)
+      .setCreatedAt(date)
+      .setComponentUuid(componentUuid)
+      .setProjectVersion(version)
+      .setBuildString(buildString)
+      .setLast(isLast)
+      .setStatus(SnapshotDto.STATUS_PROCESSED)
+      .setBuildDate(buildDate);
+  }
+
+  private static void assertAnalysis(ProjectDump.Analysis analysis, ComponentDto component, SnapshotDto dto) {
+    assertThat(analysis.getUuid()).isEqualTo(dto.getUuid());
+    assertThat(analysis.getComponentRef()).isOne();
+    assertThat(analysis.getDate()).isEqualTo(dto.getCreatedAt());
+    assertThat(analysis.getProjectVersion()).isEqualTo(defaultString(dto.getProjectVersion()));
+    assertThat(analysis.getBuildString()).isEqualTo(defaultString(dto.getBuildString()));
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/branches/ExportBranchesStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/branches/ExportBranchesStepIT.java
new file mode 100644 (file)
index 0000000..be7dd48
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ * 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.branches;
+
+import com.google.common.collect.ImmutableList;
+import com.sonarsource.governance.projectdump.protobuf.ProjectDump;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import org.apache.commons.lang.time.DateUtils;
+import org.junit.Before;
+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.BranchDto;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.project.ProjectExportMapper;
+
+import static java.util.stream.Collectors.toMap;
+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.db.component.ComponentDto.UUID_PATH_OF_ROOT;
+
+public class ExportBranchesStepIT {
+  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)
+    .setBranchUuid(PROJECT_UUID);
+
+  @Rule
+  public DbTester dbTester = DbTester.createWithExtensionMappers(System2.INSTANCE, ProjectExportMapper.class);
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  private final ProjectHolder projectHolder = mock(ProjectHolder.class);
+  private final FakeDumpWriter dumpWriter = new FakeDumpWriter();
+  private final ExportBranchesStep underTest = new ExportBranchesStep(dumpWriter, dbTester.getDbClient(), projectHolder);
+  private final List<BranchDto> branches = ImmutableList.of(
+    new BranchDto()
+      .setBranchType(BranchType.BRANCH)
+      .setProjectUuid(PROJECT_UUID)
+      .setKey("branch-1")
+      .setUuid("branch-1-uuid")
+      .setMergeBranchUuid("master")
+      .setExcludeFromPurge(true),
+    new BranchDto()
+      .setBranchType(BranchType.PULL_REQUEST)
+      .setProjectUuid(PROJECT_UUID)
+      .setKey("branch-3")
+      .setUuid("branch-3-uuid")
+      .setMergeBranchUuid("master"),
+    new BranchDto()
+      .setBranchType(BranchType.BRANCH)
+      .setProjectUuid(PROJECT_UUID)
+      .setKey("branch-4")
+      .setUuid("branch-4-uuid")
+      .setMergeBranchUuid("branch-1-uuid"),
+    new BranchDto()
+      .setBranchType(BranchType.BRANCH)
+      .setProjectUuid(PROJECT_UUID)
+      .setKey("branch-5")
+      .setUuid("branch-5-uuid")
+      .setMergeBranchUuid("master")
+      .setExcludeFromPurge(true));
+
+  @Before
+  public void setUp() {
+    Date createdAt = new Date();
+    ComponentDto projectDto = dbTester.components().insertPublicProject(PROJECT).setCreatedAt(createdAt);
+    for (BranchDto branch : branches) {
+      createdAt = DateUtils.addMinutes(createdAt, 10);
+      dbTester.components().insertProjectBranch(PROJECT, branch).setCreatedAt(createdAt);
+    }
+    dbTester.commit();
+    when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(projectDto));
+  }
+
+  @Test
+  public void export_branches() {
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("3 branches exported");
+    Map<String, ProjectDump.Branch> branches = dumpWriter.getWrittenMessagesOf(DumpElement.BRANCHES)
+      .stream()
+      .collect(toMap(ProjectDump.Branch::getUuid, Function.identity()));
+    assertThat(branches).hasSize(3);
+    ProjectDump.Branch mainBranch = branches.get(PROJECT_UUID);
+    assertThat(mainBranch).isNotNull();
+    assertThat(mainBranch.getKee()).isEqualTo(BranchDto.DEFAULT_MAIN_BRANCH_NAME);
+    assertThat(mainBranch.getProjectUuid()).isEqualTo(PROJECT_UUID);
+    assertThat(mainBranch.getMergeBranchUuid()).isEmpty();
+    assertThat(mainBranch.getBranchType()).isEqualTo("BRANCH");
+    ProjectDump.Branch branch1 = branches.get("branch-1-uuid");
+    assertThat(branch1.getKee()).isEqualTo("branch-1");
+    assertThat(branch1.getProjectUuid()).isEqualTo(PROJECT_UUID);
+    assertThat(branch1.getMergeBranchUuid()).isEqualTo("master");
+    assertThat(branch1.getBranchType()).isEqualTo("BRANCH");
+    ProjectDump.Branch branch5 = branches.get("branch-5-uuid");
+    assertThat(branch5.getKee()).isEqualTo("branch-5");
+    assertThat(branch5.getProjectUuid()).isEqualTo(PROJECT_UUID);
+    assertThat(branch5.getMergeBranchUuid()).isEqualTo("master");
+    assertThat(branch5.getBranchType()).isEqualTo("BRANCH");
+  }
+
+  @Test
+  public void throws_ISE_if_error() {
+    dumpWriter.failIfMoreThan(1, DumpElement.BRANCHES);
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Branch export failed after processing 1 branch(es) successfully");
+  }
+
+  @Test
+  public void getDescription_is_defined() {
+    assertThat(underTest.getDescription()).isEqualTo("Export branches");
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/file/ExportLineHashesStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/file/ExportLineHashesStepIT.java
new file mode 100644 (file)
index 0000000..ac6a7cf
--- /dev/null
@@ -0,0 +1,192 @@
+/*
+ * 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.file;
+
+import com.sonarsource.governance.projectdump.protobuf.ProjectDump;
+import java.util.List;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+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.projectexport.component.MutableComponentRepository;
+import org.sonar.ce.task.projectexport.steps.DumpElement;
+import org.sonar.ce.task.projectexport.steps.FakeDumpWriter;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.core.util.Uuids;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.MyBatis;
+import org.sonar.db.source.FileSourceDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+public class ExportLineHashesStepIT {
+  private static final String PROJECT_MASTER_UUID = "project uuid";
+  private static final String PROJECT_BRANCH_UUID = "branch-uuid";
+  private static final String FILE_UUID = "file uuid";
+  private static final String FILE_UUID_2 = "file-2-uuid";
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  private DbClient dbClient = dbTester.getDbClient();
+  private DbSession dbSession = dbClient.openSession(false);
+  private FakeDumpWriter dumpWriter = new FakeDumpWriter();
+  private MutableComponentRepository componentRepository = new ComponentRepositoryImpl();
+
+  private ExportLineHashesStep underTest = new ExportLineHashesStep(dbClient, dumpWriter, componentRepository);
+
+  @After
+  public void tearDown() {
+    dbSession.close();
+  }
+
+  @Test
+  public void getDescription_is_set() {
+    assertThat(underTest.getDescription()).isEqualTo("Export line hashes");
+  }
+
+  @Test
+  public void execute_does_not_create_a_session_when_there_is_no_file_in_ComponentRepository() {
+    DbClient spy = spy(dbClient);
+    new ExportLineHashesStep(spy, dumpWriter, componentRepository)
+      .execute(new TestComputationStepContext());
+
+    verifyNoInteractions(spy);
+
+    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.LINES_HASHES)).isEmpty();
+  }
+
+  @Test
+  public void execute_relies_only_on_file_uuid_and_does_not_check_project_uuid() {
+    componentRepository.register(1, FILE_UUID, true);
+
+    insertFileSource(createDto(FILE_UUID, "blabla", "A"));
+    insertFileSource(createDto(FILE_UUID_2, "blabla", "B"));
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.LINES_HASHES)).hasSize(1);
+  }
+
+  @Test
+  public void execute_maps_ref_of_component_and_hashes_from_fileSources() {
+    int fileRef = 984615;
+    componentRepository.register(fileRef, FILE_UUID, true);
+    FileSourceDto dto = createDto(FILE_UUID, PROJECT_MASTER_UUID, "B");
+    insertFileSource(dto);
+
+    underTest.execute(new TestComputationStepContext());
+
+    List<ProjectDump.LineHashes> messages = dumpWriter.getWrittenMessagesOf(DumpElement.LINES_HASHES);
+    assertThat(messages).hasSize(1);
+    ProjectDump.LineHashes lineHashes = messages.iterator().next();
+    assertThat(lineHashes.getHashes()).isEqualTo(dto.getRawLineHashes());
+    assertThat(lineHashes.getComponentRef()).isEqualTo(fileRef);
+  }
+
+  @Test
+  public void execute_does_one_SQL_request_by_1000_items_per_IN_clause() {
+    for (int i = 0; i < 2500; i++) {
+      componentRepository.register(i, "uuid_" + i, true);
+    }
+
+    DbClient spyDbClient = spy(dbClient);
+    MyBatis spyMyBatis = spy(dbClient.getMyBatis());
+    when(spyDbClient.getMyBatis()).thenReturn(spyMyBatis);
+    ArgumentCaptor<String> stringCaptor = ArgumentCaptor.forClass(String.class);
+    doCallRealMethod().when(spyMyBatis).newScrollingSelectStatement(any(DbSession.class), stringCaptor.capture());
+
+    new ExportLineHashesStep(spyDbClient, dumpWriter, componentRepository)
+      .execute(new TestComputationStepContext());
+
+    List<String> statements = stringCaptor.getAllValues();
+    assertThat(statements).hasSize(3);
+
+    assertThat(statements.get(0).split("\\?")).hasSize(1001);
+    assertThat(statements.get(1).split("\\?")).hasSize(1001);
+    assertThat(statements.get(2).split("\\?")).hasSize(501);
+  }
+
+  @Test
+  public void execute_exports_lines_hashes_of_file_sources() {
+    componentRepository.register(1, FILE_UUID, true);
+    insertFileSource(FILE_UUID, "A");
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.LINES_HASHES))
+      .extracting(ProjectDump.LineHashes::getHashes)
+      .containsOnly("A");
+  }
+
+  @Test
+  public void execute_logs_number_of_filesource_exported_and_export_by_order_of_id() {
+    componentRepository.register(1, FILE_UUID, true);
+    componentRepository.register(2, "file-2", true);
+    componentRepository.register(3, "file-3", true);
+    componentRepository.register(4, "file-4", true);
+
+    insertFileSource(createDto(FILE_UUID, PROJECT_MASTER_UUID, "A"));
+    insertFileSource(createDto("file-2", PROJECT_MASTER_UUID, "C"));
+    insertFileSource(createDto("file-3", PROJECT_MASTER_UUID, "D"));
+    insertFileSource(createDto("file-4", PROJECT_BRANCH_UUID, "E"));
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.LINES_HASHES))
+      .extracting(ProjectDump.LineHashes::getHashes)
+      .containsExactly("A", "C", "D", "E");
+
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).containsExactly("Lines hashes of 4 files exported");
+  }
+
+  private FileSourceDto insertFileSource(String fileUuid, String hashes) {
+    FileSourceDto dto = createDto(fileUuid, PROJECT_MASTER_UUID, hashes);
+    return insertFileSource(dto);
+  }
+
+  private FileSourceDto insertFileSource(FileSourceDto dto) {
+    dbClient.fileSourceDao().insert(dbSession, dto);
+    dbSession.commit();
+    return dto;
+  }
+
+  private FileSourceDto createDto(String fileUuid, String componentUuid, String hashes) {
+    FileSourceDto fileSourceDto = new FileSourceDto()
+      .setUuid(Uuids.createFast())
+      .setFileUuid(fileUuid)
+      .setProjectUuid(componentUuid);
+    fileSourceDto.setRawLineHashes(hashes);
+    return fileSourceDto;
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/issue/ExportIssuesChangelogStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/issue/ExportIssuesChangelogStepIT.java
new file mode 100644 (file)
index 0000000..0d8e79b
--- /dev/null
@@ -0,0 +1,277 @@
+/*
+ * 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.issue;
+
+import com.sonarsource.governance.projectdump.protobuf.ProjectDump;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.junit.After;
+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.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.core.util.Uuids;
+import org.sonar.db.DbClient;
+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.issue.IssueChangeDto;
+import org.sonar.db.issue.IssueDto;
+
+import static com.google.common.collect.Lists.newArrayList;
+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.api.issue.Issue.STATUS_CLOSED;
+import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
+import static org.sonar.api.issue.Issue.STATUS_OPEN;
+import static org.sonar.api.issue.Issue.STATUS_REOPENED;
+import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
+
+public class ExportIssuesChangelogStepIT {
+  private static final String PROJECT_UUID = "project uuid";
+  private static final String ISSUE_OPEN_UUID = "issue 1 uuid";
+  private static final String ISSUE_CONFIRMED_UUID = "issue 2 uuid";
+  private static final String ISSUE_REOPENED_UUID = "issue 3 uuid";
+  private static final String ISSUE_RESOLVED_UUID = "issue 4 uuid";
+  private static final String ISSUE_CLOSED_UUID = "issue closed uuid";
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  private final DbClient dbClient = dbTester.getDbClient();
+  private final DbSession dbSession = dbClient.openSession(false);
+  private final ProjectHolder projectHolder = mock(ProjectHolder.class);
+  private final FakeDumpWriter dumpWriter = new FakeDumpWriter();
+  private final ExportIssuesChangelogStep underTest = new ExportIssuesChangelogStep(dbClient, projectHolder, dumpWriter);
+
+  private int issueChangeUuidGenerator = 0;
+
+  @Before
+  public void setUp() {
+    ComponentDto projectDto = dbTester.components().insertPublicProject(p -> p.setUuid(PROJECT_UUID));
+    when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(projectDto));
+    when(projectHolder.branches()).thenReturn(newArrayList(
+      new BranchDto().setBranchType(BranchType.BRANCH).setKey("master").setProjectUuid(PROJECT_UUID).setUuid(PROJECT_UUID)));
+
+    insertIssue(PROJECT_UUID, ISSUE_OPEN_UUID, STATUS_OPEN);
+    insertIssue(PROJECT_UUID, ISSUE_CONFIRMED_UUID, STATUS_CONFIRMED);
+    insertIssue(PROJECT_UUID, ISSUE_REOPENED_UUID, STATUS_REOPENED);
+    insertIssue(PROJECT_UUID, ISSUE_RESOLVED_UUID, STATUS_RESOLVED);
+    insertIssue(PROJECT_UUID, ISSUE_CLOSED_UUID, STATUS_CLOSED);
+  }
+
+  @After
+  public void tearDown() {
+    dbSession.close();
+  }
+
+  @Test
+  public void getDescription_is_set() {
+    assertThat(underTest.getDescription()).isEqualTo("Export issues changelog");
+  }
+
+  @Test
+  public void execute_writes_now_RuleChange_is_db_is_empty() {
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES_CHANGELOG)).isEmpty();
+  }
+
+  @Test
+  public void execute_writes_entries_of_issues_of_any_type_but_CLOSED() {
+    long createdAt = 1_999_993L;
+    String[] expectedKeys = new String[] {
+      insertIssueChange(ISSUE_OPEN_UUID, createdAt).getKey(),
+      insertIssueChange(ISSUE_CONFIRMED_UUID, createdAt + 1).getKey(),
+      insertIssueChange(ISSUE_REOPENED_UUID, createdAt + 2).getKey(),
+      insertIssueChange(ISSUE_RESOLVED_UUID, createdAt + 3).getKey()
+    };
+    insertIssueChange(ISSUE_CLOSED_UUID, createdAt + 4);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES_CHANGELOG))
+      .extracting(ProjectDump.IssueChange::getKey).containsExactly(expectedKeys);
+  }
+
+  @Test
+  public void execute_writes_only_entries_of_current_project() {
+    String issueUuid = "issue uuid";
+    insertIssue("other project uuid", issueUuid, STATUS_OPEN);
+    insertIssueChange(issueUuid);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES_CHANGELOG)).isEmpty();
+  }
+
+  @Test
+  public void execute_maps_all_fields_to_protobuf() {
+    IssueChangeDto issueChangeDto = new IssueChangeDto()
+      .setUuid("uuid")
+      .setKey("key")
+      .setIssueKey(ISSUE_OPEN_UUID)
+      .setChangeData("change data")
+      .setChangeType("change type")
+      .setUserUuid("user_uuid")
+      .setIssueChangeCreationDate(454135L)
+      .setProjectUuid(PROJECT_UUID);
+    insertIssueChange(issueChangeDto);
+
+    underTest.execute(new TestComputationStepContext());
+
+    ProjectDump.IssueChange issueChange = getSingleMessage();
+    assertThat(issueChange.getKey()).isEqualTo(issueChangeDto.getKey());
+    assertThat(issueChange.getIssueUuid()).isEqualTo(issueChangeDto.getIssueKey());
+    assertThat(issueChange.getChangeData()).isEqualTo(issueChangeDto.getChangeData());
+    assertThat(issueChange.getChangeType()).isEqualTo(issueChangeDto.getChangeType());
+    assertThat(issueChange.getUserUuid()).isEqualTo(issueChangeDto.getUserUuid());
+    assertThat(issueChange.getCreatedAt()).isEqualTo(issueChangeDto.getIssueChangeCreationDate());
+    assertThat(issueChange.getProjectUuid()).isEqualTo(issueChangeDto.getProjectUuid());
+  }
+
+  @Test
+  public void execute_exports_issues_by_oldest_create_date_first() {
+    long createdAt = 1_999_993L;
+    long now = createdAt + 1_000_000_000L;
+    String key1 = insertIssueChange(ISSUE_OPEN_UUID, createdAt, now).getKey();
+    String key2 = insertIssueChange(ISSUE_OPEN_UUID, createdAt - 500, now + 100).getKey();
+    String key3 = insertIssueChange(ISSUE_OPEN_UUID, createdAt - 1000, now + 200).getKey();
+    String key4 = insertIssueChange(ISSUE_OPEN_UUID, createdAt + 200, now + 300).getKey();
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES_CHANGELOG))
+      .extracting(ProjectDump.IssueChange::getKey)
+      .containsExactly(key3, key2, key1, key4);
+  }
+
+  @Test
+  public void execute_sets_missing_fields_to_default_values() {
+    long createdAt = 1_999_888L;
+    insertIssueChange(new IssueChangeDto().setUuid(Uuids.createFast()).setIssueKey(ISSUE_REOPENED_UUID).setCreatedAt(createdAt).setProjectUuid("project_uuid"));
+
+    underTest.execute(new TestComputationStepContext());
+
+    ProjectDump.IssueChange issueChange = getSingleMessage();
+    assertThat(issueChange.getKey()).isEmpty();
+    assertThat(issueChange.getChangeType()).isEmpty();
+    assertThat(issueChange.getChangeData()).isEmpty();
+    assertThat(issueChange.getUserUuid()).isEmpty();
+    assertThat(issueChange.getCreatedAt()).isEqualTo(createdAt);
+  }
+
+  @Test
+  public void execute_sets_createAt_to_zero_if_both_createdAt_and_issueChangeDate_are_null() {
+    insertIssueChange(new IssueChangeDto().setUuid(Uuids.createFast()).setIssueKey(ISSUE_REOPENED_UUID).setProjectUuid(PROJECT_UUID));
+
+    underTest.execute(new TestComputationStepContext());
+
+    ProjectDump.IssueChange issueChange = getSingleMessage();
+    assertThat(issueChange.getCreatedAt()).isZero();
+  }
+
+  @Test
+  public void execute_writes_entries_of_closed_issue() {
+    insertIssueChange(ISSUE_CLOSED_UUID);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES_CHANGELOG)).isEmpty();
+  }
+
+  @Test
+  public void execute_logs_number_total_exported_issue_changes_count_when_successful() {
+    insertIssueChange(ISSUE_OPEN_UUID);
+    insertIssueChange(ISSUE_CONFIRMED_UUID);
+    insertIssueChange(ISSUE_REOPENED_UUID);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).containsExactly("3 issue changes exported");
+  }
+
+  @Test
+  public void execute_throws_ISE_when_exception_occurs_and_message_contains_number_of_successfully_processed_files_in_() {
+    insertIssueChange(ISSUE_OPEN_UUID);
+    insertIssueChange(ISSUE_CONFIRMED_UUID);
+    insertIssueChange(ISSUE_REOPENED_UUID);
+    dumpWriter.failIfMoreThan(2, DumpElement.ISSUES_CHANGELOG);
+    TestComputationStepContext context = new TestComputationStepContext();
+
+    assertThatThrownBy(() -> underTest.execute(context))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Issues changelog export failed after processing 2 issue changes successfully");
+  }
+
+  private void insertIssueChange(String issueUuid) {
+    insertIssueChange(issueUuid, System2.INSTANCE.now(), null);
+  }
+
+  private IssueChangeDto insertIssueChange(String issueUuid, long creationDate) {
+    return insertIssueChange(issueUuid, creationDate, null);
+  }
+
+  private IssueChangeDto insertIssueChange(String issueUuid, long creationDate, @Nullable Long issueChangeDate) {
+    IssueChangeDto dto = new IssueChangeDto()
+      .setKey("uuid_" + issueChangeUuidGenerator++)
+      .setUuid(Uuids.createFast())
+      .setCreatedAt(creationDate)
+      .setIssueKey(issueUuid)
+      .setProjectUuid("project_uuid");
+    if (issueChangeDate != null) {
+      dto.setIssueChangeCreationDate(issueChangeDate);
+    }
+    return insertIssueChange(dto);
+  }
+
+  private IssueChangeDto insertIssueChange(IssueChangeDto dto) {
+    dbClient.issueChangeDao().insert(dbSession, dto);
+    dbSession.commit();
+    return dto;
+  }
+
+  private void insertIssue(String projectUuid, String uuid, String status) {
+    IssueDto dto = new IssueDto()
+      .setKee(uuid)
+      .setProjectUuid(projectUuid)
+      .setStatus(status);
+    dbClient.issueDao().insert(dbSession, dto);
+    dbSession.commit();
+  }
+
+  private ProjectDump.IssueChange getSingleMessage() {
+    List<ProjectDump.IssueChange> messages = dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES_CHANGELOG);
+    assertThat(messages).hasSize(1);
+    return messages.get(0);
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/issue/ExportIssuesStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/issue/ExportIssuesStepIT.java
new file mode 100644 (file)
index 0000000..3dfb5e6
--- /dev/null
@@ -0,0 +1,404 @@
+/*
+ * 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.issue;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.sonarsource.governance.projectdump.protobuf.ProjectDump;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.Random;
+import org.apache.commons.io.FileUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rules.RuleType;
+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.projectexport.component.MutableComponentRepository;
+import org.sonar.ce.task.projectexport.rule.RuleRepository;
+import org.sonar.ce.task.projectexport.rule.RuleRepositoryImpl;
+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.DbClient;
+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.issue.IssueDto;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.db.protobuf.DbIssues;
+import org.sonar.db.protobuf.DbIssues.Locations;
+import org.sonar.db.protobuf.DbIssues.MessageFormattingType;
+import org.sonar.db.rule.RuleDto;
+import org.sonar.db.rule.RuleDto.Scope;
+
+import static com.google.common.collect.Lists.newArrayList;
+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.api.issue.Issue.STATUS_CONFIRMED;
+import static org.sonar.api.issue.Issue.STATUS_OPEN;
+import static org.sonar.api.issue.Issue.STATUS_REOPENED;
+import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
+
+@RunWith(DataProviderRunner.class)
+public class ExportIssuesStepIT {
+  private static final String SOME_PROJECT_UUID = "project uuid";
+  private static final String PROJECT_KEY = "projectkey";
+  private static final String SOME_REPO = "rule repo";
+  private static final String READY_RULE_KEY = "rule key 1";
+  public static final DbIssues.MessageFormatting MESSAGE_FORMATTING = DbIssues.MessageFormatting.newBuilder().setStart(0).setEnd(4).setType(MessageFormattingType.CODE).build();
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  private final DbClient dbClient = dbTester.getDbClient();
+  private final DbSession dbSession = dbClient.openSession(false);
+  private final ProjectHolder projectHolder = mock(ProjectHolder.class);
+  private final FakeDumpWriter dumpWriter = new FakeDumpWriter();
+  private final RuleRepository ruleRepository = new RuleRepositoryImpl();
+  private final MutableComponentRepository componentRepository = new ComponentRepositoryImpl();
+
+  private final ExportIssuesStep underTest = new ExportIssuesStep(dbClient, projectHolder, dumpWriter, ruleRepository, componentRepository);
+
+  private RuleDto readyRuleDto;
+
+  @Before
+  public void setUp() {
+    ProjectDto project = createProject();
+    when(projectHolder.projectDto()).thenReturn(project);
+    when(projectHolder.branches()).thenReturn(newArrayList(
+      new BranchDto().setBranchType(BranchType.BRANCH).setKey("master").setProjectUuid(SOME_PROJECT_UUID).setUuid(SOME_PROJECT_UUID)));
+
+    // adds a random number of Rules to db and repository so that READY_RULE_KEY does always get id=ref=1
+    for (int i = 0; i < new Random().nextInt(150); i++) {
+      RuleKey ruleKey = RuleKey.of("repo_" + i, "key_" + i);
+      RuleDto ruleDto = insertRule(ruleKey.toString());
+      ruleRepository.register(ruleDto.getUuid(), ruleKey);
+    }
+    this.readyRuleDto = insertRule(READY_RULE_KEY);
+    componentRepository.register(12, SOME_PROJECT_UUID, false);
+  }
+
+  @After
+  public void tearDown() {
+    dbSession.close();
+  }
+
+  @Test
+  public void getDescription_is_set() {
+    assertThat(underTest.getDescription()).isEqualTo("Export issues");
+  }
+
+  @Test
+  public void execute_written_writes_no_issues_when_project_has_no_issues() {
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES)).isEmpty();
+  }
+
+  @Test
+  public void execute_written_writes_no_issues_when_project_has_only_CLOSED_issues() {
+    insertIssue(readyRuleDto, SOME_PROJECT_UUID, Issue.STATUS_CLOSED);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES)).isEmpty();
+  }
+
+  @Test
+  public void execute_fails_with_ISE_if_componentUuid_is_not_set() {
+    insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID).setComponentUuid(null));
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Issue export failed after processing 0 issues successfully")
+      .hasRootCauseInstanceOf(NullPointerException.class)
+      .hasRootCauseMessage("uuid can not be null");
+  }
+
+  @DataProvider
+  public static Object[][] allStatusesButCLOSED() {
+    return new Object[][] {
+      {STATUS_OPEN},
+      {STATUS_CONFIRMED},
+      {STATUS_REOPENED},
+      {STATUS_RESOLVED}
+    };
+  }
+
+  @Test
+  @UseDataProvider("allStatusesButCLOSED")
+  public void execute_writes_issues_with_any_status_but_CLOSED(String status) {
+    String uuid = insertIssue(readyRuleDto, SOME_PROJECT_UUID, status).getKey();
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES))
+      .extracting(ProjectDump.Issue::getUuid)
+      .containsOnly(uuid);
+  }
+
+  @Test
+  public void execute_writes_issues_from_any_component_in_project_are_written() {
+    componentRepository.register(13, "module uuid", false);
+    componentRepository.register(14, "dir uuid", false);
+    componentRepository.register(15, "file uuid", false);
+    String projectIssueUuid = insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID)).getKey();
+    String moduleIssueUuid = insertIssue(createBaseIssueDto(readyRuleDto, "module uuid")).getKey();
+    String dirIssueUuid = insertIssue(createBaseIssueDto(readyRuleDto, "dir uuid")).getKey();
+    String fileIssueUuid = insertIssue(createBaseIssueDto(readyRuleDto, "file uuid")).getKey();
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES))
+      .extracting(ProjectDump.Issue::getUuid)
+      .containsOnly(projectIssueUuid, moduleIssueUuid, dirIssueUuid, fileIssueUuid);
+  }
+
+  @Test
+  public void execute_ignores_issues_of_other_projects() {
+    insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID).setProjectUuid("other project"));
+    String projectIssueUuid = insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID)).getKey();
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES))
+      .extracting(ProjectDump.Issue::getUuid)
+      .containsOnly(projectIssueUuid);
+  }
+
+  @Test
+  public void verify_field_by_field_mapping() throws InvalidProtocolBufferException {
+    String componentUuid = "component uuid";
+    long componentRef = 5454;
+    componentRepository.register(componentRef, componentUuid, false);
+    DbIssues.MessageFormattings messageFormattings = DbIssues.MessageFormattings.newBuilder().addMessageFormatting(MESSAGE_FORMATTING).build();
+    IssueDto issueDto = new IssueDto()
+      .setKee("issue uuid")
+      .setComponentUuid(componentUuid)
+      .setType(988)
+      .setMessage("msg")
+      .setMessageFormattings(messageFormattings)
+      .setLine(10)
+      .setChecksum("checksum")
+      .setResolution("resolution")
+      .setSeverity("severity")
+      .setManualSeverity(true)
+      .setGap(13.13d)
+      .setEffort(99L)
+      .setAssigneeUuid("assignee-uuid")
+      .setAuthorLogin("author")
+      .setTagsString("tags")
+      .setRuleDescriptionContextKey("test_rule_description_context_key")
+      .setIssueCreationTime(963L)
+      .setIssueUpdateTime(852L)
+      .setIssueCloseTime(741L);
+
+    // fields tested separately and/or required to match SQL request
+    issueDto
+      .setType(RuleType.CODE_SMELL)
+      .setLocations(Locations.newBuilder().addFlow(DbIssues.Flow.newBuilder()).build())
+      .setRuleUuid(readyRuleDto.getUuid())
+      .setStatus(STATUS_OPEN).setProjectUuid(SOME_PROJECT_UUID);
+
+    insertIssue(issueDto);
+
+    underTest.execute(new TestComputationStepContext());
+
+    ProjectDump.Issue issue = getWrittenIssue();
+
+    assertThat(issue.getUuid()).isEqualTo(issueDto.getKey());
+    assertThat(issue.getComponentRef()).isEqualTo(componentRef);
+    assertThat(issue.getType()).isEqualTo(issueDto.getType());
+    assertThat(issue.getMessage()).isEqualTo(issueDto.getMessage());
+    assertThat(issue.getLine()).isEqualTo(issueDto.getLine());
+    assertThat(issue.getChecksum()).isEqualTo(issueDto.getChecksum());
+    assertThat(issue.getStatus()).isEqualTo(issueDto.getStatus());
+    assertThat(issue.getResolution()).isEqualTo(issueDto.getResolution());
+    assertThat(issue.getSeverity()).isEqualTo(issueDto.getSeverity());
+    assertThat(issue.getManualSeverity()).isEqualTo(issueDto.isManualSeverity());
+    assertThat(issue.getGap()).isEqualTo(issueDto.getGap());
+    assertThat(issue.getEffort()).isEqualTo(issueDto.getEffort());
+    assertThat(issue.getAssignee()).isEqualTo(issueDto.getAssigneeUuid());
+    assertThat(issue.getAuthor()).isEqualTo(issueDto.getAuthorLogin());
+    assertThat(issue.getTags()).isEqualTo(issueDto.getTagsString());
+    assertThat(issue.getRuleDescriptionContextKey()).isEqualTo(issue.getRuleDescriptionContextKey());
+    assertThat(issue.getIssueCreatedAt()).isEqualTo(issueDto.getIssueCreationTime());
+    assertThat(issue.getIssueUpdatedAt()).isEqualTo(issueDto.getIssueUpdateTime());
+    assertThat(issue.getIssueClosedAt()).isEqualTo(issueDto.getIssueCloseTime());
+    assertThat(issue.getLocations()).isNotEmpty();
+    assertThat(issue.getMessageFormattingsList())
+      .isEqualTo(ExportIssuesStep.dbToDumpMessageFormatting(messageFormattings.getMessageFormattingList()));
+  }
+
+  @Test
+  public void verify_mapping_of_nullable_numerical_fields_to_defaultValue() {
+    insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
+
+    underTest.execute(new TestComputationStepContext());
+
+    ProjectDump.Issue issue = getWrittenIssue();
+
+    assertThat(issue.getLine()).isEqualTo(DumpElement.ISSUES.NO_LINE);
+    assertThat(issue.getGap()).isEqualTo(DumpElement.ISSUES.NO_GAP);
+    assertThat(issue.getEffort()).isEqualTo(DumpElement.ISSUES.NO_EFFORT);
+    assertThat(issue.getIssueCreatedAt()).isEqualTo(DumpElement.NO_DATETIME);
+    assertThat(issue.getIssueUpdatedAt()).isEqualTo(DumpElement.NO_DATETIME);
+    assertThat(issue.getIssueClosedAt()).isEqualTo(DumpElement.NO_DATETIME);
+    assertThat(issue.hasRuleDescriptionContextKey()).isFalse();
+  }
+
+  @Test
+  public void ruleRef_is_ref_provided_by_RuleRepository() {
+
+    IssueDto issueDto = insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
+
+    underTest.execute(new TestComputationStepContext());
+
+    ProjectDump.Issue issue = getWrittenIssue();
+    assertThat(issue.getRuleRef())
+      .isEqualTo(ruleRepository.register(issueDto.getRuleUuid(), readyRuleDto.getKey()).ref());
+  }
+
+  @Test
+  public void locations_is_not_set_in_protobuf_if_null_in_DB() {
+    insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(getWrittenIssue().getLocations()).isEmpty();
+  }
+
+  @Test
+  public void message_formattings_is_empty_in_protobuf_if_null_in_DB() {
+    insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(getWrittenIssue().getMessageFormattingsList()).isEmpty();
+  }
+
+  @Test
+  public void execute_fails_with_ISE_if_locations_cannot_be_parsed_to_protobuf() throws URISyntaxException, IOException {
+    byte[] rubbishBytes = getRubbishBytes();
+    String uuid = insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID).setLocations(rubbishBytes)).getKey();
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Issue export failed after processing 0 issues successfully");
+  }
+
+  @Test
+  public void execute_logs_number_total_exported_issue_count_when_successful() {
+    insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
+    insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
+    insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).containsExactly("3 issues exported");
+  }
+
+  @Test
+  public void execute_throws_ISE_with_number_of_successful_exports_before_failure() throws URISyntaxException, IOException {
+    insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
+    insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
+    insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID).setLocations(getRubbishBytes())).getKey();
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Issue export failed after processing 2 issues successfully");
+  }
+
+  private byte[] getRubbishBytes() throws IOException, URISyntaxException {
+    return FileUtils.readFileToByteArray(new File(getClass().getResource("rubbish_data.txt").toURI()));
+  }
+
+  private ProjectDump.Issue getWrittenIssue() {
+    return dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES).get(0);
+  }
+
+  // private void expectExportFailure() {
+  // expectExportFailure(0);
+  // }
+
+  // private void expectExportFailure(int i) {
+  // expectedException.expect(IllegalStateException.class);
+  // expectedException.expectMessage("Issue export failed after processing " + i + " issues successfully");
+  // }
+
+  private int issueUuidGenerator = 1;
+
+  private IssueDto insertIssue(RuleDto ruleDto, String componentUuid, String status) {
+    IssueDto dto = createBaseIssueDto(ruleDto, componentUuid, status);
+    return insertIssue(dto);
+  }
+
+  private IssueDto insertIssue(IssueDto dto) {
+    dbClient.issueDao().insert(dbSession, dto);
+    dbSession.commit();
+    return dto;
+  }
+
+  private ProjectDto createProject() {
+    ComponentDto projectDto = dbTester.components().insertPrivateProject(c -> c.setKey(PROJECT_KEY).setUuid(SOME_PROJECT_UUID));
+    dbTester.commit();
+    return dbTester.components().getProjectDto(projectDto);
+  }
+
+  private IssueDto createBaseIssueDto(RuleDto ruleDto, String componentUuid) {
+    return createBaseIssueDto(ruleDto, componentUuid, STATUS_OPEN);
+  }
+
+  private IssueDto createBaseIssueDto(RuleDto ruleDto, String componentUuid, String status) {
+    return new IssueDto()
+      .setKee("issue_uuid_" + issueUuidGenerator++)
+      .setComponentUuid(componentUuid)
+      .setProjectUuid(SOME_PROJECT_UUID)
+      .setRuleUuid(ruleDto.getUuid())
+      .setCreatedAt(System2.INSTANCE.now())
+      .setStatus(status);
+  }
+
+  private RuleDto insertRule(String ruleKey1) {
+    RuleDto dto = new RuleDto().setRepositoryKey(SOME_REPO).setScope(Scope.MAIN).setRuleKey(ruleKey1).setStatus(RuleStatus.READY);
+    dbTester.rules().insert(dto);
+    dbSession.commit();
+    return dto;
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/rule/ExportAdHocRulesStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/rule/ExportAdHocRulesStepIT.java
new file mode 100644 (file)
index 0000000..15902ec
--- /dev/null
@@ -0,0 +1,264 @@
+/*
+ * 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.rule;
+
+import com.google.common.collect.ImmutableList;
+import com.sonarsource.governance.projectdump.protobuf.ProjectDump;
+import java.util.Date;
+import java.util.List;
+import org.apache.commons.lang.RandomStringUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.resources.Scopes;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleType;
+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.BranchDto;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.db.rule.RuleDto;
+
+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.db.component.ComponentDto.UUID_PATH_OF_ROOT;
+
+public class ExportAdHocRulesStepIT {
+  private static final String PROJECT_UUID = "some-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)
+    .setBranchUuid(PROJECT_UUID);
+
+  private static final List<BranchDto> BRANCHES = ImmutableList.of(
+    new BranchDto().setBranchType(BranchType.PULL_REQUEST).setProjectUuid(PROJECT_UUID).setKey("pr-1").setUuid("pr-1-uuid").setMergeBranchUuid("master"),
+    new BranchDto().setBranchType(BranchType.BRANCH).setProjectUuid(PROJECT_UUID).setKey("branch-2").setUuid("branch-2-uuid").setMergeBranchUuid("master")
+      .setExcludeFromPurge(true),
+    new BranchDto().setBranchType(BranchType.BRANCH).setProjectUuid(PROJECT_UUID).setKey("branch-3").setUuid("branch-3-uuid").setMergeBranchUuid("master")
+      .setExcludeFromPurge(false));
+
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private int issueUuidGenerator = 1;
+  private FakeDumpWriter dumpWriter = new FakeDumpWriter();
+  private ProjectHolder projectHolder = mock(ProjectHolder.class);
+  private ExportAdHocRulesStep underTest = new ExportAdHocRulesStep(dbTester.getDbClient(), projectHolder, dumpWriter);
+
+  @Before
+  public void setup() {
+    ProjectDto project = createProject();
+    when(projectHolder.projectDto()).thenReturn(project);
+  }
+
+  @Test
+  public void export_zero_ad_hoc_rules() {
+    underTest.execute(new TestComputationStepContext());
+
+    List<ProjectDump.AdHocRule> exportedRules = dumpWriter.getWrittenMessagesOf(DumpElement.AD_HOC_RULES);
+    assertThat(exportedRules).isEmpty();
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("0 ad-hoc rules exported");
+  }
+
+  @Test
+  public void execute_only_exports_ad_hoc_rules_that_reference_project_issue() {
+    String differentProject = "diff-proj-uuid";
+    RuleDto rule1 = insertAddHocRule( "rule-1");
+    RuleDto rule2 = insertAddHocRule( "rule-2");
+    insertAddHocRule( "rule-3");
+    insertIssue(rule1, differentProject, differentProject);
+    insertIssue(rule2, PROJECT_UUID, PROJECT_UUID);
+
+    underTest.execute(new TestComputationStepContext());
+
+    List<ProjectDump.AdHocRule> exportedRules = dumpWriter.getWrittenMessagesOf(DumpElement.AD_HOC_RULES);
+    assertThat(exportedRules).hasSize(1);
+    assertProtobufAdHocRuleIsCorrectlyBuilt(exportedRules.iterator().next(), rule2);
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("1 ad-hoc rules exported");
+  }
+
+  @Test
+  public void execute_only_exports_rules_that_are_ad_hoc() {
+    RuleDto rule1 = insertStandardRule("rule-1");
+    RuleDto rule2 = insertExternalRule("rule-2");
+    RuleDto rule3 = insertAddHocRule("rule-3");
+    insertIssue(rule1, PROJECT_UUID, PROJECT_UUID);
+    insertIssue(rule2, PROJECT_UUID, PROJECT_UUID);
+    insertIssue(rule3, PROJECT_UUID, PROJECT_UUID);
+
+    underTest.execute(new TestComputationStepContext());
+
+    List<ProjectDump.AdHocRule> exportedRules = dumpWriter.getWrittenMessagesOf(DumpElement.AD_HOC_RULES);
+    assertThat(exportedRules).hasSize(1);
+    assertProtobufAdHocRuleIsCorrectlyBuilt(exportedRules.iterator().next(), rule3);
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("1 ad-hoc rules exported");
+  }
+
+  @Test
+  public void execute_exports_ad_hoc_rules_that_are_referenced_by_issues_on_branches_excluded_from_purge() {
+    when(projectHolder.branches()).thenReturn(BRANCHES);
+    RuleDto rule1 = insertAddHocRule("rule-1");
+    RuleDto rule2 = insertAddHocRule("rule-2");
+    RuleDto rule3 = insertAddHocRule("rule-3");
+    insertIssue(rule1, "branch-1-uuid", "branch-1-uuid");
+    insertIssue(rule2, "branch-2-uuid", "branch-2-uuid");
+    insertIssue(rule3, "branch-3-uuid", "branch-3-uuid");
+
+    underTest.execute(new TestComputationStepContext());
+
+    List<ProjectDump.AdHocRule> exportedRules = dumpWriter.getWrittenMessagesOf(DumpElement.AD_HOC_RULES);
+    assertThat(exportedRules).hasSize(1);
+    assertProtobufAdHocRuleIsCorrectlyBuilt(exportedRules.iterator().next(), rule2);
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("1 ad-hoc rules exported");
+  }
+
+  @Test
+  public void execute_throws_ISE_with_number_of_successful_exports_before_failure() {
+    RuleDto rule1 = insertAddHocRule("rule-1");
+    RuleDto rule2 = insertAddHocRule("rule-2");
+    RuleDto rule3 = insertAddHocRule("rule-3");
+    insertIssue(rule1, PROJECT_UUID, PROJECT_UUID);
+    insertIssue(rule2, PROJECT_UUID, PROJECT_UUID);
+    insertIssue(rule3, PROJECT_UUID, PROJECT_UUID);
+    dumpWriter.failIfMoreThan(2, DumpElement.AD_HOC_RULES);
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Ad-hoc rules export failed after processing 2 rules successfully");
+  }
+
+  @Test
+  public void getDescription() {
+    assertThat(underTest.getDescription()).isEqualTo("Export ad-hoc rules");
+  }
+
+  private ProjectDto createProject() {
+    Date createdAt = new Date();
+    ComponentDto projectDto = dbTester.components().insertPublicProject(PROJECT);
+    BRANCHES.forEach(branch -> dbTester.components().insertProjectBranch(projectDto, branch).setCreatedAt(createdAt));
+    dbTester.commit();
+    return dbTester.components().getProjectDto(projectDto);
+  }
+
+  private void insertIssue(RuleDto ruleDto, String projectUuid, String componentUuid) {
+    IssueDto dto = createBaseIssueDto(ruleDto, projectUuid, componentUuid);
+    insertIssue(dto);
+  }
+
+  private void insertIssue(IssueDto dto) {
+    dbTester.getDbClient().issueDao().insert(dbTester.getSession(), dto);
+    dbTester.commit();
+  }
+
+  private IssueDto createBaseIssueDto(RuleDto ruleDto, String projectUuid, String componentUuid) {
+    return new IssueDto()
+      .setKee("issue_uuid_" + issueUuidGenerator++)
+      .setComponentUuid(componentUuid)
+      .setProjectUuid(projectUuid)
+      .setRuleUuid(ruleDto.getUuid())
+      .setStatus("OPEN");
+  }
+
+  private RuleDto insertExternalRule(String ruleName) {
+    RuleDto ruleDto = new RuleDto()
+      .setIsExternal(true)
+      .setIsAdHoc(false);
+    return insertRule(ruleName, ruleDto);
+  }
+
+  private RuleDto insertAddHocRule(String ruleName) {
+    RuleDto ruleDto = new RuleDto()
+      .setIsExternal(false)
+      .setIsAdHoc(true)
+      .setAdHocName("ad_hoc_rule" + RandomStringUtils.randomAlphabetic(10))
+      .setAdHocType(RuleType.VULNERABILITY)
+      .setAdHocSeverity(Severity.CRITICAL)
+      .setAdHocDescription("ad hoc description: " + RandomStringUtils.randomAlphanumeric(100));
+    return insertRule(ruleName, ruleDto);
+  }
+
+  private RuleDto insertStandardRule(String ruleName) {
+    RuleDto ruleDto = new RuleDto()
+      .setIsExternal(false)
+      .setIsAdHoc(false);
+    return insertRule(ruleName, ruleDto);
+  }
+
+  private RuleDto insertRule(String ruleName, RuleDto partiallyInitRuleDto) {
+    RuleKey ruleKey = RuleKey.of("plugin1", ruleName);
+    partiallyInitRuleDto
+      .setName("ruleName" + RandomStringUtils.randomAlphanumeric(10))
+      .setRuleKey(ruleKey)
+      .setPluginKey("pluginKey" + RandomStringUtils.randomAlphanumeric(10))
+      .setStatus(RuleStatus.READY)
+      .setScope(RuleDto.Scope.ALL);
+
+    dbTester.rules().insert(partiallyInitRuleDto);
+    dbTester.commit();
+    return dbTester.getDbClient().ruleDao().selectByKey(dbTester.getSession(), ruleKey)
+      .orElseThrow(() -> new RuntimeException("insertAdHocRule failed"));
+  }
+
+  private static void assertProtobufAdHocRuleIsCorrectlyBuilt(ProjectDump.AdHocRule protobufAdHocRule, RuleDto source) {
+    assertThat(protobufAdHocRule.getName()).isEqualTo(source.getName());
+    assertThat(protobufAdHocRule.getRef()).isEqualTo(source.getUuid());
+    assertThat(protobufAdHocRule.getPluginKey()).isEqualTo(source.getPluginKey());
+    assertThat(protobufAdHocRule.getPluginRuleKey()).isEqualTo(source.getRuleKey());
+    assertThat(protobufAdHocRule.getPluginName()).isEqualTo(source.getRepositoryKey());
+    assertThat(protobufAdHocRule.getName()).isEqualTo(source.getName());
+    assertThat(protobufAdHocRule.getStatus()).isEqualTo(source.getStatus().name());
+    assertThat(protobufAdHocRule.getType()).isEqualTo(source.getType());
+    assertThat(protobufAdHocRule.getScope()).isEqualTo(source.getScope().name());
+    assertProtobufAdHocRuleIsCorrectlyBuilt(protobufAdHocRule.getMetadata(), source);
+  }
+
+  private static void assertProtobufAdHocRuleIsCorrectlyBuilt(ProjectDump.AdHocRule.RuleMetadata metadata, RuleDto expected) {
+    assertThat(metadata.getAdHocName()).isEqualTo(expected.getAdHocName());
+    assertThat(metadata.getAdHocDescription()).isEqualTo(expected.getAdHocDescription());
+    assertThat(metadata.getAdHocSeverity()).isEqualTo(expected.getAdHocSeverity());
+    assertThat(metadata.getAdHocType()).isEqualTo(expected.getAdHocType());
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/ExportEventsStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/ExportEventsStepIT.java
new file mode 100644 (file)
index 0000000..e2aea5f
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ * 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.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.component.ComponentRepositoryImpl;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.event.EventDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.sonar.db.component.ComponentDto.UUID_PATH_OF_ROOT;
+import static org.sonar.db.component.SnapshotDto.STATUS_PROCESSED;
+
+public class ExportEventsStepIT {
+
+  private static final long NOW = 1_450_000_000_000L;
+  private static final long IN_THE_PAST = 1_440_000_000_000L;
+
+  private static final String PROJECT_UUID = "project_uuid";
+  private static final ComponentDto PROJECT = new ComponentDto()
+    .setUuid(PROJECT_UUID)
+    .setUuidPath(UUID_PATH_OF_ROOT)
+    .setBranchUuid(PROJECT_UUID)
+    .setScope(Scopes.PROJECT)
+    .setQualifier(Qualifiers.PROJECT)
+    .setKey("the_project")
+    .setEnabled(true);
+
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  private FakeDumpWriter dumpWriter = new FakeDumpWriter();
+  private MutableProjectHolder projectHolder = new MutableProjectHolderImpl();
+  private ComponentRepositoryImpl componentRepository = new ComponentRepositoryImpl();
+  private ExportEventsStep underTest = new ExportEventsStep(dbTester.getDbClient(), projectHolder, componentRepository, dumpWriter);
+
+  @Before
+  public void setUp() {
+    ComponentDto projectDto = dbTester.components().insertPublicProject(PROJECT);
+    componentRepository.register(1, projectDto.uuid(), false);
+    projectHolder.setProjectDto(dbTester.components().getProjectDto(projectDto));
+  }
+
+  @Test
+  public void export_zero_events() {
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("0 events exported");
+    List<ProjectDump.Event> events = dumpWriter.getWrittenMessagesOf(DumpElement.EVENTS);
+    assertThat(events).isEmpty();
+  }
+
+  @Test
+  public void export_events() {
+    SnapshotDto snapshot = insertSnapshot();
+    insertEvent(snapshot, "E1", "one");
+    insertEvent(snapshot, "E2", "two");
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("2 events exported");
+    List<ProjectDump.Event> events = dumpWriter.getWrittenMessagesOf(DumpElement.EVENTS);
+    assertThat(events).hasSize(2);
+    assertThat(events).extracting(ProjectDump.Event::getUuid).containsOnly("E1", "E2");
+  }
+
+  @Test
+  public void throws_ISE_if_error() {
+    SnapshotDto snapshot = insertSnapshot();
+    insertEvent(snapshot, "E1", "one");
+    insertEvent(snapshot, "E2", "two");
+    dumpWriter.failIfMoreThan(1, DumpElement.EVENTS);
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Event Export failed after processing 1 events successfully");
+  }
+
+  @Test
+  public void export_all_fields() {
+    SnapshotDto snapshot = insertSnapshot();
+    dbTester.getDbClient().eventDao().insert(dbTester.getSession(), new EventDto()
+      .setUuid("E1")
+      .setAnalysisUuid(snapshot.getUuid())
+      .setComponentUuid(snapshot.getComponentUuid())
+      .setDate(IN_THE_PAST)
+      .setCreatedAt(NOW)
+      .setData("data")
+      .setName("name")
+      .setCategory("categ")
+      .setDescription("desc"));
+    dbTester.commit();
+
+    underTest.execute(new TestComputationStepContext());
+
+    ProjectDump.Event event = dumpWriter.getWrittenMessagesOf(DumpElement.EVENTS).get(0);
+    assertThat(event.getUuid()).isEqualTo("E1");
+    assertThat(event.getName()).isEqualTo("name");
+    assertThat(event.getData()).isEqualTo("data");
+    assertThat(event.getCategory()).isEqualTo("categ");
+    assertThat(event.getDescription()).isEqualTo("desc");
+    assertThat(event.getDate()).isEqualTo(IN_THE_PAST);
+    assertThat(event.getAnalysisUuid()).isEqualTo(snapshot.getUuid());
+    assertThat(event.getComponentRef()).isOne();
+  }
+
+  @Test
+  public void getDescription_is_defined() {
+    assertThat(underTest.getDescription()).isEqualTo("Export events");
+  }
+
+  private void insertEvent(SnapshotDto snapshot, String uuid, String name) {
+    dbTester.getDbClient().eventDao().insert(dbTester.getSession(), new EventDto()
+      .setUuid(uuid)
+      .setAnalysisUuid(snapshot.getUuid())
+      .setComponentUuid(snapshot.getComponentUuid())
+      .setDate(IN_THE_PAST)
+      .setCreatedAt(NOW)
+      .setName(name));
+    dbTester.commit();
+  }
+
+  private SnapshotDto insertSnapshot() {
+    SnapshotDto snapshot = new SnapshotDto()
+      .setUuid("U1")
+      .setComponentUuid(PROJECT.uuid())
+      .setStatus(STATUS_PROCESSED)
+      .setLast(false);
+    dbTester.getDbClient().snapshotDao().insert(dbTester.getSession(), snapshot);
+    dbTester.commit();
+    return snapshot;
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/ExportLinksStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/ExportLinksStepIT.java
new file mode 100644 (file)
index 0000000..d1b8475
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * 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.Link;
+import org.junit.Before;
+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.component.ComponentRepository;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ProjectLinkDto;
+import org.sonar.db.project.ProjectExportMapper;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.groups.Tuple.tuple;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.db.component.ComponentDto.UUID_PATH_OF_ROOT;
+
+public class ExportLinksStepIT {
+
+  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)
+    .setBranchUuid(PROJECT_UUID);
+
+  @Rule
+  public DbTester db = DbTester.createWithExtensionMappers(System2.INSTANCE, ProjectExportMapper.class);
+
+
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  private final FakeDumpWriter dumpWriter = new FakeDumpWriter();
+  private final ComponentRepository componentRepository = mock(ComponentRepository.class);
+  private final ProjectHolder projectHolder = mock(ProjectHolder.class);
+  private final ExportLinksStep underTest = new ExportLinksStep(db.getDbClient(), componentRepository, projectHolder, dumpWriter);
+
+  @Before
+  public void setUp() {
+    ComponentDto project = db.components().insertPublicProject(PROJECT);
+    when(projectHolder.projectDto()).thenReturn(db.components().getProjectDto(project));
+    when(componentRepository.getRef(PROJECT_UUID)).thenReturn(1L);
+  }
+
+  @Test
+  public void export_zero_links() {
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("0 links exported");
+    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.LINKS)).isEmpty();
+  }
+
+  @Test
+  public void export_links() {
+    ProjectLinkDto link1 = db.componentLinks().insertCustomLink(PROJECT);
+    ProjectLinkDto link2 = db.componentLinks().insertProvidedLink(PROJECT);
+    db.componentLinks().insertCustomLink(db.components().insertPrivateProject());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("2 links exported");
+    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.LINKS))
+      .extracting(Link::getUuid, Link::getName, Link::getType, Link::getHref)
+      .containsExactlyInAnyOrder(
+        tuple(link1.getUuid(), link1.getName(), link1.getType(), link1.getHref()),
+        tuple(link2.getUuid(), "", link2.getType(), link2.getHref()));
+  }
+
+  @Test
+  public void throws_ISE_if_error() {
+    db.componentLinks().insertCustomLink(PROJECT);
+    db.componentLinks().insertProvidedLink(PROJECT);
+    db.componentLinks().insertProvidedLink(PROJECT);
+    db.componentLinks().insertCustomLink(db.components().insertPrivateProject());
+
+    dumpWriter.failIfMoreThan(2, DumpElement.LINKS);
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Link export failed after processing 2 link(s) successfully");
+  }
+
+  @Test
+  public void test_all_fields() {
+    ProjectLinkDto link = db.componentLinks().insertCustomLink(PROJECT, l -> l.setName("name").setHref("href").setType("type"));
+
+    underTest.execute(new TestComputationStepContext());
+
+    Link reloaded = dumpWriter.getWrittenMessagesOf(DumpElement.LINKS).get(0);
+    assertThat(reloaded.getUuid()).isEqualTo(link.getUuid());
+    assertThat(reloaded.getName()).isEqualTo(link.getName());
+    assertThat(reloaded.getHref()).isEqualTo(link.getHref());
+    assertThat(reloaded.getType()).isEqualTo(link.getType());
+  }
+
+  @Test
+  public void getDescription_is_defined() {
+    assertThat(underTest.getDescription()).isEqualTo("Export links");
+  }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/ExportLiveMeasuresStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/ExportLiveMeasuresStepIT.java
new file mode 100644 (file)
index 0000000..c4aad6e
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+ * 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.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.metric.MetricDto;
+import org.sonar.db.project.ProjectDto;
+
+import static com.google.common.collect.Lists.newArrayList;
+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;
+import static org.sonar.api.measures.Metric.ValueType.INT;
+
+public class ExportLiveMeasuresStepIT {
+  @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 ExportLiveMeasuresStep underTest = new ExportLiveMeasuresStep(dbTester.getDbClient(), projectHolder, componentRepository, metricRepository, dumpWriter);
+
+  @Test
+  public void export_zero_measures() {
+    when(projectHolder.branches()).thenReturn(newArrayList());
+    when(projectHolder.projectDto()).thenReturn(new ProjectDto().setUuid("does not exist"));
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.LIVE_MEASURES)).isEmpty();
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("0 live measures exported");
+    assertThat(metricRepository.getRefByUuid()).isEmpty();
+  }
+
+  @Test
+  public void export_measures() {
+    ComponentDto project = createProject(true);
+    componentRepository.register(1, project.uuid(), false);
+    MetricDto metric = dbTester.measures().insertMetric(m -> m.setKey("metric1").setValueType(INT.name()));
+    dbTester.measures().insertLiveMeasure(project, metric, m -> m.setValue(4711.0d));
+    when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(project));
+    when(projectHolder.branches()).thenReturn(newArrayList(new BranchDto()
+      .setProjectUuid(project.uuid())
+      .setUuid(project.uuid())
+      .setKey("master")
+      .setBranchType(BranchType.BRANCH)));
+    underTest.execute(new TestComputationStepContext());
+
+    List<ProjectDump.LiveMeasure> exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.LIVE_MEASURES);
+    assertThat(exportedMeasures).hasSize(1);
+    assertThat(exportedMeasures)
+      .extracting(ProjectDump.LiveMeasure::getMetricRef, m -> m.getDoubleValue().getValue(), ProjectDump.LiveMeasure::hasVariation)
+      .containsOnly(tuple(0, 4711.0d, false));
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("1 live measures exported");
+    assertThat(metricRepository.getRefByUuid()).containsOnlyKeys(metric.getUuid());
+  }
+
+  @Test
+  public void do_not_export_measures_on_disabled_projects() {
+    ComponentDto project = createProject(false);
+    componentRepository.register(1, project.uuid(), false);
+    MetricDto metric = dbTester.measures().insertMetric(m -> m.setValueType(INT.name()));
+    dbTester.measures().insertLiveMeasure(project, metric, m -> m.setValue(4711.0d));
+    when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(project));
+    when(projectHolder.branches()).thenReturn(newArrayList(new BranchDto()
+      .setProjectUuid(project.uuid())
+      .setUuid(project.uuid())
+      .setKey("master")
+      .setBranchType(BranchType.BRANCH)));
+
+    underTest.execute(new TestComputationStepContext());
+
+    List<ProjectDump.LiveMeasure> exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.LIVE_MEASURES);
+    assertThat(exportedMeasures).isEmpty();
+  }
+
+  @Test
+  public void do_not_export_measures_on_disabled_metrics() {
+    ComponentDto project = createProject(true);
+    componentRepository.register(1, project.uuid(), false);
+    MetricDto metric = dbTester.measures().insertMetric(m -> m.setValueType(INT.name()).setEnabled(false));
+    dbTester.measures().insertLiveMeasure(project, metric, m -> m.setValue(4711.0d));
+    when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(project));
+    when(projectHolder.branches()).thenReturn(newArrayList(new BranchDto()
+      .setProjectUuid(project.uuid())
+      .setUuid(project.uuid())
+      .setKey("master")
+      .setBranchType(BranchType.BRANCH)));
+
+    underTest.execute(new TestComputationStepContext());
+
+    List<ProjectDump.LiveMeasure> exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.LIVE_MEASURES);
+    assertThat(exportedMeasures).isEmpty();
+  }
+
+  @Test
+  public void test_exported_fields() {
+    ComponentDto project = createProject(true);
+    componentRepository.register(1, project.uuid(), false);
+    MetricDto metric = dbTester.measures().insertMetric(m -> m.setKey("new_metric").setValueType(INT.name()));
+    dbTester.measures().insertLiveMeasure(project, metric, m -> m.setProjectUuid(project.uuid()).setValue(7.0d).setData("test"));
+    when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(project));
+    when(projectHolder.branches()).thenReturn(newArrayList(new BranchDto()
+      .setProjectUuid(project.uuid())
+      .setUuid(project.uuid())
+      .setKey("master")
+      .setBranchType(BranchType.BRANCH)));
+
+    underTest.execute(new TestComputationStepContext());
+
+    List<ProjectDump.LiveMeasure> exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.LIVE_MEASURES);
+    assertThat(exportedMeasures).hasSize(1);
+    assertThat(exportedMeasures)
+      .extracting(
+        ProjectDump.LiveMeasure::getComponentRef,
+        ProjectDump.LiveMeasure::getMetricRef,
+        m -> m.getDoubleValue().getValue(),
+        ProjectDump.LiveMeasure::getTextValue,
+        m -> m.getVariation().getValue())
+      .containsOnly(tuple(
+        1L,
+        0,
+        0.0d,
+        "test",
+        7.0d));
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("1 live measures exported");
+    assertThat(metricRepository.getRefByUuid()).containsOnlyKeys(metric.getUuid());
+  }
+
+  @Test
+  public void test_null_exported_fields() {
+    ComponentDto project = createProject(true);
+    componentRepository.register(1, project.uuid(), false);
+    MetricDto metric = dbTester.measures().insertMetric(m -> m.setValueType(INT.name()));
+    dbTester.measures().insertLiveMeasure(project, metric, m -> m.setProjectUuid(project.uuid()).setValue(null).setData((String) null));
+    when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(project));
+    when(projectHolder.branches()).thenReturn(newArrayList(new BranchDto()
+      .setProjectUuid(project.uuid())
+      .setUuid(project.uuid())
+      .setKey("master")
+      .setBranchType(BranchType.BRANCH)));
+
+    underTest.execute(new TestComputationStepContext());
+
+    List<ProjectDump.LiveMeasure> exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.LIVE_MEASURES);
+    assertThat(exportedMeasures).hasSize(1);
+    assertThat(exportedMeasures)
+      .extracting(
+        ProjectDump.LiveMeasure::hasDoubleValue,
+        ProjectDump.LiveMeasure::getTextValue,
+        ProjectDump.LiveMeasure::hasVariation)
+      .containsOnly(tuple(
+        false,
+        "",
+        false));
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("1 live measures exported");
+    assertThat(metricRepository.getRefByUuid()).containsOnlyKeys(metric.getUuid());
+  }
+
+  @Test
+  public void test_getDescription() {
+    assertThat(underTest.getDescription()).isEqualTo("Export live measures");
+  }
+
+  private ComponentDto createProject(boolean enabled) {
+    return dbTester.components().insertPrivateProject(p -> p.setEnabled(enabled));
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/ExportMetricsStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/ExportMetricsStepIT.java
new file mode 100644 (file)
index 0000000..16f7a32
--- /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.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.step.TestComputationStepContext;
+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 ExportMetricsStepIT {
+
+  private static final MetricDto NCLOC = new MetricDto()
+    .setUuid("1")
+    .setKey("ncloc")
+    .setShortName("Lines of code")
+    .setEnabled(true);
+  private static final MetricDto COVERAGE = new MetricDto()
+    .setUuid("2")
+    .setKey("coverage")
+    .setShortName("Coverage")
+    .setEnabled(true);
+
+
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  MutableMetricRepository metricsHolder = new MutableMetricRepositoryImpl();
+  FakeDumpWriter dumpWriter = new FakeDumpWriter();
+  ExportMetricsStep underTest = new ExportMetricsStep(dbTester.getDbClient(), metricsHolder, dumpWriter);
+
+  @Before
+  public void setUp() {
+    dbTester.getDbClient().metricDao().insert(dbTester.getSession(), NCLOC, COVERAGE);
+    dbTester.commit();
+  }
+
+  @Test
+  public void export_zero_metrics() {
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("0 metrics exported");
+  }
+
+  @Test
+  public void export_metrics() {
+    metricsHolder.add(NCLOC.getUuid());
+    metricsHolder.add(COVERAGE.getUuid());
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("2 metrics exported");
+    List<ProjectDump.Metric> exportedMetrics = dumpWriter.getWrittenMessagesOf(DumpElement.METRICS);
+
+    ProjectDump.Metric ncloc = exportedMetrics.stream().filter(input -> input.getRef() == 0).findAny().orElseThrow();
+    assertThat(ncloc.getRef()).isZero();
+    assertThat(ncloc.getKey()).isEqualTo("ncloc");
+    assertThat(ncloc.getName()).isEqualTo("Lines of code");
+
+    ProjectDump.Metric coverage = exportedMetrics.stream().filter(input -> input.getRef() == 1).findAny().orElseThrow();
+    assertThat(coverage.getRef()).isOne();
+    assertThat(coverage.getKey()).isEqualTo("coverage");
+    assertThat(coverage.getName()).isEqualTo("Coverage");
+  }
+
+  @Test
+  public void throw_ISE_if_error() {
+    metricsHolder.add(NCLOC.getUuid());
+    dumpWriter.failIfMoreThan(0, DumpElement.METRICS);
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Metric Export failed after processing 0 metrics successfully");
+  }
+
+  @Test
+  public void test_getDescription() {
+    assertThat(underTest.getDescription()).isEqualTo("Export metrics");
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/ExportNewCodePeriodsStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/ExportNewCodePeriodsStepIT.java
new file mode 100644 (file)
index 0000000..3d9697d
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ * 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.google.common.collect.ImmutableList;
+import com.sonarsource.governance.projectdump.protobuf.ProjectDump;
+import java.util.Date;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.resources.Qualifiers;
+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.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.newcodeperiod.NewCodePeriodDto;
+import org.sonar.db.newcodeperiod.NewCodePeriodType;
+import org.sonar.db.project.ProjectExportMapper;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.sonar.db.component.ComponentDto.UUID_PATH_OF_ROOT;
+import static org.sonar.db.newcodeperiod.NewCodePeriodType.PREVIOUS_VERSION;
+import static org.sonar.db.newcodeperiod.NewCodePeriodType.SPECIFIC_ANALYSIS;
+
+public class ExportNewCodePeriodsStepIT {
+
+  private static final String PROJECT_UUID = "project_uuid";
+  private static final String ANOTHER_PROJECT_UUID = "another_project_uuid";
+  private static final ComponentDto PROJECT = new ComponentDto()
+    .setUuid(PROJECT_UUID)
+    .setUuidPath(UUID_PATH_OF_ROOT)
+    .setBranchUuid(PROJECT_UUID)
+    .setQualifier(Qualifiers.PROJECT)
+    .setName("project")
+    .setKey("the_project");
+  private static final ComponentDto ANOTHER_PROJECT = new ComponentDto()
+    .setUuid(ANOTHER_PROJECT_UUID)
+    .setUuidPath(UUID_PATH_OF_ROOT)
+    .setBranchUuid(ANOTHER_PROJECT_UUID)
+    .setQualifier(Qualifiers.PROJECT)
+    .setName("another_project")
+    .setKey("another_project");
+
+  private static final List<BranchDto> PROJECT_BRANCHES = ImmutableList.of(
+    new BranchDto().setBranchType(BranchType.PULL_REQUEST).setProjectUuid(PROJECT_UUID).setKey("pr-1").setUuid("pr-uuid-1").setMergeBranchUuid("master"),
+    new BranchDto().setBranchType(BranchType.BRANCH).setProjectUuid(PROJECT_UUID).setKey("branch-1").setUuid("branch-uuid-1").setMergeBranchUuid("master")
+      .setExcludeFromPurge(true),
+    new BranchDto().setBranchType(BranchType.BRANCH).setProjectUuid(PROJECT_UUID).setKey("branch-2").setUuid("branch-uuid-2").setMergeBranchUuid("master")
+      .setExcludeFromPurge(false));
+
+  private static final List<BranchDto> ANOTHER_PROJECT_BRANCHES = ImmutableList.of(
+    new BranchDto().setBranchType(BranchType.BRANCH).setProjectUuid(ANOTHER_PROJECT_UUID).setKey("branch-3").setUuid("branch-uuid-3").setMergeBranchUuid("master")
+      .setExcludeFromPurge(true));
+
+  @Rule
+  public LogTester logTester = new LogTester();
+  @Rule
+  public DbTester dbTester = DbTester.createWithExtensionMappers(System2.INSTANCE, ProjectExportMapper.class);
+
+  private MutableProjectHolder projectHolder = new MutableProjectHolderImpl();
+  private FakeDumpWriter dumpWriter = new FakeDumpWriter();
+  private ExportNewCodePeriodsStep underTest = new ExportNewCodePeriodsStep(dbTester.getDbClient(), projectHolder, dumpWriter);
+
+  @Before
+  public void setUp() {
+    Date createdAt = new Date();
+    ComponentDto projectDto = dbTester.components().insertPublicProject(PROJECT);
+    PROJECT_BRANCHES.forEach(branch -> dbTester.components().insertProjectBranch(projectDto, branch).setCreatedAt(createdAt));
+
+    ComponentDto anotherProjectDto = dbTester.components().insertPublicProject(ANOTHER_PROJECT);
+    ANOTHER_PROJECT_BRANCHES.forEach(branch -> dbTester.components().insertProjectBranch(anotherProjectDto, branch).setCreatedAt(createdAt));
+
+    dbTester.commit();
+    projectHolder.setProjectDto(dbTester.components().getProjectDto(PROJECT));
+  }
+
+  @Test
+  public void export_zero_new_code_periods() {
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.NEW_CODE_PERIODS)).isEmpty();
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("0 new code periods exported");
+  }
+
+  @Test
+  public void export_only_project_new_code_periods_on_branches_excluded_from_purge() {
+    NewCodePeriodDto newCodePeriod1 = newDto("uuid1", PROJECT.uuid(), null, SPECIFIC_ANALYSIS, "analysis-uuid");
+    NewCodePeriodDto newCodePeriod2 = newDto("uuid2", PROJECT.uuid(), "branch-uuid-1", SPECIFIC_ANALYSIS, "analysis-uuid");
+    // the following new code periods are not exported
+    NewCodePeriodDto newCodePeriod3 = newDto("uuid3", PROJECT.uuid(), "branch-uuid-2", SPECIFIC_ANALYSIS, "analysis-uuid");
+    NewCodePeriodDto anotherProjectNewCodePeriods = newDto("uuid4", ANOTHER_PROJECT.uuid(), "branch-uuid-3", SPECIFIC_ANALYSIS, "analysis-uuid");
+    NewCodePeriodDto globalNewCodePeriod = newDto("uuid5", null, null, PREVIOUS_VERSION, null);
+    insertNewCodePeriods(newCodePeriod1, newCodePeriod2, newCodePeriod3, anotherProjectNewCodePeriods, globalNewCodePeriod);
+
+    underTest.execute(new TestComputationStepContext());
+
+    List<ProjectDump.NewCodePeriod> exportedProps = dumpWriter.getWrittenMessagesOf(DumpElement.NEW_CODE_PERIODS);
+    assertThat(exportedProps).hasSize(2);
+    assertThat(exportedProps).extracting(ProjectDump.NewCodePeriod::getUuid).containsOnly("uuid1", "uuid2");
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("2 new code periods exported");
+  }
+
+  @Test
+  public void test_exported_fields() {
+    NewCodePeriodDto dto = newDto("uuid1", PROJECT.uuid(), "branch-uuid-1", SPECIFIC_ANALYSIS, "analysis-uuid");
+    insertNewCodePeriods(dto);
+
+    underTest.execute(new TestComputationStepContext());
+
+    ProjectDump.NewCodePeriod exportedNewCodePeriod = dumpWriter.getWrittenMessagesOf(DumpElement.NEW_CODE_PERIODS).get(0);
+    assertThat(exportedNewCodePeriod.getUuid()).isEqualTo(dto.getUuid());
+    assertThat(exportedNewCodePeriod.getProjectUuid()).isEqualTo(dto.getProjectUuid());
+    assertThat(exportedNewCodePeriod.getBranchUuid()).isEqualTo(dto.getBranchUuid());
+    assertThat(exportedNewCodePeriod.getType()).isEqualTo(dto.getType().name());
+    assertThat(exportedNewCodePeriod.getValue()).isEqualTo(dto.getValue());
+  }
+
+  @Test
+  public void throws_ISE_if_error() {
+    dumpWriter.failIfMoreThan(1, DumpElement.NEW_CODE_PERIODS);
+    insertNewCodePeriods(
+      newDto("uuid1", PROJECT.uuid(), null, SPECIFIC_ANALYSIS, "analysis-uuid"),
+      newDto("uuid2", PROJECT.uuid(), "branch-uuid-1", SPECIFIC_ANALYSIS, "analysis-uuid"));
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("New Code Periods Export failed after processing 1 new code periods successfully");
+  }
+
+  @Test
+  public void test_getDescription() {
+    assertThat(underTest.getDescription()).isEqualTo("Export new code periods");
+  }
+
+  private static NewCodePeriodDto newDto(String uuid, @Nullable String projectUuid, @Nullable String branchUuid, NewCodePeriodType type, @Nullable String value) {
+    return new NewCodePeriodDto()
+      .setUuid(uuid)
+      .setProjectUuid(projectUuid)
+      .setBranchUuid(branchUuid)
+      .setType(type)
+      .setValue(value);
+  }
+
+  private void insertNewCodePeriods(NewCodePeriodDto... dtos) {
+    for (NewCodePeriodDto dto : dtos) {
+      dbTester.getDbClient().newCodePeriodDao().insert(dbTester.getSession(), dto);
+    }
+    dbTester.commit();
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/ExportSettingsStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/ExportSettingsStepIT.java
new file mode 100644 (file)
index 0000000..e8ca859
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * 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 javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.resources.Qualifiers;
+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.projectexport.component.MutableComponentRepository;
+import org.sonar.ce.task.step.TestComputationStepContext;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.project.ProjectExportMapper;
+import org.sonar.db.property.PropertyDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.sonar.db.component.ComponentDto.UUID_PATH_OF_ROOT;
+
+public class ExportSettingsStepIT {
+
+  private static final ComponentDto GLOBAL = null;
+  private static final ComponentDto PROJECT = new ComponentDto()
+    .setUuid("project_uuid")
+    .setUuidPath(UUID_PATH_OF_ROOT)
+    .setBranchUuid("project_uuid")
+    .setKey("the_project");
+  private static final ComponentDto ANOTHER_PROJECT = new ComponentDto()
+    .setUuid("another_project_uuid")
+    .setUuidPath(UUID_PATH_OF_ROOT)
+    .setBranchUuid("another_project_uuid")
+    .setKey("another_project");
+
+  @Rule
+  public LogTester logTester = new LogTester();
+  @Rule
+  public DbTester dbTester = DbTester.createWithExtensionMappers(System2.INSTANCE, ProjectExportMapper.class);
+  private MutableComponentRepository componentRepository = new ComponentRepositoryImpl();
+  private MutableProjectHolder projectHolder = new MutableProjectHolderImpl();
+  private FakeDumpWriter dumpWriter = new FakeDumpWriter();
+  private ExportSettingsStep underTest = new ExportSettingsStep(dbTester.getDbClient(), projectHolder, componentRepository, dumpWriter);
+
+  @Before
+  public void setUp() {
+    dbTester.components().insertPublicProject(PROJECT);
+    dbTester.components().insertPublicProject(ANOTHER_PROJECT);
+    dbTester.commit();
+    projectHolder.setProjectDto(dbTester.components().getProjectDto(PROJECT));
+    componentRepository.register(1, PROJECT.uuid(), false);
+  }
+
+  @Test
+  public void export_zero_settings() {
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.SETTINGS)).isEmpty();
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("0 settings exported");
+  }
+
+  @Test
+  public void export_only_project_settings() {
+    PropertyDto projectProperty1 = newDto("p1", "v1", PROJECT);
+    PropertyDto projectProperty2 = newDto("p2", "v2", PROJECT);
+    // the following properties are not exported
+    PropertyDto propOnOtherProject = newDto("p3", "v3", ANOTHER_PROJECT);
+    PropertyDto globalProperty = newDto("p4", "v4", GLOBAL);
+    insertProperties(PROJECT.getKey(), PROJECT.name(), projectProperty1, projectProperty2);
+    insertProperties(ANOTHER_PROJECT.getKey(), ANOTHER_PROJECT.name(), propOnOtherProject);
+    insertProperties(null, null, globalProperty);
+
+    underTest.execute(new TestComputationStepContext());
+
+    List<ProjectDump.Setting> exportedProps = dumpWriter.getWrittenMessagesOf(DumpElement.SETTINGS);
+    assertThat(exportedProps).hasSize(2);
+    assertThat(exportedProps).extracting(ProjectDump.Setting::getKey).containsOnly("p1", "p2");
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("2 settings exported");
+  }
+
+  @Test
+  public void exclude_properties_specific_to_environment() {
+    insertProperties(PROJECT.getKey(), PROJECT.name(), newDto("sonar.issues.defaultAssigneeLogin", null, PROJECT));
+
+    underTest.execute(new TestComputationStepContext());
+
+    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.SETTINGS)).isEmpty();
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("0 settings exported");
+  }
+
+  @Test
+  public void test_exported_fields() {
+    PropertyDto dto = newDto("p1", "v1", PROJECT);
+    insertProperties(PROJECT.getKey(), PROJECT.name(), dto);
+
+    underTest.execute(new TestComputationStepContext());
+
+    ProjectDump.Setting exportedProp = dumpWriter.getWrittenMessagesOf(DumpElement.SETTINGS).get(0);
+    assertThat(exportedProp.getKey()).isEqualTo(dto.getKey());
+    assertThat(exportedProp.getValue()).isEqualTo(dto.getValue());
+  }
+
+  @Test
+  public void property_can_have_empty_value() {
+    insertProperties(PROJECT.getKey(), PROJECT.name(), newDto("p1", null, PROJECT));
+
+    underTest.execute(new TestComputationStepContext());
+
+    ProjectDump.Setting exportedProp = dumpWriter.getWrittenMessagesOf(DumpElement.SETTINGS).get(0);
+    assertThat(exportedProp.getKey()).isEqualTo("p1");
+    assertThat(exportedProp.getValue()).isEmpty();
+  }
+
+  @Test
+  public void throws_ISE_if_error() {
+    dumpWriter.failIfMoreThan(1, DumpElement.SETTINGS);
+    insertProperties(PROJECT.getKey(), PROJECT.name(), newDto("p1", null, PROJECT),
+      newDto("p2", null, PROJECT));
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Settings Export failed after processing 1 settings successfully");
+  }
+
+  @Test
+  public void test_getDescription() {
+    assertThat(underTest.getDescription()).isEqualTo("Export settings");
+  }
+
+  private static PropertyDto newDto(String key, @Nullable String value, @Nullable ComponentDto project) {
+    PropertyDto dto = new PropertyDto().setKey(key).setValue(value);
+    if (project != null) {
+      dto.setComponentUuid(project.uuid());
+    }
+    return dto;
+  }
+
+  private void insertProperties(@Nullable String componentKey, @Nullable String componentName, PropertyDto... dtos) {
+    for (PropertyDto dto : dtos) {
+      dbTester.getDbClient().propertiesDao().saveProperty(dbTester.getSession(), dto, null, componentKey, componentName, Qualifiers.VIEW);
+    }
+    dbTester.commit();
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/LoadProjectStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/LoadProjectStepIT.java
new file mode 100644 (file)
index 0000000..2f9475a
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * 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 org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.utils.MessageException;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.projectexport.taskprocessor.ProjectDescriptor;
+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;
+
+public class LoadProjectStepIT {
+
+  private static final String PROJECT_KEY = "project_key";
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private final ProjectDescriptor descriptor = new ProjectDescriptor("project_uuid", PROJECT_KEY, "Project Name");
+  private final MutableProjectHolder definitionHolder = new MutableProjectHolderImpl();
+  private final LoadProjectStep underTest = new LoadProjectStep(descriptor, definitionHolder, dbTester.getDbClient());
+
+  @Test
+  public void fails_if_project_does_not_exist() {
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(MessageException.class)
+      .hasMessage("Project with key [project_key] does not exist");
+  }
+
+  @Test
+  public void fails_if_component_is_not_a_project() {
+    // insert a module, but not a project
+    dbTester.executeInsert("projects",
+      "kee", PROJECT_KEY,
+      "qualifier", Qualifiers.APP,
+      "uuid", "not_used",
+      "private", false,
+      "created_at", 1L,
+      "updated_at", 1L);
+
+    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
+      .isInstanceOf(MessageException.class)
+      .hasMessage("Project with key [project_key] does not exist");
+  }
+
+  @Test
+  public void registers_project_if_valid() {
+    ComponentDto project = dbTester.components().insertPublicProject(c -> c.setKey(PROJECT_KEY));
+    underTest.execute(new TestComputationStepContext());
+    assertThat(definitionHolder.projectDto().getKey()).isEqualTo(project.getKey());
+  }
+
+  @Test
+  public void getDescription_is_defined() {
+    assertThat(underTest.getDescription()).isNotEmpty();
+  }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/step/ExtractReportStepIT/zip-bomb.zip b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/step/ExtractReportStepIT/zip-bomb.zip
new file mode 100644 (file)
index 0000000..d06da2c
Binary files /dev/null and b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/step/ExtractReportStepIT/zip-bomb.zip differ
diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectexport/issue/rubbish_data.txt b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectexport/issue/rubbish_data.txt
new file mode 100644 (file)
index 0000000..3b5aee3
--- /dev/null
@@ -0,0 +1,3 @@
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas hendrerit sapien magna, eget ultrices sapien ornare et. Donec interdum posuere magna nec tristique. Nam a dictum diam. Fusce consequat dolor vel justo egestas, a vulputate urna mattis. Nam at velit a quam eleifend aliquam quis vel est. Praesent facilisis consequat magna, eget aliquam leo eleifend non. Nunc et posuere tortor, sit amet tincidunt metus. Donec bibendum ligula id nisi lobortis, interdum vestibulum neque dignissim. Phasellus ornare sapien at auctor posuere. In hac habitasse platea dictumst. Sed nec tempor purus, sed interdum erat.
+
+Etiam nec porta magna, eget efficitur augue. Maecenas commodo ac nibh a dignissim. Curabitur eu ipsum nibh. Aenean id sodales velit. Vestibulum tristique at lectus eu gravida. Suspendisse vehicula gravida elit, eget fringilla nibh ullamcorper vitae. Integer est neque, aliquet sit amet turpis eget, tempor tincidunt nisi. Cras ornare mollis lacinia. Aenean at dolor scelerisque, rhoncus ex eget, pellentesque justo. Proin at malesuada ex. Praesent feugiat sapien id mollis vulputate. Fusce ornare, ligula id auctor elementum, enim erat finibus velit, vitae auctor metus ante vitae mauris. Quisque efficitur arcu vel enim sollicitudin accumsan. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec gravida erat vitae laoreet posuere.
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactoryImplTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactoryImplTest.java
deleted file mode 100644 (file)
index a7e9ef3..0000000
+++ /dev/null
@@ -1,87 +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 org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.projectanalysis.analysis.Branch;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.BranchType;
-import org.sonar.db.component.ComponentDto;
-
-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.BranchDto.DEFAULT_MAIN_BRANCH_NAME;
-
-public class ComponentUuidFactoryImplTest {
-  private final Branch mainBranch = new DefaultBranchImpl(DEFAULT_MAIN_BRANCH_NAME);
-  private final Branch mockedBranch = mock(Branch.class);
-
-  @Rule
-  public DbTester db = DbTester.create(System2.INSTANCE);
-
-  @Test
-  public void getOrCreateForKey_when_existingComponentsInDbForMainBranch_should_load() {
-    ComponentDto project = db.components().insertPrivateProject();
-
-    ComponentUuidFactory underTest = new ComponentUuidFactoryImpl(db.getDbClient(), db.getSession(), project.getKey(), mainBranch);
-
-    assertThat(underTest.getOrCreateForKey(project.getKey())).isEqualTo(project.uuid());
-  }
-
-  @Test
-  public void getOrCreateForKey_when_existingComponentsInDbForNonMainBranch_should_load() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("b1"));
-    when(mockedBranch.getType()).thenReturn(BranchType.BRANCH);
-    when(mockedBranch.isMain()).thenReturn(false);
-    when(mockedBranch.getName()).thenReturn("b1");
-
-    ComponentUuidFactory underTest = new ComponentUuidFactoryImpl(db.getDbClient(), db.getSession(), project.getKey(), mockedBranch);
-
-    assertThat(underTest.getOrCreateForKey(project.getKey())).isEqualTo(branch.uuid());
-  }
-
-  @Test
-  public void getOrCreateForKey_when_existingComponentsInDbForPr_should_load() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto pr = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.PULL_REQUEST).setKey("pr1"));
-    when(mockedBranch.getType()).thenReturn(BranchType.PULL_REQUEST);
-    when(mockedBranch.isMain()).thenReturn(false);
-    when(mockedBranch.getPullRequestKey()).thenReturn("pr1");
-
-    ComponentUuidFactory underTest = new ComponentUuidFactoryImpl(db.getDbClient(), db.getSession(), project.getKey(), mockedBranch);
-
-    assertThat(underTest.getOrCreateForKey(project.getKey())).isEqualTo(pr.uuid());
-  }
-
-  @Test
-  public void getOrCreateForKey_when_componentsNotInDb_should_generate() {
-    ComponentUuidFactory underTest = new ComponentUuidFactoryImpl(db.getDbClient(), db.getSession(), "theProjectKey", mainBranch);
-
-    String generatedKey = underTest.getOrCreateForKey("foo");
-    assertThat(generatedKey).isNotEmpty();
-
-    // uuid is kept in memory for further calls with same key
-    assertThat(underTest.getOrCreateForKey("foo")).isEqualTo(generatedKey);
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ProjectPersisterTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ProjectPersisterTest.java
deleted file mode 100644 (file)
index 4aef4c2..0000000
+++ /dev/null
@@ -1,111 +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 org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.impl.utils.TestSystem2;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
-import org.sonar.db.DbTester;
-import org.sonar.db.project.ProjectDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT;
-import static org.sonar.ce.task.projectanalysis.component.Component.Type.VIEW;
-import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
-
-public class ProjectPersisterTest {
-  private final static Component ROOT = builder(PROJECT, 1)
-    .setUuid("PROJECT_UUID")
-    .setKey("PROJECT_KEY")
-    .setDescription("PROJECT_DESC")
-    .setName("PROJECT_NAME")
-    .build();
-
-  @Rule
-  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-  public TestSystem2 system2 = new TestSystem2();
-
-  private ProjectPersister underTest = new ProjectPersister(dbTester.getDbClient(), treeRootHolder, system2);
-
-  @Before
-  public void prepare() {
-    treeRootHolder.setRoot(ROOT);
-    system2.setNow(1000L);
-  }
-
-  @Test
-  public void skip_portfolios() {
-    Component root = ViewsComponent.builder(VIEW, 1).build();
-    TreeRootHolder treeRootHolder = mock(TreeRootHolder.class);
-    when(treeRootHolder.getRoot()).thenReturn(root);
-    new ProjectPersister(dbTester.getDbClient(), treeRootHolder, system2).persist(dbTester.getSession());
-    verify(treeRootHolder).getRoot();
-    verifyNoMoreInteractions(treeRootHolder);
-
-  }
-
-  @Test
-  public void update_description() {
-    ProjectDto p1 = dbTester.components().insertPublicProjectDto(
-      c -> c.setUuid("PROJECT_UUID").setKey(ROOT.getKey()).setName(ROOT.getName()).setDescription("OLD_DESC"));
-
-    assertProject("OLD_DESC", ROOT.getName(), p1.getUpdatedAt());
-    underTest.persist(dbTester.getSession());
-    assertProject(ROOT.getDescription(), ROOT.getName(), 1000L);
-  }
-
-  @Test
-  public void update_name() {
-    ProjectDto p1 = dbTester.components().insertPublicProjectDto(
-      c -> c.setUuid("PROJECT_UUID").setKey(ROOT.getKey()).setName("OLD_NAME").setDescription(ROOT.getDescription()));
-
-    assertProject(ROOT.getDescription(), "OLD_NAME", p1.getUpdatedAt());
-    underTest.persist(dbTester.getSession());
-    assertProject(ROOT.getDescription(), ROOT.getName(), 1000L);
-  }
-
-  @Test
-  public void dont_update() {
-    ProjectDto p1 = dbTester.components().insertPublicProjectDto(
-      c -> c.setUuid("PROJECT_UUID").setKey(ROOT.getKey()).setName(ROOT.getName()).setDescription(ROOT.getDescription()));
-
-    assertProject(ROOT.getDescription(), ROOT.getName(), p1.getUpdatedAt());
-    underTest.persist(dbTester.getSession());
-    assertProject(ROOT.getDescription(), ROOT.getName(), p1.getUpdatedAt());
-  }
-
-  private void assertProject(String description, String name, long updated) {
-    assertThat(dbTester.getDbClient().projectDao().selectProjectByKey(dbTester.getSession(), ROOT.getKey()).get())
-      .extracting(ProjectDto::getName, ProjectDto::getDescription, ProjectDto::getUpdatedAt)
-      .containsExactly(name, description, updated);
-
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ReferenceBranchComponentUuidsTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ReferenceBranchComponentUuidsTest.java
deleted file mode 100644 (file)
index 95e2c5e..0000000
+++ /dev/null
@@ -1,114 +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 org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
-import org.sonar.ce.task.projectanalysis.analysis.Branch;
-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.server.project.Project;
-
-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.SnapshotTesting.newAnalysis;
-
-public class ReferenceBranchComponentUuidsTest {
-  @Rule
-  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
-
-  @Rule
-  public DbTester db = DbTester.create();
-
-  private ReferenceBranchComponentUuids underTest;
-  private Branch branch = mock(Branch.class);
-
-  private ComponentDto branch1;
-  private ComponentDto branch1File;
-  private ComponentDto pr1File;
-  private ComponentDto pr2File;
-  private Project project;
-  private ComponentDto pr1;
-  private ComponentDto pr2;
-  private ComponentDto branch2;
-  private ComponentDto branch2File;
-
-  @Before
-  public void setUp() {
-    underTest = new ReferenceBranchComponentUuids(analysisMetadataHolder, db.getDbClient());
-    project = mock(Project.class);
-    analysisMetadataHolder.setProject(project);
-    analysisMetadataHolder.setBranch(branch);
-
-    ComponentDto projectDto = db.components().insertPublicProject();
-    when(project.getUuid()).thenReturn(projectDto.uuid());
-    branch1 = db.components().insertProjectBranch(projectDto, b -> b.setKey("branch1"));
-    branch2 = db.components().insertProjectBranch(projectDto, b -> b.setKey("branch2"));
-    pr1 = db.components().insertProjectBranch(projectDto, b -> b.setKey("pr1").setBranchType(BranchType.PULL_REQUEST).setMergeBranchUuid(branch1.uuid()));
-    pr2 = db.components().insertProjectBranch(projectDto, b -> b.setKey("pr2").setBranchType(BranchType.PULL_REQUEST).setMergeBranchUuid(branch1.uuid()));
-    branch1File = ComponentTesting.newFileDto(branch1, null, "file").setUuid("branch1File");
-    branch2File = ComponentTesting.newFileDto(branch2, null, "file").setUuid("branch2File");
-    pr1File = ComponentTesting.newFileDto(pr1, null, "file").setUuid("file1");
-    pr2File = ComponentTesting.newFileDto(pr2, null, "file").setUuid("file2");
-    db.components().insertComponents(branch1File, pr1File, pr2File, branch2File);
-  }
-
-  @Test
-  public void should_support_db_key_when_looking_for_reference_component() {
-    when(branch.getReferenceBranchUuid()).thenReturn(branch1.uuid());
-    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
-    when(branch.getTargetBranchName()).thenReturn("notAnalyzedBranch");
-    db.components().insertSnapshot(newAnalysis(branch1));
-    assertThat(underTest.getComponentUuid(pr1File.getKey())).isEqualTo(branch1File.uuid());
-    assertThat(underTest.hasReferenceBranchAnalysis()).isTrue();
-    assertThat(underTest.getReferenceBranchName()).isEqualTo("branch1");
-  }
-
-  @Test
-  public void should_support_key_when_looking_for_reference_component() {
-    when(branch.getReferenceBranchUuid()).thenReturn(branch1.uuid());
-    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
-    when(branch.getTargetBranchName()).thenReturn("notAnalyzedBranch");
-    db.components().insertSnapshot(newAnalysis(branch1));
-    assertThat(underTest.getComponentUuid(pr1File.getKey())).isEqualTo(branch1File.uuid());
-  }
-
-  @Test
-  public void return_null_if_file_doesnt_exist() {
-    when(branch.getReferenceBranchUuid()).thenReturn(branch1.uuid());
-    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
-    when(branch.getTargetBranchName()).thenReturn("notAnalyzedBranch");
-    db.components().insertSnapshot(newAnalysis(branch1));
-    assertThat(underTest.getComponentUuid("doesnt exist")).isNull();
-  }
-
-  @Test
-  public void skip_init_if_no_reference_branch_analysis() {
-    when(branch.getReferenceBranchUuid()).thenReturn(branch1.uuid());
-    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
-    when(branch.getTargetBranchName()).thenReturn("notAnalyzedBranch");
-    assertThat(underTest.getComponentUuid(pr1File.getKey())).isNull();
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/SiblingComponentsWithOpenIssuesTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/SiblingComponentsWithOpenIssuesTest.java
deleted file mode 100644 (file)
index 711da56..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.component;
-
-import java.util.Collections;
-import javax.annotation.Nullable;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
-import org.sonar.ce.task.projectanalysis.analysis.Branch;
-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.rule.RuleDto;
-import org.sonar.server.project.Project;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class SiblingComponentsWithOpenIssuesTest {
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-
-  @Rule
-  public AnalysisMetadataHolderRule metadataHolder = new AnalysisMetadataHolderRule();
-
-  @Rule
-  public DbTester db = DbTester.create();
-
-  private SiblingComponentsWithOpenIssues underTest;
-
-  private ComponentDto branch1;
-  private ComponentDto fileWithNoIssuesOnBranch1;
-  private ComponentDto fileWithOneOpenIssueOnBranch1Pr1;
-  private ComponentDto fileWithOneResolvedIssueOnBranch1Pr1;
-  private ComponentDto fileWithOneOpenTwoResolvedIssuesOnBranch1Pr1;
-  private ComponentDto fileXWithOneResolvedIssueOnBranch1Pr1;
-  private ComponentDto fileXWithOneResolvedIssueOnBranch1Pr2;
-
-  private ComponentDto branch2;
-  private ComponentDto fileWithOneOpenIssueOnBranch2Pr1;
-  private ComponentDto fileWithOneResolvedIssueOnBranch2Pr1;
-  private ComponentDto branch1pr1;
-
-  @Before
-  public void setUp() {
-    ComponentDto project = db.components().insertPublicProject();
-    metadataHolder.setProject(new Project(project.uuid(), project.getKey(), project.name(), project.description(), Collections.emptyList()));
-
-    branch1 = db.components().insertProjectBranch(project, b -> b.setKey("branch1"), b -> b.setBranchType(BranchType.BRANCH));
-    branch1pr1 = db.components().insertProjectBranch(project,
-      b -> b.setKey("branch1pr1"),
-      b -> b.setBranchType(BranchType.PULL_REQUEST),
-      b -> b.setMergeBranchUuid(branch1.uuid()));
-    ComponentDto branch1pr2 = db.components().insertProjectBranch(project,
-      b -> b.setKey("branch1pr2"),
-      b -> b.setBranchType(BranchType.PULL_REQUEST),
-      b -> b.setMergeBranchUuid(branch1.uuid()));
-
-    fileWithNoIssuesOnBranch1 = db.components().insertComponent(ComponentTesting.newFileDto(branch1, null));
-
-    RuleDto rule = db.rules().insert();
-
-    fileWithOneOpenIssueOnBranch1Pr1 = db.components().insertComponent(ComponentTesting.newFileDto(branch1pr1, null));
-    db.issues().insert(rule, branch1pr1, fileWithOneOpenIssueOnBranch1Pr1);
-
-    fileWithOneResolvedIssueOnBranch1Pr1 = db.components().insertComponent(ComponentTesting.newFileDto(branch1pr1, null));
-    db.issues().insert(rule, branch1pr1, fileWithOneResolvedIssueOnBranch1Pr1, i -> i.setStatus("RESOLVED"));
-
-    fileWithOneOpenTwoResolvedIssuesOnBranch1Pr1 = db.components().insertComponent(ComponentTesting.newFileDto(branch1pr1, null));
-    db.issues().insert(rule, branch1pr1, fileWithOneOpenTwoResolvedIssuesOnBranch1Pr1);
-    db.issues().insert(rule, branch1pr1, fileWithOneOpenTwoResolvedIssuesOnBranch1Pr1, i -> i.setStatus("RESOLVED"));
-
-    String fileKey = "file-x";
-    fileXWithOneResolvedIssueOnBranch1Pr1 = db.components().insertComponent(ComponentTesting.newFileDto(branch1pr1, null)
-      .setKey(fileKey));
-    db.issues().insert(rule, branch1pr1, fileXWithOneResolvedIssueOnBranch1Pr1, i -> i.setStatus("RESOLVED"));
-
-    fileXWithOneResolvedIssueOnBranch1Pr2 = db.components().insertComponent(ComponentTesting.newFileDto(branch1pr2, null)
-      .setKey(fileKey));
-    db.issues().insert(rule, branch1pr2, fileXWithOneResolvedIssueOnBranch1Pr2, i -> i.setStatus("RESOLVED"));
-
-    branch2 = db.components().insertProjectBranch(project, b -> b.setKey("branch2"), b -> b.setBranchType(BranchType.BRANCH));
-    ComponentDto branch2pr1 = db.components().insertProjectBranch(project,
-      b -> b.setKey("branch2pr1"),
-      b -> b.setBranchType(BranchType.PULL_REQUEST),
-      b -> b.setMergeBranchUuid(branch2.uuid()));
-
-    fileWithOneOpenIssueOnBranch2Pr1 = db.components().insertComponent(ComponentTesting.newFileDto(branch2pr1, null));
-    db.issues().insert(rule, branch2pr1, fileWithOneOpenIssueOnBranch2Pr1);
-
-    fileWithOneResolvedIssueOnBranch2Pr1 = db.components().insertComponent(ComponentTesting.newFileDto(branch2pr1, null));
-    db.issues().insert(rule, branch2pr1, fileWithOneResolvedIssueOnBranch2Pr1, i -> i.setStatus("RESOLVED"));
-    setRoot(branch1);
-    underTest = new SiblingComponentsWithOpenIssues(treeRootHolder, metadataHolder, db.getDbClient());
-  }
-
-  @Test
-  public void should_find_sibling_components_with_open_issues_for_branch1() {
-    setRoot(branch1);
-    setBranch(BranchType.BRANCH);
-
-    assertThat(underTest.getUuids(fileWithNoIssuesOnBranch1.getKey())).isEmpty();
-    assertThat(underTest.getUuids(fileWithOneOpenIssueOnBranch1Pr1.getKey())).containsOnly(fileWithOneOpenIssueOnBranch1Pr1.uuid());
-    assertThat(underTest.getUuids(fileWithOneResolvedIssueOnBranch1Pr1.getKey())).containsOnly(fileWithOneResolvedIssueOnBranch1Pr1.uuid());
-    assertThat(underTest.getUuids(fileWithOneOpenTwoResolvedIssuesOnBranch1Pr1.getKey())).containsOnly(fileWithOneOpenTwoResolvedIssuesOnBranch1Pr1.uuid());
-
-    assertThat(fileXWithOneResolvedIssueOnBranch1Pr1.getKey()).isEqualTo(fileXWithOneResolvedIssueOnBranch1Pr2.getKey());
-    assertThat(underTest.getUuids(fileXWithOneResolvedIssueOnBranch1Pr1.getKey())).containsOnly(
-      fileXWithOneResolvedIssueOnBranch1Pr1.uuid(),
-      fileXWithOneResolvedIssueOnBranch1Pr2.uuid());
-  }
-
-  @Test
-  public void should_find_sibling_components_with_open_issues_for_pr1() {
-    setRoot(branch1pr1);
-    setBranch(BranchType.PULL_REQUEST, branch1.uuid());
-
-    assertThat(underTest.getUuids(fileWithNoIssuesOnBranch1.getKey())).isEmpty();
-    assertThat(underTest.getUuids(fileWithOneOpenIssueOnBranch1Pr1.getKey())).isEmpty();
-    assertThat(underTest.getUuids(fileWithOneResolvedIssueOnBranch1Pr1.getKey())).isEmpty();
-    assertThat(underTest.getUuids(fileWithOneOpenTwoResolvedIssuesOnBranch1Pr1.getKey())).isEmpty();
-
-    assertThat(underTest.getUuids(fileXWithOneResolvedIssueOnBranch1Pr1.getKey())).containsOnly(fileXWithOneResolvedIssueOnBranch1Pr2.uuid());
-  }
-
-  @Test
-  public void should_find_sibling_components_with_open_issues_for_branch2() {
-    setRoot(branch2);
-    setBranch(BranchType.BRANCH);
-
-    underTest = new SiblingComponentsWithOpenIssues(treeRootHolder, metadataHolder, db.getDbClient());
-
-    assertThat(underTest.getUuids(fileWithOneResolvedIssueOnBranch1Pr1.getKey())).isEmpty();
-    assertThat(underTest.getUuids(fileWithOneResolvedIssueOnBranch2Pr1.getKey())).containsOnly(fileWithOneResolvedIssueOnBranch2Pr1.uuid());
-    assertThat(underTest.getUuids(fileWithOneOpenIssueOnBranch2Pr1.getKey())).containsOnly(fileWithOneOpenIssueOnBranch2Pr1.uuid());
-  }
-
-  @Test
-  public void should_find_sibling_components_with_open_issues_from_pullrequest() {
-    ComponentDto project = db.components().insertPublicProject();
-    setRoot(project);
-    setBranch(BranchType.BRANCH);
-
-    ComponentDto pullRequest = db.components().insertProjectBranch(project,
-      b -> b.setBranchType(BranchType.PULL_REQUEST),
-      b -> b.setMergeBranchUuid(project.uuid()));
-
-    RuleDto rule = db.rules().insert();
-
-    ComponentDto fileWithResolvedIssueOnPullrequest = db.components().insertComponent(ComponentTesting.newFileDto(pullRequest, null));
-    db.issues().insert(rule, pullRequest, fileWithResolvedIssueOnPullrequest, i -> i.setStatus("RESOLVED"));
-    underTest = new SiblingComponentsWithOpenIssues(treeRootHolder, metadataHolder, db.getDbClient());
-
-    assertThat(underTest.getUuids(fileWithResolvedIssueOnPullrequest.getKey())).hasSize(1);
-  }
-
-  @Test
-  public void should_not_find_sibling_components_on_derived_branch() {
-    ComponentDto project = db.components().insertPublicProject();
-    setRoot(project);
-    setBranch(BranchType.BRANCH);
-
-    ComponentDto derivedBranch = db.components().insertProjectBranch(project,
-      b -> b.setBranchType(BranchType.BRANCH),
-      b -> b.setMergeBranchUuid(project.uuid()));
-
-    RuleDto rule = db.rules().insert();
-
-    ComponentDto fileWithResolvedIssueOnDerivedBranch = db.components().insertComponent(ComponentTesting.newFileDto(derivedBranch, null));
-    db.issues().insert(rule, derivedBranch, fileWithResolvedIssueOnDerivedBranch, i -> i.setStatus("RESOLVED"));
-
-    underTest = new SiblingComponentsWithOpenIssues(treeRootHolder, metadataHolder, db.getDbClient());
-
-    assertThat(underTest.getUuids(fileWithResolvedIssueOnDerivedBranch.getKey())).isEmpty();
-  }
-
-  private void setRoot(ComponentDto componentDto) {
-    Component root = mock(Component.class);
-    when(root.getUuid()).thenReturn(componentDto.uuid());
-    treeRootHolder.setRoot(root);
-  }
-
-  private void setBranch(BranchType currentBranchType) {
-    setBranch(currentBranchType, null);
-  }
-
-  private void setBranch(BranchType currentBranchType, @Nullable String mergeBranchUuid) {
-    Branch branch = mock(Branch.class);
-    when(branch.getType()).thenReturn(currentBranchType);
-    when(branch.getReferenceBranchUuid()).thenReturn(mergeBranchUuid);
-    metadataHolder.setBranch(branch);
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/AdHocRuleCreatorTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/AdHocRuleCreatorTest.java
deleted file mode 100644 (file)
index 3383ee1..0000000
+++ /dev/null
@@ -1,186 +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.issue;
-
-import org.junit.Test;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rule.Severity;
-import org.sonar.api.rules.RuleType;
-import org.sonar.api.utils.System2;
-import org.sonar.core.util.SequenceUuidFactory;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.scanner.protocol.Constants;
-import org.sonar.scanner.protocol.output.ScannerReport;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.rule.index.RuleIndexer;
-
-import static org.apache.commons.lang.StringUtils.repeat;
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class AdHocRuleCreatorTest {
-
-  @org.junit.Rule
-  public DbTester db = DbTester.create(System2.INSTANCE);
-  @org.junit.Rule
-  public EsTester es = EsTester.create();
-
-  private RuleIndexer indexer = new RuleIndexer(es.client(), db.getDbClient());
-  private AdHocRuleCreator underTest = new AdHocRuleCreator(db.getDbClient(), System2.INSTANCE, indexer, new SequenceUuidFactory());
-  private DbSession dbSession = db.getSession();
-
-  @Test
-  public void create_ad_hoc_rule_from_issue() {
-    NewAdHocRule addHocRule = new NewAdHocRule(ScannerReport.ExternalIssue.newBuilder().setEngineId("eslint").setRuleId("no-cond-assign").build());
-
-    RuleDto rule = underTest.persistAndIndex(dbSession, addHocRule);
-
-    assertThat(rule).isNotNull();
-    assertThat(rule.isExternal()).isTrue();
-    assertThat(rule.isAdHoc()).isTrue();
-    assertThat(rule.getUuid()).isNotBlank();
-    assertThat(rule.getKey()).isEqualTo(RuleKey.of("external_eslint", "no-cond-assign"));
-    assertThat(rule.getName()).isEqualTo("eslint:no-cond-assign");
-    assertThat(rule.getRuleDescriptionSectionDtos()).isEmpty();
-    assertThat(rule.getSeverity()).isNull();
-    assertThat(rule.getType()).isZero();
-    assertThat(rule.getAdHocName()).isNull();
-    assertThat(rule.getAdHocDescription()).isNull();
-    assertThat(rule.getAdHocSeverity()).isNull();
-    assertThat(rule.getAdHocType()).isNull();
-  }
-
-  @Test
-  public void create_ad_hoc_rule_from_scanner_report() {
-    NewAdHocRule addHocRule = new NewAdHocRule(ScannerReport.AdHocRule.newBuilder()
-      .setEngineId("eslint")
-      .setRuleId("no-cond-assign")
-      .setName("No condition assigned")
-      .setDescription("A description")
-      .setSeverity(Constants.Severity.BLOCKER)
-      .setType(ScannerReport.IssueType.BUG)
-      .build());
-
-    RuleDto rule = underTest.persistAndIndex(dbSession, addHocRule);
-
-    assertThat(rule).isNotNull();
-    assertThat(rule.isExternal()).isTrue();
-    assertThat(rule.isAdHoc()).isTrue();
-    assertThat(rule.getUuid()).isNotBlank();
-    assertThat(rule.getKey()).isEqualTo(RuleKey.of("external_eslint", "no-cond-assign"));
-    assertThat(rule.getName()).isEqualTo("eslint:no-cond-assign");
-    assertThat(rule.getRuleDescriptionSectionDtos()).isEmpty();
-    assertThat(rule.getSeverity()).isNull();
-    assertThat(rule.getType()).isZero();
-    assertThat(rule.getAdHocName()).isEqualTo("No condition assigned");
-    assertThat(rule.getAdHocDescription()).isEqualTo("A description");
-    assertThat(rule.getAdHocSeverity()).isEqualTo(Severity.BLOCKER);
-    assertThat(rule.getAdHocType()).isEqualTo(RuleType.BUG.getDbConstant());
-  }
-
-  @Test
-  public void truncate_metadata_name_and_desc_if_longer_than_max_value() {
-    NewAdHocRule addHocRule = new NewAdHocRule(ScannerReport.AdHocRule.newBuilder()
-      .setEngineId("eslint")
-      .setRuleId("no-cond-assign")
-      .setName(repeat("a", 201))
-      .setDescription(repeat("a", 16_777_216))
-      .setSeverity(Constants.Severity.BLOCKER)
-      .setType(ScannerReport.IssueType.BUG)
-      .build());
-
-    RuleDto rule = underTest.persistAndIndex(dbSession, addHocRule);
-
-    assertThat(rule.getAdHocName()).isEqualTo(repeat("a", 200));
-    assertThat(rule.getAdHocDescription()).isEqualTo(repeat("a", 16_777_215));
-  }
-
-  @Test
-  public void update_metadata_only() {
-    NewAdHocRule addHocRule = new NewAdHocRule(ScannerReport.AdHocRule.newBuilder()
-      .setEngineId("eslint")
-      .setRuleId("no-cond-assign")
-      .setName("No condition assigned")
-      .setDescription("A description")
-      .setSeverity(Constants.Severity.BLOCKER)
-      .setType(ScannerReport.IssueType.BUG)
-      .build());
-    RuleDto rule = underTest.persistAndIndex(dbSession, addHocRule);
-    long creationDate = rule.getCreatedAt();
-    NewAdHocRule addHocRuleUpdated = new NewAdHocRule(ScannerReport.AdHocRule.newBuilder()
-      .setEngineId("eslint")
-      .setRuleId("no-cond-assign")
-      .setName("No condition assigned updated")
-      .setDescription("A description updated")
-      .setSeverity(Constants.Severity.CRITICAL)
-      .setType(ScannerReport.IssueType.CODE_SMELL)
-      .build());
-
-    RuleDto ruleUpdated = underTest.persistAndIndex(dbSession, addHocRuleUpdated);
-
-    assertThat(ruleUpdated).isNotNull();
-    assertThat(ruleUpdated.isExternal()).isTrue();
-    assertThat(ruleUpdated.isAdHoc()).isTrue();
-    assertThat(ruleUpdated.getUuid()).isNotBlank();
-    assertThat(ruleUpdated.getKey()).isEqualTo(RuleKey.of("external_eslint", "no-cond-assign"));
-    assertThat(ruleUpdated.getName()).isEqualTo("eslint:no-cond-assign");
-    assertThat(ruleUpdated.getRuleDescriptionSectionDtos()).isEmpty();
-    assertThat(ruleUpdated.getSeverity()).isNull();
-    assertThat(ruleUpdated.getType()).isZero();
-    assertThat(ruleUpdated.getAdHocName()).isEqualTo("No condition assigned updated");
-    assertThat(ruleUpdated.getAdHocDescription()).isEqualTo("A description updated");
-    assertThat(ruleUpdated.getAdHocSeverity()).isEqualTo(Severity.CRITICAL);
-    assertThat(ruleUpdated.getAdHocType()).isEqualTo(RuleType.CODE_SMELL.getDbConstant());
-    assertThat(ruleUpdated.getCreatedAt()).isEqualTo(creationDate);
-    assertThat(ruleUpdated.getUpdatedAt()).isGreaterThan(creationDate);
-  }
-
-  @Test
-  public void does_not_update_rule_when_no_change() {
-    RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("external_eslint").setIsExternal(true).setIsAdHoc(true));
-
-    RuleDto ruleUpdated = underTest.persistAndIndex(dbSession, new NewAdHocRule(ScannerReport.AdHocRule.newBuilder()
-      .setEngineId("eslint")
-      .setRuleId(rule.getKey().rule())
-      .setName(rule.getAdHocName())
-      .setDescription(rule.getAdHocDescription())
-      .setSeverity(Constants.Severity.valueOf(rule.getAdHocSeverity()))
-      .setType(ScannerReport.IssueType.forNumber(rule.getAdHocType()))
-      .build()));
-
-    assertThat(ruleUpdated).isNotNull();
-    assertThat(ruleUpdated.isExternal()).isTrue();
-    assertThat(ruleUpdated.isAdHoc()).isTrue();
-    assertThat(ruleUpdated.getKey()).isEqualTo(rule.getKey());
-    assertThat(ruleUpdated.getName()).isEqualTo(rule.getName());
-    assertThat(ruleUpdated.getRuleDescriptionSectionDtos()).usingRecursiveFieldByFieldElementComparator().isEqualTo(rule.getRuleDescriptionSectionDtos());
-    assertThat(ruleUpdated.getSeverity()).isEqualTo(rule.getSeverity());
-    assertThat(ruleUpdated.getType()).isEqualTo(rule.getType());
-    assertThat(ruleUpdated.getCreatedAt()).isEqualTo(rule.getCreatedAt());
-    assertThat(ruleUpdated.getUpdatedAt()).isEqualTo(rule.getUpdatedAt());
-
-    assertThat(ruleUpdated.getAdHocName()).isEqualTo(rule.getAdHocName());
-    assertThat(ruleUpdated.getAdHocDescription()).isEqualTo(rule.getAdHocDescription());
-    assertThat(ruleUpdated.getAdHocSeverity()).isEqualTo(rule.getAdHocSeverity());
-    assertThat(ruleUpdated.getAdHocType()).isEqualTo(rule.getAdHocType());
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ClosedIssuesInputFactoryTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ClosedIssuesInputFactoryTest.java
deleted file mode 100644 (file)
index 5e92f2b..0000000
+++ /dev/null
@@ -1,100 +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.issue;
-
-import com.google.common.collect.ImmutableList;
-import java.util.List;
-import java.util.Optional;
-import org.junit.Test;
-import org.sonar.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.component.ReportComponent;
-import org.sonar.ce.task.projectanalysis.filemove.MovedFilesRepository;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.tracking.Input;
-import org.sonar.db.DbClient;
-
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.verifyNoInteractions;
-import static org.mockito.Mockito.when;
-
-public class ClosedIssuesInputFactoryTest {
-  private ComponentIssuesLoader issuesLoader = mock(ComponentIssuesLoader.class);
-  private DbClient dbClient = mock(DbClient.class);
-  private MovedFilesRepository movedFilesRepository = mock(MovedFilesRepository.class);
-  private ClosedIssuesInputFactory underTest = new ClosedIssuesInputFactory(issuesLoader, dbClient, movedFilesRepository);
-
-  @Test
-  public void underTest_returns_inputFactory_loading_closed_issues_only_when_getIssues_is_called() {
-    String componentUuid = randomAlphanumeric(12);
-    ReportComponent component = ReportComponent.builder(Component.Type.FILE, 1).setUuid(componentUuid).build();
-    when(movedFilesRepository.getOriginalFile(component)).thenReturn(Optional.empty());
-
-    Input<DefaultIssue> input = underTest.create(component);
-
-    verifyNoInteractions(dbClient, issuesLoader);
-
-    List<DefaultIssue> issues = ImmutableList.of(new DefaultIssue(), new DefaultIssue());
-    when(issuesLoader.loadClosedIssues(componentUuid)).thenReturn(issues);
-
-    assertThat(input.getIssues()).isSameAs(issues);
-  }
-
-  @Test
-  public void underTest_returns_inputFactory_loading_closed_issues_from_moved_component_when_present() {
-    String componentUuid = randomAlphanumeric(12);
-    String originalComponentUuid = randomAlphanumeric(12);
-    ReportComponent component = ReportComponent.builder(Component.Type.FILE, 1).setUuid(componentUuid).build();
-    when(movedFilesRepository.getOriginalFile(component))
-      .thenReturn(Optional.of(new MovedFilesRepository.OriginalFile(originalComponentUuid, randomAlphanumeric(2))));
-
-    Input<DefaultIssue> input = underTest.create(component);
-
-    verifyNoInteractions(dbClient, issuesLoader);
-
-    List<DefaultIssue> issues = ImmutableList.of();
-    when(issuesLoader.loadClosedIssues(originalComponentUuid)).thenReturn(issues);
-
-    assertThat(input.getIssues()).isSameAs(issues);
-  }
-
-  @Test
-  public void underTest_returns_inputFactory_which_caches_loaded_issues() {
-    String componentUuid = randomAlphanumeric(12);
-    ReportComponent component = ReportComponent.builder(Component.Type.FILE, 1).setUuid(componentUuid).build();
-    when(movedFilesRepository.getOriginalFile(component)).thenReturn(Optional.empty());
-
-    Input<DefaultIssue> input = underTest.create(component);
-
-    verifyNoInteractions(dbClient, issuesLoader);
-
-    List<DefaultIssue> issues = ImmutableList.of(new DefaultIssue());
-    when(issuesLoader.loadClosedIssues(componentUuid)).thenReturn(issues);
-
-    assertThat(input.getIssues()).isSameAs(issues);
-
-    reset(issuesLoader);
-
-    assertThat(input.getIssues()).isSameAs(issues);
-    verifyNoInteractions(issuesLoader);
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesLoaderTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesLoaderTest.java
deleted file mode 100644 (file)
index 7093599..0000000
+++ /dev/null
@@ -1,441 +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.issue;
-
-import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-import java.util.Random;
-import java.util.function.Consumer;
-import java.util.stream.IntStream;
-import java.util.stream.LongStream;
-import javax.annotation.Nullable;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.utils.System2;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.DefaultIssueComment;
-import org.sonar.core.issue.FieldDiffs;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.issue.IssueChangeDto;
-import org.sonar.db.issue.IssueDto;
-import org.sonar.db.rule.RuleDto;
-
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singleton;
-import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verifyNoInteractions;
-import static org.mockito.Mockito.when;
-import static org.sonar.api.issue.Issue.STATUS_CLOSED;
-import static org.sonar.api.rules.RuleType.CODE_SMELL;
-import static org.sonar.api.utils.DateUtils.addDays;
-import static org.sonar.api.utils.DateUtils.parseDateTime;
-import static org.sonar.ce.task.projectanalysis.issue.ComponentIssuesLoader.NUMBER_STATUS_AND_BRANCH_CHANGES_TO_KEEP;
-
-@RunWith(DataProviderRunner.class)
-public class ComponentIssuesLoaderTest {
-  private static final Date NOW = parseDateTime("2018-08-17T13:44:53+0000");
-  private static final Date DATE_LIMIT_30_DAYS_BACK_MIDNIGHT = parseDateTime("2018-07-18T00:00:00+0000");
-
-  @Rule
-  public DbTester db = DbTester.create(System2.INSTANCE);
-
-  private final DbClient dbClient = db.getDbClient();
-  private final System2 system2 = mock(System2.class);
-  private final IssueChangesToDeleteRepository issueChangesToDeleteRepository = new IssueChangesToDeleteRepository();
-
-  @Test
-  public void loadClosedIssues_returns_single_DefaultIssue_by_issue_based_on_first_row() {
-    ComponentDto project = db.components().insertPublicProject();
-    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
-    RuleDto rule = db.rules().insert(t -> t.setType(CODE_SMELL));
-    Date issueDate = addDays(NOW, -10);
-    IssueDto issue = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIssueCloseDate(issueDate).setType(CODE_SMELL));
-    db.issues().insertFieldDiffs(issue, newToClosedDiffsWithLine(issueDate, 10));
-    db.issues().insertFieldDiffs(issue, newToClosedDiffsWithLine(addDays(issueDate, 3), 20));
-    db.issues().insertFieldDiffs(issue, newToClosedDiffsWithLine(addDays(issueDate, 1), 30));
-    when(system2.now()).thenReturn(NOW.getTime());
-
-    ComponentIssuesLoader underTest = newComponentIssuesLoader(newEmptySettings());
-    List<DefaultIssue> defaultIssues = underTest.loadClosedIssues(file.uuid());
-
-    assertThat(defaultIssues).hasSize(1);
-    assertThat(defaultIssues.iterator().next().getLine()).isEqualTo(20);
-  }
-
-  @Test
-  public void loadClosedIssues_returns_single_DefaultIssue_with_null_line_if_first_row_has_no_line_diff() {
-    ComponentDto project = db.components().insertPublicProject();
-    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
-    RuleDto rule = db.rules().insert(t -> t.setType(CODE_SMELL));
-    Date issueDate = addDays(NOW, -10);
-    IssueDto issue = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIssueCloseDate(issueDate).setType(CODE_SMELL));
-    db.issues().insertFieldDiffs(issue, newToClosedDiffsWithLine(issueDate, 10));
-    db.issues().insertFieldDiffs(issue, newToClosedDiffsWithLine(addDays(issueDate, 2), null));
-    db.issues().insertFieldDiffs(issue, newToClosedDiffsWithLine(addDays(issueDate, 1), 30));
-    when(system2.now()).thenReturn(NOW.getTime());
-
-    ComponentIssuesLoader underTest = newComponentIssuesLoader(newEmptySettings());
-    List<DefaultIssue> defaultIssues = underTest.loadClosedIssues(file.uuid());
-
-    assertThat(defaultIssues).hasSize(1);
-    assertThat(defaultIssues.iterator().next().getLine()).isNull();
-  }
-
-  @Test
-  public void loadClosedIssues_returns_only_closed_issues_with_close_date() {
-    ComponentDto project = db.components().insertPublicProject();
-    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
-    RuleDto rule = db.rules().insert(t -> t.setType(CODE_SMELL));
-    Date issueDate = addDays(NOW, -10);
-    IssueDto closedIssue = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIssueCloseDate(issueDate).setType(CODE_SMELL));
-    db.issues().insertFieldDiffs(closedIssue, newToClosedDiffsWithLine(issueDate, 10));
-    IssueDto issueNoCloseDate = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED));
-    db.issues().insertFieldDiffs(issueNoCloseDate, newToClosedDiffsWithLine(issueDate, 10));
-    when(system2.now()).thenReturn(NOW.getTime());
-
-    ComponentIssuesLoader underTest = newComponentIssuesLoader(newEmptySettings());
-    List<DefaultIssue> defaultIssues = underTest.loadClosedIssues(file.uuid());
-
-    assertThat(defaultIssues)
-      .extracting(DefaultIssue::key)
-      .containsOnly(closedIssue.getKey());
-  }
-
-  @Test
-  public void loadClosedIssues_returns_only_closed_issues_which_close_date_is_from_day_30_days_ago() {
-    ComponentIssuesLoader underTest = newComponentIssuesLoader(newEmptySettings());
-    loadClosedIssues_returns_only_closed_issues_with_close_date_is_from_30_days_ago(underTest);
-  }
-
-  @Test
-  public void loadClosedIssues_returns_only_closed_issues_with_close_date_is_from_30_days_ago_if_property_is_empty() {
-    Configuration configuration = newConfiguration(null);
-    ComponentIssuesLoader underTest = newComponentIssuesLoader(configuration);
-
-    loadClosedIssues_returns_only_closed_issues_with_close_date_is_from_30_days_ago(underTest);
-  }
-
-  @Test
-  public void loadClosedIssues_returns_only_closed_with_close_date_is_from_30_days_ago_if_property_is_less_than_0() {
-    Configuration configuration = newConfiguration(String.valueOf(-(1 + new Random().nextInt(10))));
-    ComponentIssuesLoader underTest = newComponentIssuesLoader(configuration);
-
-    loadClosedIssues_returns_only_closed_issues_with_close_date_is_from_30_days_ago(underTest);
-  }
-
-  @Test
-  public void loadClosedIssues_returns_only_closed_with_close_date_is_from_30_days_ago_if_property_is_30() {
-    Configuration configuration = newConfiguration("30");
-    ComponentIssuesLoader underTest = newComponentIssuesLoader(configuration);
-
-    loadClosedIssues_returns_only_closed_issues_with_close_date_is_from_30_days_ago(underTest);
-  }
-
-  @Test
-  @UseDataProvider("notAnIntegerPropertyValues")
-  public void loadClosedIssues_returns_only_closed_with_close_date_is_from_30_days_ago_if_property_is_not_an_integer(String notAnInteger) {
-    Configuration configuration = newConfiguration(notAnInteger);
-    ComponentIssuesLoader underTest = newComponentIssuesLoader(configuration);
-
-    loadClosedIssues_returns_only_closed_issues_with_close_date_is_from_30_days_ago(underTest);
-  }
-
-  @DataProvider
-  public static Object[][] notAnIntegerPropertyValues() {
-    return new Object[][] {
-      {"foo"},
-      {"1,3"},
-      {"1.3"},
-      {"-3.14"}
-    };
-  }
-
-  private void loadClosedIssues_returns_only_closed_issues_with_close_date_is_from_30_days_ago(ComponentIssuesLoader underTest) {
-    ComponentDto project = db.components().insertPublicProject();
-    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
-    RuleDto rule = db.rules().insert(t -> t.setType(CODE_SMELL));
-    Date[] issueDates = new Date[] {
-      addDays(NOW, -10),
-      addDays(NOW, -31),
-      addDays(NOW, -30),
-      DATE_LIMIT_30_DAYS_BACK_MIDNIGHT,
-      addDays(NOW, -29),
-      addDays(NOW, -60),
-    };
-    IssueDto[] issues = Arrays.stream(issueDates)
-      .map(issueDate -> {
-        IssueDto closedIssue = db.issues().insert(rule, project, file, t -> t.setStatus(STATUS_CLOSED).setIssueCloseDate(issueDate).setType(CODE_SMELL));
-        db.issues().insertFieldDiffs(closedIssue, newToClosedDiffsWithLine(issueDate, 10));
-        return closedIssue;
-      })
-      .toArray(IssueDto[]::new);
-    when(system2.now()).thenReturn(NOW.getTime());
-
-    List<DefaultIssue> defaultIssues = underTest.loadClosedIssues(file.uuid());
-
-    assertThat(defaultIssues)
-      .extracting(DefaultIssue::key)
-      .containsOnly(issues[0].getKey(), issues[2].getKey(), issues[3].getKey(), issues[4].getKey());
-  }
-
-  @Test
-  public void loadClosedIssues_returns_empty_without_querying_DB_if_property_is_0() {
-    System2 system2 = mock(System2.class);
-    DbClient dbClient = mock(DbClient.class);
-    Configuration configuration = newConfiguration("0");
-    String componentUuid = randomAlphabetic(15);
-    ComponentIssuesLoader underTest = new ComponentIssuesLoader(dbClient, null, null, configuration, system2, issueChangesToDeleteRepository);
-
-    assertThat(underTest.loadClosedIssues(componentUuid)).isEmpty();
-
-    verifyNoInteractions(dbClient, system2);
-  }
-
-  @Test
-  public void loadLatestDiffChangesForReopeningOfClosedIssues_collects_issue_changes_to_delete() {
-    IssueDto issue = db.issues().insert();
-    for (long i = 0; i < NUMBER_STATUS_AND_BRANCH_CHANGES_TO_KEEP + 5; i++) {
-      db.issues().insertChange(issue, diffIssueChangeModifier(i, "status"));
-    }
-    // should not be deleted
-    db.issues().insertChange(issue, diffIssueChangeModifier(-1, "other"));
-
-    ComponentIssuesLoader underTest = new ComponentIssuesLoader(dbClient, null, null, newConfiguration("0"), null, issueChangesToDeleteRepository);
-
-    underTest.loadLatestDiffChangesForReopeningOfClosedIssues(singleton(new DefaultIssue().setKey(issue.getKey())));
-    assertThat(issueChangesToDeleteRepository.getUuids()).containsOnly("0", "1", "2", "3", "4");
-  }
-
-  @Test
-  public void loadLatestDiffChangesForReopeningOfClosedIssues_does_not_query_DB_if_issue_list_is_empty() {
-    DbClient dbClient = mock(DbClient.class);
-    ComponentIssuesLoader underTest = new ComponentIssuesLoader(dbClient, null, null, newConfiguration("0"), null, issueChangesToDeleteRepository);
-
-    underTest.loadLatestDiffChangesForReopeningOfClosedIssues(emptyList());
-
-    verifyNoInteractions(dbClient, system2);
-  }
-
-  @Test
-  @UseDataProvider("statusOrResolutionFieldName")
-  public void loadLatestDiffChangesForReopeningOfClosedIssues_add_diff_change_with_most_recent_status_or_resolution(String statusOrResolutionFieldName) {
-    IssueDto issue = db.issues().insert();
-    db.issues().insertChange(issue, t -> t.setChangeData(randomDiffWith(statusOrResolutionFieldName, "val1")).setIssueChangeCreationDate(5));
-    db.issues().insertChange(issue, t -> t.setChangeData(randomDiffWith(statusOrResolutionFieldName, "val2")).setIssueChangeCreationDate(20));
-    db.issues().insertChange(issue, t -> t.setChangeData(randomDiffWith(statusOrResolutionFieldName, "val3")).setIssueChangeCreationDate(13));
-    ComponentIssuesLoader underTest = new ComponentIssuesLoader(dbClient, null, null, newConfiguration("0"), null, issueChangesToDeleteRepository);
-    DefaultIssue defaultIssue = new DefaultIssue().setKey(issue.getKey());
-
-    underTest.loadLatestDiffChangesForReopeningOfClosedIssues(singleton(defaultIssue));
-
-    assertThat(defaultIssue.changes())
-      .hasSize(1);
-    assertThat(defaultIssue.changes())
-      .extracting(t -> t.get(statusOrResolutionFieldName))
-      .filteredOn(t -> hasValue(t, "val2"))
-      .hasSize(1);
-  }
-
-  @Test
-  public void loadLatestDiffChangesForReopeningOfClosedIssues_add_single_diff_change_when_most_recent_status_and_resolution_is_the_same_diff() {
-    IssueDto issue = db.issues().insert();
-    db.issues().insertChange(issue, t -> t.setChangeData(randomDiffWith("status", "valStatus1")).setIssueChangeCreationDate(5));
-    db.issues().insertChange(issue, t -> t.setChangeData(randomDiffWith("status", "valStatus2")).setIssueChangeCreationDate(19));
-    db.issues().insertChange(issue, t -> t.setChangeData(randomDiffWith("status", "valStatus3", "resolution", "valRes3")).setIssueChangeCreationDate(20));
-    db.issues().insertChange(issue, t -> t.setChangeData(randomDiffWith("resolution", "valRes4")).setIssueChangeCreationDate(13));
-    ComponentIssuesLoader underTest = new ComponentIssuesLoader(dbClient, null, null, newConfiguration("0"), null, issueChangesToDeleteRepository);
-    DefaultIssue defaultIssue = new DefaultIssue().setKey(issue.getKey());
-
-    underTest.loadLatestDiffChangesForReopeningOfClosedIssues(singleton(defaultIssue));
-
-    assertThat(defaultIssue.changes())
-      .hasSize(1);
-    assertThat(defaultIssue.changes())
-      .extracting(t -> t.get("status"))
-      .filteredOn(t -> hasValue(t, "valStatus3"))
-      .hasSize(1);
-    assertThat(defaultIssue.changes())
-      .extracting(t -> t.get("resolution"))
-      .filteredOn(t -> hasValue(t, "valRes3"))
-      .hasSize(1);
-  }
-
-  @Test
-  public void loadLatestDiffChangesForReopeningOfClosedIssues_adds_2_diff_changes_if_most_recent_status_and_resolution_are_not_the_same_diff() {
-    IssueDto issue = db.issues().insert();
-    db.issues().insertChange(issue, t -> t.setChangeData(randomDiffWith("status", "valStatus1")).setIssueChangeCreationDate(5));
-    db.issues().insertChange(issue, t -> t.setChangeData(randomDiffWith("status", "valStatus2", "resolution", "valRes2")).setIssueChangeCreationDate(19));
-    db.issues().insertChange(issue, t -> t.setChangeData(randomDiffWith("status", "valStatus3")).setIssueChangeCreationDate(20));
-    db.issues().insertChange(issue, t -> t.setChangeData(randomDiffWith("resolution", "valRes4")).setIssueChangeCreationDate(13));
-    ComponentIssuesLoader underTest = new ComponentIssuesLoader(dbClient, null /* not used in method */, null /* not used in method */,
-      newConfiguration("0"), null /* not used by method */, issueChangesToDeleteRepository);
-    DefaultIssue defaultIssue = new DefaultIssue().setKey(issue.getKey());
-
-    underTest.loadLatestDiffChangesForReopeningOfClosedIssues(singleton(defaultIssue));
-
-    assertThat(defaultIssue.changes())
-      .hasSize(2);
-    assertThat(defaultIssue.changes())
-      .extracting(t -> t.get("status"))
-      .filteredOn(t -> hasValue(t, "valStatus3"))
-      .hasSize(1);
-    assertThat(defaultIssue.changes())
-      .extracting(t -> t.get("resolution"))
-      .filteredOn(t -> hasValue(t, "valRes2"))
-      .hasSize(1);
-  }
-
-  @Test
-  public void loadChanges_should_filter_out_old_status_changes() {
-    IssueDto issue = db.issues().insert();
-    for (int i = 0; i < NUMBER_STATUS_AND_BRANCH_CHANGES_TO_KEEP + 1; i++) {
-      db.issues().insertChange(issue, diffIssueChangeModifier(i, "status"));
-    }
-    // these are kept
-    db.issues().insertChange(issue, diffIssueChangeModifier(NUMBER_STATUS_AND_BRANCH_CHANGES_TO_KEEP + 1, "other"));
-    db.issues().insertChange(issue, t -> t
-      .setChangeType(IssueChangeDto.TYPE_COMMENT)
-      .setKey("comment1"));
-
-    ComponentIssuesLoader underTest = new ComponentIssuesLoader(dbClient, null, null, newConfiguration("0"), null, issueChangesToDeleteRepository);
-    DefaultIssue defaultIssue = new DefaultIssue().setKey(issue.getKey());
-    underTest.loadChanges(db.getSession(), singleton(defaultIssue));
-
-    assertThat(defaultIssue.changes())
-      .extracting(d -> d.creationDate().getTime())
-      .containsOnly(LongStream.rangeClosed(1, NUMBER_STATUS_AND_BRANCH_CHANGES_TO_KEEP + 1).boxed().toArray(Long[]::new));
-    assertThat(defaultIssue.defaultIssueComments()).extracting(DefaultIssueComment::key).containsOnly("comment1");
-    assertThat(issueChangesToDeleteRepository.getUuids()).containsOnly("0");
-  }
-
-  @Test
-  public void loadChanges_should_filter_out_old_from_branch_changes() {
-    IssueDto issue = db.issues().insert();
-    for (int i = 0; i < NUMBER_STATUS_AND_BRANCH_CHANGES_TO_KEEP + 1; i++) {
-      db.issues().insertChange(issue, diffIssueChangeModifier(i, "from_branch"));
-    }
-
-    ComponentIssuesLoader underTest = new ComponentIssuesLoader(dbClient, null, null, newConfiguration("0"), null, issueChangesToDeleteRepository);
-    DefaultIssue defaultIssue = new DefaultIssue().setKey(issue.getKey());
-    underTest.loadChanges(db.getSession(), singleton(defaultIssue));
-    assertThat(defaultIssue.changes())
-      .extracting(d -> d.creationDate().getTime())
-      .containsOnly(LongStream.rangeClosed(1, NUMBER_STATUS_AND_BRANCH_CHANGES_TO_KEEP).boxed().toArray(Long[]::new));
-    assertThat(issueChangesToDeleteRepository.getUuids()).containsOnly("0");
-  }
-
-  private Consumer<IssueChangeDto> diffIssueChangeModifier(long created, String field) {
-    return issueChangeDto -> issueChangeDto
-      .setChangeData(new FieldDiffs().setDiff(field, "A", "B").toEncodedString())
-      .setIssueChangeCreationDate(created)
-      .setUuid(String.valueOf(created));
-  }
-
-  private static boolean hasValue(@Nullable FieldDiffs.Diff t, String value) {
-    if (t == null) {
-      return false;
-    }
-    return (t.oldValue() == null || value.equals(t.oldValue())) && (t.newValue() == null || value.equals(t.newValue()));
-  }
-
-  @DataProvider
-  public static Object[][] statusOrResolutionFieldName() {
-    return new Object[][] {
-      {"status"},
-      {"resolution"},
-    };
-  }
-
-  private static String randomDiffWith(String... fieldsAndValues) {
-    Random random = new Random();
-    List<Diff> diffs = new ArrayList<>();
-    for (int i = 0; i < fieldsAndValues.length; i++) {
-      int oldOrNew = random.nextInt(3);
-      String value = fieldsAndValues[i + 1];
-      diffs.add(new Diff(fieldsAndValues[i], oldOrNew <= 2 ? value : null, oldOrNew >= 2 ? value : null));
-      i++;
-    }
-    IntStream.range(0, random.nextInt(5))
-      .forEach(i -> diffs.add(new Diff(randomAlphabetic(10), random.nextBoolean() ? null : randomAlphabetic(11), random.nextBoolean() ? null : randomAlphabetic(12))));
-    Collections.shuffle(diffs);
-
-    FieldDiffs res = new FieldDiffs();
-    diffs.forEach(diff -> res.setDiff(diff.field, diff.oldValue, diff.newValue));
-    return res.toEncodedString();
-  }
-
-  private static final class Diff {
-    private final String field;
-    private final String oldValue;
-    private final String newValue;
-
-    private Diff(String field, @Nullable String oldValue, @Nullable String newValue) {
-      this.field = field;
-      this.oldValue = oldValue;
-      this.newValue = newValue;
-    }
-  }
-
-  private static FieldDiffs newToClosedDiffsWithLine(Date creationDate, @Nullable Integer oldLineValue) {
-    FieldDiffs fieldDiffs = new FieldDiffs().setCreationDate(addDays(creationDate, -5))
-      .setDiff("status", randomNonCloseStatus(), STATUS_CLOSED);
-    if (oldLineValue != null) {
-      fieldDiffs.setDiff("line", oldLineValue, "");
-    }
-    return fieldDiffs;
-  }
-
-  private static String randomNonCloseStatus() {
-    String[] nonCloseStatuses = Issue.STATUSES.stream()
-      .filter(t -> !STATUS_CLOSED.equals(t))
-      .toArray(String[]::new);
-    return nonCloseStatuses[new Random().nextInt(nonCloseStatuses.length)];
-  }
-
-  private ComponentIssuesLoader newComponentIssuesLoader(Configuration configuration) {
-    return new ComponentIssuesLoader(dbClient, null /* not used in loadClosedIssues */, null /* not used in loadClosedIssues */,
-      configuration, system2, issueChangesToDeleteRepository);
-  }
-
-  private static Configuration newEmptySettings() {
-    return new MapSettings().asConfig();
-  }
-
-  private static Configuration newConfiguration(@Nullable String maxAge) {
-    MapSettings settings = new MapSettings();
-    settings.setProperty("sonar.issuetracking.closedissues.maxage", maxAge);
-    return settings.asConfig();
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/DefaultAssigneeTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/DefaultAssigneeTest.java
deleted file mode 100644 (file)
index 9b4f73d..0000000
+++ /dev/null
@@ -1,82 +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.issue;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.ce.task.projectanalysis.component.ConfigurationRepository;
-import org.sonar.ce.task.projectanalysis.component.TestSettingsRepository;
-import org.sonar.db.DbTester;
-import org.sonar.db.user.UserDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class DefaultAssigneeTest {
-
-  public static final String PROJECT_KEY = "PROJECT_KEY";
-
-  @Rule
-  public DbTester db = DbTester.create();
-
-  private final MapSettings settings = new MapSettings();
-  private final ConfigurationRepository settingsRepository = new TestSettingsRepository(settings.asConfig());
-  private final DefaultAssignee underTest = new DefaultAssignee(db.getDbClient(), settingsRepository);
-
-  @Test
-  public void no_default_assignee() {
-    assertThat(underTest.loadDefaultAssigneeUuid()).isNull();
-  }
-
-  @Test
-  public void set_default_assignee() {
-    settings.setProperty(CoreProperties.DEFAULT_ISSUE_ASSIGNEE, "erik");
-    UserDto userDto = db.users().insertUser("erik");
-
-    assertThat(underTest.loadDefaultAssigneeUuid()).isEqualTo(userDto.getUuid());
-  }
-
-  @Test
-  public void configured_login_does_not_exist() {
-    settings.setProperty(CoreProperties.DEFAULT_ISSUE_ASSIGNEE, "erik");
-
-    assertThat(underTest.loadDefaultAssigneeUuid()).isNull();
-  }
-
-  @Test
-  public void configured_login_is_disabled() {
-    settings.setProperty(CoreProperties.DEFAULT_ISSUE_ASSIGNEE, "erik");
-    db.users().insertUser(user -> user.setLogin("erik").setActive(false));
-
-    assertThat(underTest.loadDefaultAssigneeUuid()).isNull();
-  }
-
-  @Test
-  public void default_assignee_is_cached() {
-    settings.setProperty(CoreProperties.DEFAULT_ISSUE_ASSIGNEE, "erik");
-    UserDto userDto = db.users().insertUser("erik");
-    assertThat(underTest.loadDefaultAssigneeUuid()).isEqualTo(userDto.getUuid());
-
-    // The setting is updated but the assignee hasn't changed
-    settings.setProperty(CoreProperties.DEFAULT_ISSUE_ASSIGNEE, "other");
-    assertThat(underTest.loadDefaultAssigneeUuid()).isEqualTo(userDto.getUuid());
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java
deleted file mode 100644 (file)
index 1a3efbb..0000000
+++ /dev/null
@@ -1,366 +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.issue;
-
-import java.util.Date;
-import java.util.List;
-import java.util.Optional;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.mockito.ArgumentCaptor;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rule.Severity;
-import org.sonar.api.utils.DateUtils;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
-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.FileStatuses;
-import org.sonar.ce.task.projectanalysis.component.ReferenceBranchComponentUuids;
-import org.sonar.ce.task.projectanalysis.component.ReportComponent;
-import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
-import org.sonar.ce.task.projectanalysis.component.TypeAwareVisitor;
-import org.sonar.ce.task.projectanalysis.filemove.MovedFilesRepository;
-import org.sonar.ce.task.projectanalysis.issue.filter.IssueFilter;
-import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRulesHolder;
-import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRulesHolderRule;
-import org.sonar.ce.task.projectanalysis.qualityprofile.AlwaysActiveRulesHolderImpl;
-import org.sonar.ce.task.projectanalysis.source.NewLinesRepository;
-import org.sonar.ce.task.projectanalysis.source.SourceLinesHashRepository;
-import org.sonar.ce.task.projectanalysis.source.SourceLinesRepository;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.FieldDiffs;
-import org.sonar.core.issue.IssueChangeContext;
-import org.sonar.core.issue.tracking.Tracker;
-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.issue.IssueDto;
-import org.sonar.db.issue.IssueTesting;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.db.rule.RuleTesting;
-import org.sonar.scanner.protocol.Constants;
-import org.sonar.scanner.protocol.output.ScannerReport;
-import org.sonar.server.issue.IssueFieldsSetter;
-import org.sonar.server.issue.workflow.IssueWorkflow;
-
-import static com.google.common.collect.Lists.newArrayList;
-import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.entry;
-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.when;
-
-public class IntegrateIssuesVisitorTest {
-
-  private static final String FILE_UUID = "FILE_UUID";
-  private static final String FILE_UUID_ON_BRANCH = "FILE_UUID_BRANCH";
-  private static final String FILE_KEY = "FILE_KEY";
-  private static final int FILE_REF = 2;
-
-  private static final Component FILE = ReportComponent.builder(Component.Type.FILE, FILE_REF)
-    .setKey(FILE_KEY)
-    .setUuid(FILE_UUID)
-    .build();
-
-  private static final String PROJECT_KEY = "PROJECT_KEY";
-  private static final String PROJECT_UUID = "PROJECT_UUID";
-  private static final String PROJECT_UUID_ON_BRANCH = "PROJECT_UUID_BRANCH";
-  private static final int PROJECT_REF = 1;
-  private static final Component PROJECT = ReportComponent.builder(Component.Type.PROJECT, PROJECT_REF)
-    .setKey(PROJECT_KEY)
-    .setUuid(PROJECT_UUID)
-    .addChildren(FILE)
-    .build();
-
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-  @Rule
-  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
-  @Rule
-  public ActiveRulesHolderRule activeRulesHolderRule = new ActiveRulesHolderRule();
-  @Rule
-  public RuleRepositoryRule ruleRepositoryRule = new RuleRepositoryRule();
-
-  private final AnalysisMetadataHolder analysisMetadataHolder = mock(AnalysisMetadataHolder.class);
-  private final IssueFilter issueFilter = mock(IssueFilter.class);
-  private final MovedFilesRepository movedFilesRepository = mock(MovedFilesRepository.class);
-  private final IssueChangeContext issueChangeContext = mock(IssueChangeContext.class);
-  private final IssueLifecycle issueLifecycle = new IssueLifecycle(analysisMetadataHolder, issueChangeContext, mock(IssueWorkflow.class), new IssueFieldsSetter(),
-    mock(DebtCalculator.class), ruleRepositoryRule);
-  private final IssueVisitor issueVisitor = mock(IssueVisitor.class);
-  private final ReferenceBranchComponentUuids mergeBranchComponentsUuids = mock(ReferenceBranchComponentUuids.class);
-  private final SiblingsIssueMerger issueStatusCopier = mock(SiblingsIssueMerger.class);
-  private final ReferenceBranchComponentUuids referenceBranchComponentUuids = mock(ReferenceBranchComponentUuids.class);
-  private final SourceLinesHashRepository sourceLinesHash = mock(SourceLinesHashRepository.class);
-  private final NewLinesRepository newLinesRepository = mock(NewLinesRepository.class);
-  private final TargetBranchComponentUuids targetBranchComponentUuids = mock(TargetBranchComponentUuids.class);
-  private final SourceLinesRepository sourceLinesRepository = mock(SourceLinesRepository.class);
-  private final FileStatuses fileStatuses = mock(FileStatuses.class);
-  private ArgumentCaptor<DefaultIssue> defaultIssueCaptor;
-
-  private final ComponentIssuesLoader issuesLoader = new ComponentIssuesLoader(dbTester.getDbClient(), ruleRepositoryRule, activeRulesHolderRule, new MapSettings().asConfig(),
-    System2.INSTANCE, mock(IssueChangesToDeleteRepository.class));
-  private IssueTrackingDelegator trackingDelegator;
-  private TrackerExecution tracker;
-  private PullRequestTrackerExecution prBranchTracker;
-  private ReferenceBranchTrackerExecution mergeBranchTracker;
-  private final ActiveRulesHolder activeRulesHolder = new AlwaysActiveRulesHolderImpl();
-  private ProtoIssueCache protoIssueCache;
-
-  private TypeAwareVisitor underTest;
-
-  @Before
-  public void setUp() throws Exception {
-    IssueVisitors issueVisitors = new IssueVisitors(new IssueVisitor[] {issueVisitor});
-
-    defaultIssueCaptor = ArgumentCaptor.forClass(DefaultIssue.class);
-    when(movedFilesRepository.getOriginalFile(any(Component.class))).thenReturn(Optional.empty());
-
-    DbClient dbClient = dbTester.getDbClient();
-    TrackerRawInputFactory rawInputFactory = new TrackerRawInputFactory(treeRootHolder, reportReader, sourceLinesHash, issueFilter,
-      ruleRepositoryRule, activeRulesHolder);
-    TrackerBaseInputFactory baseInputFactory = new TrackerBaseInputFactory(issuesLoader, dbClient, movedFilesRepository);
-    TrackerTargetBranchInputFactory targetInputFactory = new TrackerTargetBranchInputFactory(issuesLoader, targetBranchComponentUuids, dbClient, movedFilesRepository);
-    TrackerReferenceBranchInputFactory mergeInputFactory = new TrackerReferenceBranchInputFactory(issuesLoader, mergeBranchComponentsUuids, dbClient);
-    ClosedIssuesInputFactory closedIssuesInputFactory = new ClosedIssuesInputFactory(issuesLoader, dbClient, movedFilesRepository);
-    tracker = new TrackerExecution(baseInputFactory, closedIssuesInputFactory, new Tracker<>(), issuesLoader, analysisMetadataHolder);
-    mergeBranchTracker = new ReferenceBranchTrackerExecution(mergeInputFactory, new Tracker<>());
-    prBranchTracker = new PullRequestTrackerExecution(baseInputFactory, targetInputFactory, new Tracker<>(), newLinesRepository);
-    trackingDelegator = new IssueTrackingDelegator(prBranchTracker, mergeBranchTracker, tracker, analysisMetadataHolder);
-    treeRootHolder.setRoot(PROJECT);
-    protoIssueCache = new ProtoIssueCache(temp.newFile(), System2.INSTANCE);
-    when(issueFilter.accept(any(DefaultIssue.class), eq(FILE))).thenReturn(true);
-    when(issueChangeContext.date()).thenReturn(new Date());
-    underTest = new IntegrateIssuesVisitor(protoIssueCache, rawInputFactory, baseInputFactory, issueLifecycle, issueVisitors, trackingDelegator, issueStatusCopier,
-      referenceBranchComponentUuids, mock(PullRequestSourceBranchMerger.class), fileStatuses);
-  }
-
-  @Test
-  public void process_new_issue() {
-    ruleRepositoryRule.add(RuleKey.of("xoo", "S001"));
-    when(analysisMetadataHolder.isBranch()).thenReturn(true);
-    ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
-      .setMsg("the message")
-      .setRuleRepository("xoo")
-      .setRuleKey("S001")
-      .setSeverity(Constants.Severity.BLOCKER)
-      .build();
-    reportReader.putIssues(FILE_REF, singletonList(reportIssue));
-
-    underTest.visitAny(FILE);
-
-    assertThat(newArrayList(protoIssueCache.traverse())).hasSize(1);
-  }
-
-  @Test
-  public void process_existing_issue() {
-    RuleKey ruleKey = RuleTesting.XOO_X1;
-    // Issue from db has severity major
-    addBaseIssue(ruleKey);
-
-    // Issue from report has severity blocker
-    ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
-      .setMsg("new message")
-      .setRuleRepository(ruleKey.repository())
-      .setRuleKey(ruleKey.rule())
-      .setSeverity(Constants.Severity.BLOCKER)
-      .build();
-    reportReader.putIssues(FILE_REF, singletonList(reportIssue));
-
-    underTest.visitAny(FILE);
-
-    List<DefaultIssue> issues = newArrayList(protoIssueCache.traverse());
-    assertThat(issues).hasSize(1);
-    assertThat(issues.get(0).severity()).isEqualTo(Severity.BLOCKER);
-  }
-
-  @Test
-  public void dont_cache_existing_issue_if_unmodified() {
-    RuleKey ruleKey = RuleTesting.XOO_X1;
-    // Issue from db has severity major
-    addBaseIssue(ruleKey);
-
-    // Issue from report has severity blocker
-    ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
-      .setMsg("the message")
-      .setRuleRepository(ruleKey.repository())
-      .setRuleKey(ruleKey.rule())
-      .setSeverity(Constants.Severity.BLOCKER)
-      .build();
-    reportReader.putIssues(FILE_REF, singletonList(reportIssue));
-
-    underTest.visitAny(FILE);
-
-    List<DefaultIssue> issues = newArrayList(protoIssueCache.traverse());
-    assertThat(issues).hasSize(1);
-    assertThat(issues.get(0).severity()).isEqualTo(Severity.BLOCKER);
-  }
-
-  @Test
-  public void execute_issue_visitors() {
-    ruleRepositoryRule.add(RuleKey.of("xoo", "S001"));
-    ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
-      .setMsg("the message")
-      .setRuleRepository("xoo")
-      .setRuleKey("S001")
-      .setSeverity(Constants.Severity.BLOCKER)
-      .build();
-    reportReader.putIssues(FILE_REF, singletonList(reportIssue));
-
-    underTest.visitAny(FILE);
-
-    verify(issueVisitor).beforeComponent(FILE);
-    verify(issueVisitor).afterComponent(FILE);
-    verify(issueVisitor).onIssue(eq(FILE), defaultIssueCaptor.capture());
-    assertThat(defaultIssueCaptor.getValue().ruleKey().rule()).isEqualTo("S001");
-  }
-
-  @Test
-  public void close_unmatched_base_issue() {
-    RuleKey ruleKey = RuleTesting.XOO_X1;
-    addBaseIssue(ruleKey);
-
-    // No issue in the report
-    underTest.visitAny(FILE);
-
-    List<DefaultIssue> issues = newArrayList(protoIssueCache.traverse());
-    assertThat(issues).isEmpty();
-  }
-
-  @Test
-  public void remove_uuid_of_original_file_from_componentsWithUnprocessedIssues_if_component_has_one() {
-    String originalFileUuid = "original file uuid";
-    when(movedFilesRepository.getOriginalFile(FILE))
-      .thenReturn(Optional.of(new MovedFilesRepository.OriginalFile(originalFileUuid, "original file key")));
-
-    underTest.visitAny(FILE);
-  }
-
-  @Test
-  public void reuse_issues_when_data_unchanged() {
-    RuleKey ruleKey = RuleTesting.XOO_X1;
-    // Issue from db has severity major
-    addBaseIssue(ruleKey);
-
-    // Issue from report has severity blocker
-    ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
-      .setMsg("new message")
-      .setRuleRepository(ruleKey.repository())
-      .setRuleKey(ruleKey.rule())
-      .setSeverity(Constants.Severity.BLOCKER)
-      .build();
-    reportReader.putIssues(FILE_REF, singletonList(reportIssue));
-    when(fileStatuses.isDataUnchanged(FILE)).thenReturn(true);
-
-    underTest.visitAny(FILE);
-
-    // visitors get called, so measures created from issues should be calculated taking these issues into account
-    verify(issueVisitor).onIssue(eq(FILE), defaultIssueCaptor.capture());
-    assertThat(defaultIssueCaptor.getValue().ruleKey().rule()).isEqualTo(ruleKey.rule());
-
-    // most issues won't go to the cache since they aren't changed and don't need to be persisted
-    // In this test they are being closed but the workflows aren't working (we mock them) so nothing is changed on the issue is not cached.
-    assertThat(newArrayList(protoIssueCache.traverse())).isEmpty();
-  }
-
-  @Test
-  public void copy_issues_when_creating_new_non_main_branch() {
-    when(mergeBranchComponentsUuids.getComponentUuid(FILE_KEY)).thenReturn(FILE_UUID_ON_BRANCH);
-    when(referenceBranchComponentUuids.getReferenceBranchName()).thenReturn("master");
-
-    when(analysisMetadataHolder.isBranch()).thenReturn(true);
-    when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(true);
-    Branch branch = mock(Branch.class);
-    when(branch.isMain()).thenReturn(false);
-    when(branch.getType()).thenReturn(BranchType.BRANCH);
-    when(analysisMetadataHolder.getBranch()).thenReturn(branch);
-
-    RuleKey ruleKey = RuleTesting.XOO_X1;
-    // Issue from main branch has severity major
-    addBaseIssueOnBranch(ruleKey);
-
-    // Issue from report has severity blocker
-    ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
-      .setMsg("the message")
-      .setRuleRepository(ruleKey.repository())
-      .setRuleKey(ruleKey.rule())
-      .setSeverity(Constants.Severity.BLOCKER)
-      .build();
-    reportReader.putIssues(FILE_REF, singletonList(reportIssue));
-
-    underTest.visitAny(FILE);
-
-    List<DefaultIssue> issues = newArrayList(protoIssueCache.traverse());
-    assertThat(issues).hasSize(1);
-    assertThat(issues.get(0).severity()).isEqualTo(Severity.BLOCKER);
-    assertThat(issues.get(0).isNew()).isFalse();
-    assertThat(issues.get(0).isCopied()).isTrue();
-    assertThat(issues.get(0).changes()).hasSize(1);
-    assertThat(issues.get(0).changes().get(0).diffs()).contains(entry(IssueFieldsSetter.FROM_BRANCH, new FieldDiffs.Diff<>("master", null)));
-  }
-
-  private void addBaseIssue(RuleKey ruleKey) {
-    ComponentDto project = ComponentTesting.newPrivateProjectDto(PROJECT_UUID).setKey(PROJECT_KEY);
-    ComponentDto file = ComponentTesting.newFileDto(project, null, FILE_UUID).setKey(FILE_KEY);
-    dbTester.components().insertComponents(project, file);
-
-    RuleDto ruleDto = RuleTesting.newRule(ruleKey);
-    dbTester.rules().insert(ruleDto);
-    ruleRepositoryRule.add(ruleKey);
-
-    IssueDto issue = IssueTesting.newIssue(ruleDto, project, file)
-      .setKee("ISSUE")
-      .setSeverity(Severity.MAJOR);
-    dbTester.getDbClient().issueDao().insert(dbTester.getSession(), issue);
-    dbTester.getSession().commit();
-  }
-
-  private void addBaseIssueOnBranch(RuleKey ruleKey) {
-    ComponentDto project = ComponentTesting.newPrivateProjectDto(PROJECT_UUID_ON_BRANCH).setKey(PROJECT_KEY);
-    ComponentDto file = ComponentTesting.newFileDto(project, null, FILE_UUID_ON_BRANCH).setKey(FILE_KEY);
-    dbTester.components().insertComponents(project, file);
-
-    RuleDto ruleDto = RuleTesting.newRule(ruleKey);
-    dbTester.rules().insert(ruleDto);
-    ruleRepositoryRule.add(ruleKey);
-
-    IssueDto issue = IssueTesting.newIssue(ruleDto, project, file)
-      .setKee("ISSUE")
-      .setSeverity(Severity.MAJOR)
-      .setChecksum(null);
-    dbTester.getDbClient().issueDao().insert(dbTester.getSession(), issue);
-    dbTester.getSession().commit();
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ProjectTrackerBaseLazyInputTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ProjectTrackerBaseLazyInputTest.java
deleted file mode 100644 (file)
index 397447f..0000000
+++ /dev/null
@@ -1,84 +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.issue;
-
-import java.util.Date;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-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.component.Component;
-import org.sonar.ce.task.projectanalysis.component.ReportComponent;
-import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRulesHolderRule;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.issue.IssueDto;
-import org.sonar.db.rule.RuleDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.sonar.api.utils.DateUtils.parseDate;
-import static org.sonar.db.component.ComponentTesting.newFileDto;
-
-public class ProjectTrackerBaseLazyInputTest {
-
-  private static final Date ANALYSIS_DATE = parseDate("2016-06-01");
-
-  @Rule
-  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule().setAnalysisDate(ANALYSIS_DATE);
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-  @Rule
-  public ActiveRulesHolderRule activeRulesHolderRule = new ActiveRulesHolderRule();
-  @Rule
-  public RuleRepositoryRule ruleRepositoryRule = new RuleRepositoryRule();
-
-  private DbClient dbClient = dbTester.getDbClient();
-  private ProjectTrackerBaseLazyInput underTest;
-  private RuleDto rule;
-  private ComponentDto rootProjectDto;
-  private ComponentIssuesLoader issuesLoader = new ComponentIssuesLoader(dbTester.getDbClient(), ruleRepositoryRule, activeRulesHolderRule, new MapSettings().asConfig(),
-    System2.INSTANCE, mock(IssueChangesToDeleteRepository.class));
-
-  @Before
-  public void prepare() {
-    rule = dbTester.rules().insert();
-    ruleRepositoryRule.add(rule.getKey());
-    rootProjectDto = dbTester.components().insertPublicProject();
-    ReportComponent rootProject = ReportComponent.builder(Component.Type.FILE, 1)
-      .setKey(rootProjectDto.getKey())
-      .setUuid(rootProjectDto.uuid()).build();
-    underTest = new ProjectTrackerBaseLazyInput(dbClient, issuesLoader, rootProject);
-  }
-
-  @Test
-  public void return_only_open_project_issues_if_no_folders() {
-    ComponentDto file = dbTester.components().insertComponent(newFileDto(rootProjectDto));
-    IssueDto openIssueOnProject = dbTester.issues().insert(rule, rootProjectDto, rootProjectDto, i -> i.setStatus("OPEN").setResolution(null));
-    IssueDto closedIssueOnProject = dbTester.issues().insert(rule, rootProjectDto, rootProjectDto, i -> i.setStatus("CLOSED").setResolution("FIXED"));
-    IssueDto openIssue1OnFile = dbTester.issues().insert(rule, rootProjectDto, file, i -> i.setStatus("OPEN").setResolution(null));
-
-    assertThat(underTest.loadIssues()).extracting(DefaultIssue::key).containsOnly(openIssueOnProject.getKey());
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ScmAccountToUserLoaderTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ScmAccountToUserLoaderTest.java
deleted file mode 100644 (file)
index 5f4b2b0..0000000
+++ /dev/null
@@ -1,84 +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.issue;
-
-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.db.DbTester;
-import org.sonar.db.user.UserDto;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.user.index.UserIndex;
-import org.sonar.server.user.index.UserIndexer;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptyList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
-
-public class ScmAccountToUserLoaderTest {
-
-  @Rule
-  public DbTester db = DbTester.create();
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public LogTester logTester = new LogTester();
-
-  private UserIndexer userIndexer = new UserIndexer(db.getDbClient(), es.client());
-
-  @Test
-  public void load_login_for_scm_account() {
-    UserDto user = db.users().insertUser(u -> u.setScmAccounts(asList("charlie", "jesuis@charlie.com")));
-    userIndexer.indexAll();
-
-    UserIndex index = new UserIndex(es.client(), System2.INSTANCE);
-    ScmAccountToUserLoader underTest = new ScmAccountToUserLoader(index);
-
-    assertThat(underTest.load("missing")).isNull();
-    assertThat(underTest.load("jesuis@charlie.com")).isEqualTo(user.getUuid());
-  }
-
-  @Test
-  public void warn_if_multiple_users_share_the_same_scm_account() {
-    db.users().insertUser(u -> u.setLogin("charlie").setScmAccounts(asList("charlie", "jesuis@charlie.com")));
-    db.users().insertUser(u -> u.setLogin("another.charlie").setScmAccounts(asList("charlie")));
-    userIndexer.indexAll();
-
-    UserIndex index = new UserIndex(es.client(), System2.INSTANCE);
-    ScmAccountToUserLoader underTest = new ScmAccountToUserLoader(index);
-
-    assertThat(underTest.load("charlie")).isNull();
-    assertThat(logTester.logs(LoggerLevel.WARN)).contains("Multiple users share the SCM account 'charlie': another.charlie, charlie");
-  }
-
-  @Test
-  public void load_by_multiple_scm_accounts_is_not_supported_yet() {
-    UserIndex index = new UserIndex(es.client(), System2.INSTANCE);
-    ScmAccountToUserLoader underTest = new ScmAccountToUserLoader(index);
-    try {
-      underTest.loadAll(emptyList());
-      fail();
-    } catch (UnsupportedOperationException ignored) {
-    }
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMergerTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/SiblingsIssueMergerTest.java
deleted file mode 100644 (file)
index a4f5b63..0000000
+++ /dev/null
@@ -1,207 +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.issue;
-
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import java.util.Collections;
-import java.util.Date;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.rule.RuleKey;
-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.ce.task.projectanalysis.component.SiblingComponentsWithOpenIssues;
-import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.FieldDiffs;
-import org.sonar.core.issue.tracking.SimpleTracker;
-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.issue.IssueDto;
-import org.sonar.db.issue.IssueTesting;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.db.user.UserDto;
-import org.sonar.server.project.Project;
-
-import static org.assertj.core.api.Assertions.assertThat;
-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.ce.task.projectanalysis.component.ReportComponent.builder;
-import static org.sonar.db.component.ComponentTesting.newFileDto;
-
-public class SiblingsIssueMergerTest {
-  private final IssueLifecycle issueLifecycle = mock(IssueLifecycle.class);
-  private final Branch branch = mock(Branch.class);
-
-  @Rule
-  public DbTester db = DbTester.create();
-
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule()
-    .setRoot(builder(org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT, PROJECT_REF).setKey(PROJECT_KEY).setUuid(PROJECT_UUID)
-      .addChildren(FILE_1)
-      .build());
-
-  @Rule
-  public AnalysisMetadataHolderRule metadataHolder = new AnalysisMetadataHolderRule();
-
-  private static final String PROJECT_KEY = "project";
-  private static final int PROJECT_REF = 1;
-  private static final String PROJECT_UUID = "projectUuid";
-  private static final int FILE_1_REF = 12341;
-  private static final String FILE_1_KEY = "fileKey";
-  private static final String FILE_1_UUID = "fileUuid";
-
-  private static final org.sonar.ce.task.projectanalysis.component.Component FILE_1 = builder(
-    org.sonar.ce.task.projectanalysis.component.Component.Type.FILE, FILE_1_REF)
-    .setKey(FILE_1_KEY)
-    .setUuid(FILE_1_UUID)
-    .build();
-
-  private final SimpleTracker<DefaultIssue, SiblingIssue> tracker = new SimpleTracker<>();
-  private SiblingsIssueMerger copier;
-  private ComponentDto fileOnBranch1Dto;
-  private ComponentDto fileOnBranch2Dto;
-  private ComponentDto fileOnBranch3Dto;
-  private ComponentDto projectDto;
-  private ComponentDto branch1Dto;
-  private ComponentDto branch2Dto;
-  private ComponentDto branch3Dto;
-  private RuleDto rule;
-
-  @Before
-  public void setUp() {
-    DbClient dbClient = db.getDbClient();
-    ComponentIssuesLoader componentIssuesLoader = new ComponentIssuesLoader(dbClient, null, null, new MapSettings().asConfig(), System2.INSTANCE,
-      mock(IssueChangesToDeleteRepository.class));
-    copier = new SiblingsIssueMerger(new SiblingsIssuesLoader(new SiblingComponentsWithOpenIssues(treeRootHolder, metadataHolder, dbClient), dbClient, componentIssuesLoader),
-      tracker,
-      issueLifecycle);
-    projectDto = db.components().insertPublicProject(p -> p.setKey(PROJECT_KEY).setUuid(PROJECT_UUID));
-    branch1Dto = db.components().insertProjectBranch(projectDto, b -> b.setKey("myBranch1")
-      .setBranchType(BranchType.PULL_REQUEST)
-      .setMergeBranchUuid(projectDto.uuid()));
-    branch2Dto = db.components().insertProjectBranch(projectDto, b -> b.setKey("myBranch2")
-      .setBranchType(BranchType.PULL_REQUEST)
-      .setMergeBranchUuid(projectDto.uuid()));
-    branch3Dto = db.components().insertProjectBranch(projectDto, b -> b.setKey("myBranch3")
-      .setBranchType(BranchType.PULL_REQUEST)
-      .setMergeBranchUuid(projectDto.uuid()));
-    fileOnBranch1Dto = db.components().insertComponent(newFileDto(branch1Dto).setKey(FILE_1_KEY));
-    fileOnBranch2Dto = db.components().insertComponent(newFileDto(branch2Dto).setKey(FILE_1_KEY));
-    fileOnBranch3Dto = db.components().insertComponent(newFileDto(branch3Dto).setKey(FILE_1_KEY));
-    rule = db.rules().insert();
-    when(branch.getReferenceBranchUuid()).thenReturn(projectDto.uuid());
-    metadataHolder.setBranch(branch);
-    metadataHolder.setProject(new Project(projectDto.uuid(), projectDto.getKey(), projectDto.name(), projectDto.description(), Collections.emptyList()));
-  }
-
-  @Test
-  public void do_nothing_if_no_match() {
-    DefaultIssue i = createIssue("issue1", rule.getKey(), Issue.STATUS_CONFIRMED, new Date());
-    copier.tryMerge(FILE_1, Collections.singleton(i));
-
-    verifyNoInteractions(issueLifecycle);
-  }
-
-  @Test
-  public void do_nothing_if_no_new_issue() {
-    db.issues().insert(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum"));
-    copier.tryMerge(FILE_1, Collections.emptyList());
-
-    verifyNoInteractions(issueLifecycle);
-  }
-
-  @Test
-  public void merge_confirmed_issues() {
-    db.issues().insert(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum"));
-    DefaultIssue newIssue = createIssue("issue2", rule.getKey(), Issue.STATUS_OPEN, new Date());
-
-    copier.tryMerge(FILE_1, Collections.singleton(newIssue));
-
-    ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class);
-    verify(issueLifecycle).mergeConfirmedOrResolvedFromPrOrBranch(eq(newIssue), issueToMerge.capture(), eq(BranchType.PULL_REQUEST), eq("myBranch1"));
-
-    assertThat(issueToMerge.getValue().key()).isEqualTo("issue1");
-  }
-
-  @Test
-  public void prefer_more_recently_updated_issues() {
-    Instant now = Instant.now();
-    db.issues().insert(IssueTesting.newIssue(rule, branch1Dto, fileOnBranch1Dto).setKee("issue1").setStatus(Issue.STATUS_REOPENED).setLine(1).setChecksum("checksum")
-      .setIssueUpdateDate(Date.from(now.plus(2, ChronoUnit.SECONDS))));
-    db.issues().insert(IssueTesting.newIssue(rule, branch2Dto, fileOnBranch2Dto).setKee("issue2").setStatus(Issue.STATUS_OPEN).setLine(1).setChecksum("checksum")
-      .setIssueUpdateDate(Date.from(now.plus(1, ChronoUnit.SECONDS))));
-    db.issues().insert(IssueTesting.newIssue(rule, branch3Dto, fileOnBranch3Dto).setKee("issue3").setStatus(Issue.STATUS_OPEN).setLine(1).setChecksum("checksum")
-      .setIssueUpdateDate(Date.from(now)));
-    DefaultIssue newIssue = createIssue("newIssue", rule.getKey(), Issue.STATUS_OPEN, new Date());
-
-    copier.tryMerge(FILE_1, Collections.singleton(newIssue));
-
-    ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class);
-    verify(issueLifecycle).mergeConfirmedOrResolvedFromPrOrBranch(eq(newIssue), issueToMerge.capture(), eq(BranchType.PULL_REQUEST), eq("myBranch1"));
-
-    assertThat(issueToMerge.getValue().key()).isEqualTo("issue1");
-  }
-
-  @Test
-  public void lazy_load_changes() {
-    UserDto user = db.users().insertUser();
-    IssueDto issue = db.issues()
-      .insert(IssueTesting.newIssue(rule, branch2Dto, fileOnBranch2Dto).setKee("issue").setStatus(Issue.STATUS_CONFIRMED).setLine(1).setChecksum("checksum"));
-    db.issues().insertComment(issue, user, "A comment 2");
-    db.issues().insertFieldDiffs(issue, FieldDiffs.parse("severity=BLOCKER|MINOR,assignee=foo|bar").setCreationDate(new Date()));
-    DefaultIssue newIssue = createIssue("newIssue", rule.getKey(), Issue.STATUS_OPEN, new Date());
-
-    copier.tryMerge(FILE_1, Collections.singleton(newIssue));
-
-    ArgumentCaptor<DefaultIssue> issueToMerge = ArgumentCaptor.forClass(DefaultIssue.class);
-    verify(issueLifecycle).mergeConfirmedOrResolvedFromPrOrBranch(eq(newIssue), issueToMerge.capture(), eq(BranchType.PULL_REQUEST), eq("myBranch2"));
-
-    assertThat(issueToMerge.getValue().key()).isEqualTo("issue");
-    assertThat(issueToMerge.getValue().defaultIssueComments()).isNotEmpty();
-    assertThat(issueToMerge.getValue().changes()).isNotEmpty();
-  }
-
-  private static DefaultIssue createIssue(String key, RuleKey ruleKey, String status, Date creationDate) {
-    DefaultIssue issue = new DefaultIssue();
-    issue.setKey(key);
-    issue.setRuleKey(ruleKey);
-    issue.setMessage("msg");
-    issue.setLine(1);
-    issue.setStatus(status);
-    issue.setResolution(null);
-    issue.setCreationDate(creationDate);
-    issue.setChecksum("checksum");
-    return issue;
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/SourceBranchComponentUuidsTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/SourceBranchComponentUuidsTest.java
deleted file mode 100644 (file)
index 5fa3df3..0000000
+++ /dev/null
@@ -1,121 +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.issue;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
-import org.sonar.ce.task.projectanalysis.analysis.Branch;
-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.protobuf.DbProjectBranches;
-import org.sonar.server.project.Project;
-
-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.SnapshotTesting.newAnalysis;
-
-public class SourceBranchComponentUuidsTest {
-
-  private static final String BRANCH_KEY = "branch1";
-  private static final String PR_KEY = "pr1";
-
-  @Rule
-  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
-
-  @Rule
-  public DbTester db = DbTester.create();
-
-  private SourceBranchComponentUuids underTest;
-  private final Branch branch = mock(Branch.class);
-  private ComponentDto branch1;
-  private ComponentDto branch1File;
-  private ComponentDto pr1File;
-
-  @Before
-  public void setup() {
-    underTest = new SourceBranchComponentUuids(analysisMetadataHolder, db.getDbClient());
-    Project project = mock(Project.class);
-    analysisMetadataHolder.setProject(project);
-    analysisMetadataHolder.setBranch(branch);
-
-    ComponentDto projectDto = db.components().insertPublicProject();
-    when(project.getUuid()).thenReturn(projectDto.uuid());
-    branch1 = db.components().insertProjectBranch(projectDto, b -> b.setKey(BRANCH_KEY));
-    ComponentDto pr1branch = db.components().insertProjectBranch(projectDto, b -> b.setKey(PR_KEY)
-      .setBranchType(BranchType.PULL_REQUEST)
-      .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder().setBranch(BRANCH_KEY).build())
-      .setMergeBranchUuid(projectDto.getMainBranchProjectUuid()));
-    branch1File = ComponentTesting.newFileDto(branch1, null, "file").setUuid("branch1File");
-    pr1File = ComponentTesting.newFileDto(pr1branch, null, "file").setUuid("file1");
-    db.components().insertComponents(branch1File, pr1File);
-  }
-
-  @Test
-  public void should_support_db_key_when_looking_for_source_branch_component() {
-    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
-    when(branch.getName()).thenReturn(BRANCH_KEY);
-    when(branch.getPullRequestKey()).thenReturn(PR_KEY);
-    db.components().insertSnapshot(newAnalysis(branch1));
-
-    assertThat(underTest.getSourceBranchComponentUuid(pr1File.getKey())).isEqualTo(branch1File.uuid());
-    assertThat(underTest.hasSourceBranchAnalysis()).isTrue();
-  }
-
-  @Test
-  public void should_support_key_when_looking_for_source_branch_component() {
-    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
-    when(branch.getName()).thenReturn(BRANCH_KEY);
-    when(branch.getPullRequestKey()).thenReturn(PR_KEY);
-    db.components().insertSnapshot(newAnalysis(branch1));
-
-    assertThat(underTest.getSourceBranchComponentUuid(pr1File.getKey())).isEqualTo(branch1File.uuid());
-  }
-
-  @Test
-  public void return_null_if_file_doesnt_exist() {
-    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
-    when(branch.getName()).thenReturn(BRANCH_KEY);
-    when(branch.getPullRequestKey()).thenReturn(PR_KEY);
-    db.components().insertSnapshot(newAnalysis(branch1));
-
-    assertThat(underTest.getSourceBranchComponentUuid("doesnt exist")).isNull();
-  }
-
-  @Test
-  public void skip_init_if_not_a_pull_request() {
-    when(branch.getType()).thenReturn(BranchType.BRANCH);
-    when(branch.getName()).thenReturn(BRANCH_KEY);
-
-    assertThat(underTest.getSourceBranchComponentUuid(pr1File.getKey())).isNull();
-  }
-
-  @Test
-  public void skip_init_if_no_source_branch_analysis() {
-    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
-    when(branch.getName()).thenReturn(BRANCH_KEY);
-
-    assertThat(underTest.getSourceBranchComponentUuid(pr1File.getKey())).isNull();
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TargetBranchComponentUuidsTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TargetBranchComponentUuidsTest.java
deleted file mode 100644 (file)
index f764410..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.ce.task.projectanalysis.issue;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
-import org.sonar.ce.task.projectanalysis.analysis.Branch;
-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.protobuf.DbProjectBranches;
-import org.sonar.server.project.Project;
-
-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.SnapshotTesting.newAnalysis;
-
-public class TargetBranchComponentUuidsTest {
-  private static final String BRANCH_KEY = "branch1";
-  private static final String PR_KEY = "pr1";
-
-  @Rule
-  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
-
-  @Rule
-  public DbTester db = DbTester.create();
-
-  private TargetBranchComponentUuids underTest;
-  private final Branch branch = mock(Branch.class);
-  private ComponentDto branch1;
-  private ComponentDto branch1File;
-  private ComponentDto pr1File;
-
-  @Before
-  public void setup() {
-    underTest = new TargetBranchComponentUuids(analysisMetadataHolder, db.getDbClient());
-    Project project = mock(Project.class);
-    analysisMetadataHolder.setProject(project);
-    analysisMetadataHolder.setBranch(branch);
-
-    ComponentDto projectDto = db.components().insertPublicProject();
-    when(project.getUuid()).thenReturn(projectDto.uuid());
-    branch1 = db.components().insertProjectBranch(projectDto, b -> b.setKey(BRANCH_KEY));
-    ComponentDto pr1branch = db.components().insertProjectBranch(projectDto, b -> b.setKey(PR_KEY)
-      .setBranchType(BranchType.PULL_REQUEST)
-      .setPullRequestData(DbProjectBranches.PullRequestData.newBuilder().setTarget(BRANCH_KEY).build())
-      .setMergeBranchUuid(projectDto.getMainBranchProjectUuid()));
-    branch1File = ComponentTesting.newFileDto(branch1, null, "file").setUuid("branch1File");
-    pr1File = ComponentTesting.newFileDto(pr1branch, null, "file").setUuid("file1");
-    db.components().insertComponents(branch1File, pr1File);
-  }
-
-  @Test
-  public void should_support_db_key_when_looking_for_target_branch_component() {
-    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
-    when(branch.getName()).thenReturn("prBranch");
-    when(branch.getTargetBranchName()).thenReturn(BRANCH_KEY);
-
-    when(branch.getPullRequestKey()).thenReturn(PR_KEY);
-    db.components().insertSnapshot(newAnalysis(branch1));
-
-    assertThat(underTest.getTargetBranchComponentUuid(pr1File.getKey())).isEqualTo(branch1File.uuid());
-    assertThat(underTest.hasTargetBranchAnalysis()).isTrue();
-  }
-
-  @Test
-  public void should_support_key_when_looking_for_target_branch_component() {
-    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
-    when(branch.getName()).thenReturn("prBranch");
-    when(branch.getTargetBranchName()).thenReturn(BRANCH_KEY);
-    when(branch.getPullRequestKey()).thenReturn(PR_KEY);
-    db.components().insertSnapshot(newAnalysis(branch1));
-
-    assertThat(underTest.getTargetBranchComponentUuid(pr1File.getKey())).isEqualTo(branch1File.uuid());
-  }
-
-  @Test
-  public void return_null_if_file_doesnt_exist() {
-    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
-    when(branch.getName()).thenReturn("prBranch");
-    when(branch.getTargetBranchName()).thenReturn(BRANCH_KEY);
-    when(branch.getPullRequestKey()).thenReturn(PR_KEY);
-    db.components().insertSnapshot(newAnalysis(branch1));
-
-    assertThat(underTest.getTargetBranchComponentUuid("doesnt exist")).isNull();
-  }
-
-  @Test
-  public void skip_init_if_not_a_pull_request() {
-    when(branch.getType()).thenReturn(BranchType.BRANCH);
-    when(branch.getName()).thenReturn("prBranch");
-    when(branch.getTargetBranchName()).thenReturn(BRANCH_KEY);
-
-    assertThat(underTest.getTargetBranchComponentUuid(pr1File.getKey())).isNull();
-  }
-
-  @Test
-  public void skip_init_if_no_target_branch_analysis() {
-    when(branch.getType()).thenReturn(BranchType.PULL_REQUEST);
-    when(branch.getName()).thenReturn("prBranch");
-    when(branch.getTargetBranchName()).thenReturn(BRANCH_KEY);
-
-    assertThat(underTest.getTargetBranchComponentUuid(pr1File.getKey())).isNull();
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerReferenceBranchInputFactoryTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerReferenceBranchInputFactoryTest.java
deleted file mode 100644 (file)
index c7f0489..0000000
+++ /dev/null
@@ -1,81 +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.issue;
-
-import java.util.Collections;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.component.ReferenceBranchComponentUuids;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.tracking.Input;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class TrackerReferenceBranchInputFactoryTest {
-  private final static String COMPONENT_KEY = "file1";
-  private final static String COMPONENT_UUID = "uuid1";
-
-  @Rule
-  public DbTester db = DbTester.create();
-
-  private ComponentIssuesLoader componentIssuesLoader = mock(ComponentIssuesLoader.class);
-  private ReferenceBranchComponentUuids referenceBranchComponentUuids = mock(ReferenceBranchComponentUuids.class);
-  private TrackerReferenceBranchInputFactory underTest;
-
-  @Before
-  public void setUp() {
-    underTest = new TrackerReferenceBranchInputFactory(componentIssuesLoader, referenceBranchComponentUuids, db.getDbClient());
-  }
-
-  @Test
-  public void gets_issues_and_hashes_in_matching_component() {
-    DefaultIssue issue1 = new DefaultIssue();
-    when(referenceBranchComponentUuids.getComponentUuid(COMPONENT_KEY)).thenReturn(COMPONENT_UUID);
-    when(componentIssuesLoader.loadOpenIssuesWithChanges(COMPONENT_UUID)).thenReturn(Collections.singletonList(issue1));
-    ComponentDto fileDto = ComponentTesting.newFileDto(ComponentTesting.newPublicProjectDto()).setUuid(COMPONENT_UUID);
-    db.fileSources().insertFileSource(fileDto, 3);
-
-    Component component = mock(Component.class);
-    when(component.getKey()).thenReturn(COMPONENT_KEY);
-    when(component.getType()).thenReturn(Component.Type.FILE);
-    Input<DefaultIssue> input = underTest.create(component);
-
-    assertThat(input.getIssues()).containsOnly(issue1);
-    assertThat(input.getLineHashSequence().length()).isEqualTo(3);
-  }
-
-  @Test
-  public void gets_nothing_when_there_is_no_matching_component() {
-    Component component = mock(Component.class);
-    when(component.getKey()).thenReturn(COMPONENT_KEY);
-    when(component.getType()).thenReturn(Component.Type.FILE);
-    Input<DefaultIssue> input = underTest.create(component);
-
-    assertThat(input.getIssues()).isEmpty();
-    assertThat(input.getLineHashSequence().length()).isZero();
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerSourceBranchInputFactoryTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerSourceBranchInputFactoryTest.java
deleted file mode 100644 (file)
index f711b75..0000000
+++ /dev/null
@@ -1,111 +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.issue;
-
-import java.util.Collections;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.ce.task.projectanalysis.component.Component;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.tracking.Input;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class TrackerSourceBranchInputFactoryTest {
-  private final static String COMPONENT_KEY = "file1";
-  private final static String COMPONENT_UUID = "uuid1";
-
-  @Rule
-  public DbTester db = DbTester.create();
-
-  private final ComponentIssuesLoader componentIssuesLoader = mock(ComponentIssuesLoader.class);
-  private final SourceBranchComponentUuids sourceBranchComponentUuids = mock(SourceBranchComponentUuids.class);
-  private TrackerSourceBranchInputFactory underTest;
-
-  @Before
-  public void setUp() {
-    underTest = new TrackerSourceBranchInputFactory(componentIssuesLoader, sourceBranchComponentUuids, db.getDbClient());
-  }
-
-  @Test
-  public void gets_issues_and_hashes_in_matching_component() {
-    DefaultIssue issue1 = new DefaultIssue();
-    when(sourceBranchComponentUuids.getSourceBranchComponentUuid(COMPONENT_KEY)).thenReturn(COMPONENT_UUID);
-    when(componentIssuesLoader.loadOpenIssuesWithChanges(COMPONENT_UUID)).thenReturn(Collections.singletonList(issue1));
-    ComponentDto fileDto = ComponentTesting.newFileDto(ComponentTesting.newPublicProjectDto()).setUuid(COMPONENT_UUID);
-    db.fileSources().insertFileSource(fileDto, 3);
-
-    Component component = mock(Component.class);
-    when(component.getKey()).thenReturn(COMPONENT_KEY);
-    when(component.getType()).thenReturn(Component.Type.FILE);
-    Input<DefaultIssue> input = underTest.createForSourceBranch(component);
-
-    assertThat(input.getIssues()).containsOnly(issue1);
-    assertThat(input.getLineHashSequence().length()).isEqualTo(3);
-  }
-
-  @Test
-  public void get_issues_without_line_hashes() {
-    DefaultIssue issue1 = new DefaultIssue();
-    when(sourceBranchComponentUuids.getSourceBranchComponentUuid(COMPONENT_KEY)).thenReturn(COMPONENT_UUID);
-    when(componentIssuesLoader.loadOpenIssuesWithChanges(COMPONENT_UUID)).thenReturn(Collections.singletonList(issue1));
-    ComponentDto fileDto = ComponentTesting.newFileDto(ComponentTesting.newPublicProjectDto()).setUuid(COMPONENT_UUID);
-    db.fileSources().insertFileSource(fileDto, 0);
-
-    Component component = mock(Component.class);
-    when(component.getKey()).thenReturn(COMPONENT_KEY);
-    when(component.getType()).thenReturn(Component.Type.FILE);
-    Input<DefaultIssue> input = underTest.createForSourceBranch(component);
-
-    assertThat(input.getIssues()).containsOnly(issue1);
-    assertThat(input.getLineHashSequence().length()).isZero();
-  }
-
-  @Test
-  public void gets_nothing_when_there_is_no_matching_component() {
-    Component component = mock(Component.class);
-    when(component.getKey()).thenReturn(COMPONENT_KEY);
-    when(component.getType()).thenReturn(Component.Type.FILE);
-    Input<DefaultIssue> input = underTest.createForSourceBranch(component);
-
-    assertThat(input.getIssues()).isEmpty();
-    assertThat(input.getLineHashSequence().length()).isZero();
-  }
-
-  @Test
-  public void hasSourceBranchAnalysis_returns_true_if_source_branch_of_pr_was_analysed() {
-    when(sourceBranchComponentUuids.hasSourceBranchAnalysis()).thenReturn(true);
-
-    assertThat(underTest.hasSourceBranchAnalysis()).isTrue();
-  }
-
-  @Test
-  public void hasSourceBranchAnalysis_returns_false_if_no_source_branch_analysis() {
-    when(sourceBranchComponentUuids.hasSourceBranchAnalysis()).thenReturn(false);
-
-    assertThat(underTest.hasSourceBranchAnalysis()).isFalse();
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerTargetBranchInputFactoryTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerTargetBranchInputFactoryTest.java
deleted file mode 100644 (file)
index d088cb6..0000000
+++ /dev/null
@@ -1,135 +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.issue;
-
-import java.util.Collections;
-import java.util.Optional;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.filemove.MovedFilesRepository;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.tracking.Input;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class TrackerTargetBranchInputFactoryTest {
-  private final static String COMPONENT_KEY = "file1";
-  private final static String COMPONENT_UUID = "uuid1";
-  private final static String ORIGINAL_COMPONENT_KEY = "file2";
-  private final static String ORIGINAL_COMPONENT_UUID = "uuid2";
-
-  @Rule
-  public DbTester db = DbTester.create();
-
-  private final ComponentIssuesLoader componentIssuesLoader = mock(ComponentIssuesLoader.class);
-  private final TargetBranchComponentUuids targetBranchComponentUuids = mock(TargetBranchComponentUuids.class);
-  private final MovedFilesRepository movedFilesRepository = mock(MovedFilesRepository.class);
-  private TrackerTargetBranchInputFactory underTest;
-
-  @Before
-  public void setUp() {
-    underTest = new TrackerTargetBranchInputFactory(componentIssuesLoader, targetBranchComponentUuids, db.getDbClient(), movedFilesRepository);
-  }
-
-  @Test
-  public void gets_issues_and_hashes_in_matching_component() {
-    DefaultIssue issue1 = new DefaultIssue();
-    when(targetBranchComponentUuids.getTargetBranchComponentUuid(COMPONENT_KEY)).thenReturn(COMPONENT_UUID);
-    when(componentIssuesLoader.loadOpenIssuesWithChanges(COMPONENT_UUID)).thenReturn(Collections.singletonList(issue1));
-    ComponentDto fileDto = ComponentTesting.newFileDto(ComponentTesting.newPublicProjectDto()).setUuid(COMPONENT_UUID);
-    db.fileSources().insertFileSource(fileDto, 3);
-
-    Component component = getComponent();
-    Input<DefaultIssue> input = underTest.createForTargetBranch(component);
-
-    assertThat(input.getIssues()).containsOnly(issue1);
-    assertThat(input.getLineHashSequence().length()).isEqualTo(3);
-  }
-
-  @Test
-  public void get_issues_without_line_hashes() {
-    DefaultIssue issue1 = new DefaultIssue();
-    when(targetBranchComponentUuids.getTargetBranchComponentUuid(COMPONENT_KEY)).thenReturn(COMPONENT_UUID);
-    when(componentIssuesLoader.loadOpenIssuesWithChanges(COMPONENT_UUID)).thenReturn(Collections.singletonList(issue1));
-    ComponentDto fileDto = ComponentTesting.newFileDto(ComponentTesting.newPublicProjectDto()).setUuid(COMPONENT_UUID);
-    db.fileSources().insertFileSource(fileDto, 0);
-
-    Component component = getComponent();
-    Input<DefaultIssue> input = underTest.createForTargetBranch(component);
-
-    assertThat(input.getIssues()).containsOnly(issue1);
-    assertThat(input.getLineHashSequence().length()).isZero();
-  }
-
-  @Test
-  public void gets_nothing_when_there_is_no_matching_component() {
-    Component component = getComponent();
-    Input<DefaultIssue> input = underTest.createForTargetBranch(component);
-
-    assertThat(input.getIssues()).isEmpty();
-    assertThat(input.getLineHashSequence().length()).isZero();
-  }
-
-  @Test
-  public void uses_original_component_uuid_when_component_is_moved_file() {
-    Component component = getComponent();
-    MovedFilesRepository.OriginalFile originalFile = new MovedFilesRepository.OriginalFile(ORIGINAL_COMPONENT_UUID, ORIGINAL_COMPONENT_KEY);
-    when(movedFilesRepository.getOriginalPullRequestFile(component)).thenReturn(Optional.of(originalFile));
-    when(targetBranchComponentUuids.getTargetBranchComponentUuid(ORIGINAL_COMPONENT_KEY))
-      .thenReturn(ORIGINAL_COMPONENT_UUID);
-    DefaultIssue issue1 = new DefaultIssue();
-    when(componentIssuesLoader.loadOpenIssuesWithChanges(ORIGINAL_COMPONENT_UUID)).thenReturn(Collections.singletonList(issue1));
-
-
-    Input<DefaultIssue> targetBranchIssue = underTest.createForTargetBranch(component);
-
-    verify(targetBranchComponentUuids).getTargetBranchComponentUuid(ORIGINAL_COMPONENT_KEY);
-    assertThat(targetBranchIssue.getIssues()).containsOnly(issue1);
-  }
-
-  private Component getComponent() {
-    Component component = mock(Component.class);
-    when(component.getKey()).thenReturn(COMPONENT_KEY);
-    when(component.getType()).thenReturn(Component.Type.FILE);
-    return component;
-  }
-
-  @Test
-  public void hasTargetBranchAnalysis_returns_true_if_source_branch_of_pr_was_analysed() {
-    when(targetBranchComponentUuids.hasTargetBranchAnalysis()).thenReturn(true);
-
-    assertThat(underTest.hasTargetBranchAnalysis()).isTrue();
-  }
-
-  @Test
-  public void hasTargetBranchAnalysis_returns_false_if_no_target_branch_analysis() {
-    when(targetBranchComponentUuids.hasTargetBranchAnalysis()).thenReturn(false);
-
-    assertThat(underTest.hasTargetBranchAnalysis()).isFalse();
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/measure/MeasureRepositoryImplTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/measure/MeasureRepositoryImplTest.java
deleted file mode 100644 (file)
index 608c66b..0000000
+++ /dev/null
@@ -1,408 +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.measure;
-
-import com.google.common.base.Function;
-import com.google.common.collect.ImmutableList;
-import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import javax.annotation.Nullable;
-import org.junit.Before;
-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.batch.BatchReportReader;
-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.metric.Metric;
-import org.sonar.ce.task.projectanalysis.metric.MetricImpl;
-import org.sonar.ce.task.projectanalysis.metric.MetricRepository;
-import org.sonar.ce.task.projectanalysis.metric.ReportMetricValidator;
-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.SnapshotDto;
-import org.sonar.db.measure.MeasureDto;
-import org.sonar.db.metric.MetricDto;
-import org.sonar.scanner.protocol.output.ScannerReport;
-import org.sonar.scanner.protocol.output.ScannerReport.Measure.StringValue;
-
-import static com.google.common.collect.FluentIterable.from;
-import static java.lang.String.format;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verifyNoInteractions;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-import static org.sonar.db.component.ComponentTesting.newFileDto;
-
-@RunWith(DataProviderRunner.class)
-public class MeasureRepositoryImplTest {
-
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-  @Rule
-  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
-
-  private static final String FILE_COMPONENT_KEY = "file cpt key";
-  private static final ReportComponent FILE_COMPONENT = ReportComponent.builder(Component.Type.FILE, 1).setKey(FILE_COMPONENT_KEY).build();
-  private static final ReportComponent OTHER_COMPONENT = ReportComponent.builder(Component.Type.FILE, 2).setKey("some other key").build();
-  private static final String METRIC_KEY_1 = "metric 1";
-  private static final int METRIC_ID_1 = 1;
-  private static final String METRIC_KEY_2 = "metric 2";
-  private static final int METRIC_ID_2 = 2;
-  private final Metric metric1 = mock(Metric.class);
-  private final Metric metric2 = mock(Metric.class);
-  private static final String LAST_ANALYSIS_UUID = "u123";
-  private static final String OTHER_ANALYSIS_UUID = "u369";
-  private static final Measure SOME_MEASURE = Measure.newMeasureBuilder().create("some value");
-  private static final String SOME_DATA = "some data";
-
-  private ReportMetricValidator reportMetricValidator = mock(ReportMetricValidator.class);
-
-  private DbClient dbClient = dbTester.getDbClient();
-  private MetricRepository metricRepository = mock(MetricRepository.class);
-  private MeasureRepositoryImpl underTest = new MeasureRepositoryImpl(dbClient, reportReader, metricRepository, reportMetricValidator);
-
-  private DbClient mockedDbClient = mock(DbClient.class);
-  private BatchReportReader mockBatchReportReader = mock(BatchReportReader.class);
-  private MeasureRepositoryImpl underTestWithMock = new MeasureRepositoryImpl(mockedDbClient, mockBatchReportReader, metricRepository, reportMetricValidator);
-
-  private DbSession dbSession = dbTester.getSession();
-
-  @Before
-  public void setUp() {
-    when(metric1.getKey()).thenReturn(METRIC_KEY_1);
-    when(metric1.getType()).thenReturn(Metric.MetricType.STRING);
-    when(metric2.getKey()).thenReturn(METRIC_KEY_2);
-    when(metric2.getType()).thenReturn(Metric.MetricType.STRING);
-
-    // references to metrics are consistent with DB by design
-    when(metricRepository.getByKey(METRIC_KEY_1)).thenReturn(metric1);
-    when(metricRepository.getByKey(METRIC_KEY_2)).thenReturn(metric2);
-  }
-
-  @Test
-  public void getBaseMeasure_throws_NPE_and_does_not_open_session_if_component_is_null() {
-    try {
-      underTestWithMock.getBaseMeasure(null, metric1);
-      fail("an NPE should have been raised");
-    } catch (NullPointerException e) {
-      verifyNoInteractions(mockedDbClient);
-    }
-  }
-
-  @Test
-  public void getBaseMeasure_throws_NPE_and_does_not_open_session_if_metric_is_null() {
-    try {
-      underTestWithMock.getBaseMeasure(FILE_COMPONENT, null);
-      fail("an NPE should have been raised");
-    } catch (NullPointerException e) {
-      verifyNoInteractions(mockedDbClient);
-    }
-  }
-
-  @Test
-  public void getBaseMeasure_returns_absent_if_measure_does_not_exist_in_DB() {
-    Optional<Measure> res = underTest.getBaseMeasure(FILE_COMPONENT, metric1);
-
-    assertThat(res).isNotPresent();
-  }
-
-  @Test
-  public void getBaseMeasure_returns_Measure_if_measure_of_last_snapshot_only_in_DB() {
-    ComponentDto project = dbTester.components().insertPrivateProject();
-    dbTester.components().insertComponent(newFileDto(project).setUuid(FILE_COMPONENT.getUuid()));
-    SnapshotDto lastAnalysis = dbTester.components().insertSnapshot(project, t -> t.setLast(true));
-    SnapshotDto oldAnalysis = dbTester.components().insertSnapshot(project, t -> t.setLast(false));
-    MetricDto metric1 = dbTester.measures().insertMetric(t -> t.setValueType(org.sonar.api.measures.Metric.ValueType.STRING.name()));
-    MetricDto metric2 = dbTester.measures().insertMetric(t -> t.setValueType(org.sonar.api.measures.Metric.ValueType.STRING.name()));
-    dbClient.measureDao().insert(dbSession, createMeasureDto(metric1.getUuid(), FILE_COMPONENT.getUuid(), lastAnalysis.getUuid()));
-    dbClient.measureDao().insert(dbSession, createMeasureDto(metric1.getUuid(), FILE_COMPONENT.getUuid(), oldAnalysis.getUuid()));
-    dbSession.commit();
-
-    // metric 1 is associated to snapshot with "last=true"
-    assertThat(underTest.getBaseMeasure(FILE_COMPONENT, metricOf(metric1)).get().getStringValue())
-      .isEqualTo(SOME_DATA);
-    // metric 2 is associated to snapshot with "last=false" => not retrieved
-    assertThat(underTest.getBaseMeasure(FILE_COMPONENT, metricOf(metric2))).isNotPresent();
-  }
-
-  private Metric metricOf(MetricDto metricDto) {
-    Metric res = mock(Metric.class);
-    when(res.getKey()).thenReturn(metricDto.getKey());
-    when(res.getUuid()).thenReturn(metricDto.getUuid());
-    when(res.getType()).thenReturn(Metric.MetricType.valueOf(metricDto.getValueType()));
-    return res;
-  }
-
-  @Test
-  public void add_throws_NPE_if_Component_argument_is_null() {
-    assertThatThrownBy(() -> underTest.add(null, metric1, SOME_MEASURE))
-      .isInstanceOf(NullPointerException.class);
-  }
-
-  @Test
-  public void add_throws_NPE_if_Component_metric_is_null() {
-    assertThatThrownBy(() -> underTest.add(FILE_COMPONENT, null, SOME_MEASURE))
-      .isInstanceOf(NullPointerException.class);
-  }
-
-  @Test
-  public void add_throws_NPE_if_Component_measure_is_null() {
-    assertThatThrownBy(() -> underTest.add(FILE_COMPONENT, metric1, null))
-      .isInstanceOf(NullPointerException.class);
-  }
-
-  @Test
-  public void add_throws_UOE_if_measure_already_exists() {
-    assertThatThrownBy(() -> {
-      underTest.add(FILE_COMPONENT, metric1, SOME_MEASURE);
-      underTest.add(FILE_COMPONENT, metric1, SOME_MEASURE);
-    })
-      .isInstanceOf(UnsupportedOperationException.class);
-  }
-
-  @Test
-  public void update_throws_NPE_if_Component_metric_is_null() {
-    assertThatThrownBy(() -> underTest.update(FILE_COMPONENT, null, SOME_MEASURE))
-      .isInstanceOf(NullPointerException.class);
-  }
-
-  @Test
-  public void update_throws_NPE_if_Component_measure_is_null() {
-    assertThatThrownBy(() -> underTest.update(FILE_COMPONENT, metric1, null))
-      .isInstanceOf(NullPointerException.class);
-  }
-
-  @Test
-  public void update_throws_UOE_if_measure_does_not_exists() {
-    assertThatThrownBy(() -> underTest.update(FILE_COMPONENT, metric1, SOME_MEASURE))
-      .isInstanceOf(UnsupportedOperationException.class);
-  }
-
-  private static final List<Measure> MEASURES = ImmutableList.of(
-    Measure.newMeasureBuilder().create(1),
-    Measure.newMeasureBuilder().create(1L),
-    Measure.newMeasureBuilder().create(1d, 1),
-    Measure.newMeasureBuilder().create(true),
-    Measure.newMeasureBuilder().create(false),
-    Measure.newMeasureBuilder().create("sds"),
-    Measure.newMeasureBuilder().create(Measure.Level.OK),
-    Measure.newMeasureBuilder().createNoValue());
-
-  @DataProvider
-  public static Object[][] measures() {
-    return from(MEASURES).transform(new Function<Measure, Object[]>() {
-      @Nullable
-      @Override
-      public Object[] apply(Measure input) {
-        return new Measure[] {input};
-      }
-    }).toArray(Object[].class);
-  }
-
-  @Test
-  public void add_accepts_NO_VALUE_as_measure_arg() {
-    for (Metric.MetricType metricType : Metric.MetricType.values()) {
-      underTest.add(FILE_COMPONENT, new MetricImpl("1", "key" + metricType, "name" + metricType, metricType), Measure.newMeasureBuilder().createNoValue());
-    }
-  }
-
-  @Test
-  @UseDataProvider("measures")
-  public void update_throws_IAE_if_valueType_of_Measure_is_not_the_same_as_the_Metric_valueType_unless_NO_VALUE(Measure measure) {
-    for (Metric.MetricType metricType : Metric.MetricType.values()) {
-      if (metricType.getValueType() == measure.getValueType() || measure.getValueType() == Measure.ValueType.NO_VALUE) {
-        continue;
-      }
-
-      try {
-        final MetricImpl metric = new MetricImpl("1", "key" + metricType, "name" + metricType, metricType);
-        underTest.add(FILE_COMPONENT, metric, getSomeMeasureByValueType(metricType));
-        underTest.update(FILE_COMPONENT, metric, measure);
-        fail("An IllegalArgumentException should have been raised");
-      } catch (IllegalArgumentException e) {
-        assertThat(e).hasMessage(format(
-          "Measure's ValueType (%s) is not consistent with the Metric's ValueType (%s)",
-          measure.getValueType(), metricType.getValueType()));
-      }
-    }
-  }
-
-  @Test
-  public void update_accepts_NO_VALUE_as_measure_arg() {
-    for (Metric.MetricType metricType : Metric.MetricType.values()) {
-      MetricImpl metric = new MetricImpl("1", "key" + metricType, "name" + metricType, metricType);
-      underTest.add(FILE_COMPONENT, metric, getSomeMeasureByValueType(metricType));
-      underTest.update(FILE_COMPONENT, metric, Measure.newMeasureBuilder().createNoValue());
-    }
-  }
-
-  private Measure getSomeMeasureByValueType(final Metric.MetricType metricType) {
-    return MEASURES.stream().filter(input -> input.getValueType() == metricType.getValueType()).findFirst().get();
-  }
-
-  @Test
-  public void update_supports_updating_to_the_same_value() {
-    underTest.add(FILE_COMPONENT, metric1, SOME_MEASURE);
-    underTest.update(FILE_COMPONENT, metric1, SOME_MEASURE);
-  }
-
-  @Test
-  public void update_updates_the_stored_value() {
-    Measure newMeasure = Measure.updatedMeasureBuilder(SOME_MEASURE).create();
-
-    underTest.add(FILE_COMPONENT, metric1, SOME_MEASURE);
-    underTest.update(FILE_COMPONENT, metric1, newMeasure);
-
-    assertThat(underTest.getRawMeasure(FILE_COMPONENT, metric1)).containsSame(newMeasure);
-  }
-
-  @Test
-  public void getRawMeasure_throws_NPE_without_reading_batch_report_if_component_arg_is_null() {
-    try {
-      underTestWithMock.getRawMeasure(null, metric1);
-      fail("an NPE should have been raised");
-    } catch (NullPointerException e) {
-      verifyNoMoreInteractions(mockBatchReportReader);
-    }
-  }
-
-  @Test
-  public void getRawMeasure_throws_NPE_without_reading_batch_report_if_metric_arg_is_null() {
-    try {
-      underTestWithMock.getRawMeasure(FILE_COMPONENT, null);
-      fail("an NPE should have been raised");
-    } catch (NullPointerException e) {
-      verifyNoMoreInteractions(mockBatchReportReader);
-    }
-  }
-
-  @Test
-  public void getRawMeasure_returns_measure_added_through_add_method() {
-    underTest.add(FILE_COMPONENT, metric1, SOME_MEASURE);
-
-    Optional<Measure> res = underTest.getRawMeasure(FILE_COMPONENT, metric1);
-
-    assertThat(res)
-      .isPresent()
-      .containsSame(SOME_MEASURE);
-
-    // make sure we really match on the specified component and metric
-    assertThat(underTest.getRawMeasure(OTHER_COMPONENT, metric1)).isNotPresent();
-    assertThat(underTest.getRawMeasure(FILE_COMPONENT, metric2)).isNotPresent();
-  }
-
-  @Test
-  public void getRawMeasure_returns_measure_from_batch_if_not_added_through_add_method() {
-    String value = "trololo";
-
-    when(reportMetricValidator.validate(METRIC_KEY_1)).thenReturn(true);
-
-    reportReader.putMeasures(FILE_COMPONENT.getReportAttributes().getRef(), ImmutableList.of(
-      ScannerReport.Measure.newBuilder().setMetricKey(METRIC_KEY_1).setStringValue(StringValue.newBuilder().setValue(value)).build()));
-
-    Optional<Measure> res = underTest.getRawMeasure(FILE_COMPONENT, metric1);
-
-    assertThat(res).isPresent();
-    assertThat(res.get().getStringValue()).isEqualTo(value);
-
-    // make sure we really match on the specified component and metric
-    assertThat(underTest.getRawMeasure(FILE_COMPONENT, metric2)).isNotPresent();
-    assertThat(underTest.getRawMeasure(OTHER_COMPONENT, metric1)).isNotPresent();
-  }
-
-  @Test
-  public void getRawMeasure_returns_only_validate_measure_from_batch_if_not_added_through_add_method() {
-    when(reportMetricValidator.validate(METRIC_KEY_1)).thenReturn(true);
-    when(reportMetricValidator.validate(METRIC_KEY_2)).thenReturn(false);
-
-    reportReader.putMeasures(FILE_COMPONENT.getReportAttributes().getRef(), ImmutableList.of(
-      ScannerReport.Measure.newBuilder().setMetricKey(METRIC_KEY_1).setStringValue(StringValue.newBuilder().setValue("value1")).build(),
-      ScannerReport.Measure.newBuilder().setMetricKey(METRIC_KEY_2).setStringValue(StringValue.newBuilder().setValue("value2")).build()));
-
-    assertThat(underTest.getRawMeasure(FILE_COMPONENT, metric1)).isPresent();
-    assertThat(underTest.getRawMeasure(FILE_COMPONENT, metric2)).isNotPresent();
-  }
-
-  @Test
-  public void getRawMeasure_retrieves_added_measure_over_batch_measure() {
-    when(reportMetricValidator.validate(METRIC_KEY_1)).thenReturn(true);
-    reportReader.putMeasures(FILE_COMPONENT.getReportAttributes().getRef(), ImmutableList.of(
-      ScannerReport.Measure.newBuilder().setMetricKey(METRIC_KEY_1).setStringValue(StringValue.newBuilder().setValue("some value")).build()));
-
-    Measure addedMeasure = SOME_MEASURE;
-    underTest.add(FILE_COMPONENT, metric1, addedMeasure);
-
-    Optional<Measure> res = underTest.getRawMeasure(FILE_COMPONENT, metric1);
-
-    assertThat(res)
-      .isPresent()
-      .containsSame(addedMeasure);
-  }
-
-  @Test
-  public void getRawMeasure_retrieves_measure_from_batch_and_caches_it_locally_so_that_it_can_be_updated() {
-    when(reportMetricValidator.validate(METRIC_KEY_1)).thenReturn(true);
-    reportReader.putMeasures(FILE_COMPONENT.getReportAttributes().getRef(), ImmutableList.of(
-      ScannerReport.Measure.newBuilder().setMetricKey(METRIC_KEY_1).setStringValue(StringValue.newBuilder().setValue("some value")).build()));
-
-    Optional<Measure> measure = underTest.getRawMeasure(FILE_COMPONENT, metric1);
-
-    underTest.update(FILE_COMPONENT, metric1, Measure.updatedMeasureBuilder(measure.get()).create());
-  }
-
-  @Test
-  public void getRawMeasures_returns_added_measures_over_batch_measures() {
-    when(reportMetricValidator.validate(METRIC_KEY_1)).thenReturn(true);
-    when(reportMetricValidator.validate(METRIC_KEY_2)).thenReturn(true);
-    ScannerReport.Measure batchMeasure1 = ScannerReport.Measure.newBuilder().setMetricKey(METRIC_KEY_1).setStringValue(StringValue.newBuilder().setValue("some value")).build();
-    ScannerReport.Measure batchMeasure2 = ScannerReport.Measure.newBuilder().setMetricKey(METRIC_KEY_2).setStringValue(StringValue.newBuilder().setValue("some value")).build();
-    reportReader.putMeasures(FILE_COMPONENT.getReportAttributes().getRef(), ImmutableList.of(batchMeasure1, batchMeasure2));
-
-    Measure addedMeasure = SOME_MEASURE;
-    underTest.add(FILE_COMPONENT, metric1, addedMeasure);
-
-    Map<String, Measure> rawMeasures = underTest.getRawMeasures(FILE_COMPONENT);
-
-    assertThat(rawMeasures.keySet()).hasSize(2);
-    assertThat(rawMeasures).containsEntry(METRIC_KEY_1, addedMeasure);
-    assertThat(rawMeasures.get(METRIC_KEY_2)).extracting(Measure::getStringValue).isEqualTo("some value");
-  }
-
-  private static MeasureDto createMeasureDto(String metricUuid, String componentUuid, String analysisUuid) {
-    return new MeasureDto()
-      .setComponentUuid(componentUuid)
-      .setAnalysisUuid(analysisUuid)
-      .setData(SOME_DATA)
-      .setMetricUuid(metricUuid);
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/period/NewCodeReferenceBranchComponentUuidsTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/period/NewCodeReferenceBranchComponentUuidsTest.java
deleted file mode 100644 (file)
index 555fd07..0000000
+++ /dev/null
@@ -1,116 +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.period;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
-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.newcodeperiod.NewCodePeriodType;
-import org.sonar.server.project.Project;
-
-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.db.component.SnapshotTesting.newAnalysis;
-
-public class NewCodeReferenceBranchComponentUuidsTest {
-  @Rule
-  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
-
-  @Rule
-  public PeriodHolderRule periodHolder = new PeriodHolderRule();
-  @Rule
-  public DbTester db = DbTester.create();
-
-  private NewCodeReferenceBranchComponentUuids underTest = new NewCodeReferenceBranchComponentUuids(analysisMetadataHolder, periodHolder, db.getDbClient());
-
-  private ComponentDto branch1;
-  private ComponentDto branch1File;
-  private ComponentDto pr1File;
-  private ComponentDto pr2File;
-  private Project project = mock(Project.class);
-  private ComponentDto pr1;
-  private ComponentDto pr2;
-  private ComponentDto branch2;
-  private ComponentDto branch2File;
-
-  @Before
-  public void setUp() {
-    analysisMetadataHolder.setProject(project);
-
-    ComponentDto projectDto = db.components().insertPublicProject();
-    when(project.getUuid()).thenReturn(projectDto.uuid());
-    branch1 = db.components().insertProjectBranch(projectDto, b -> b.setKey("branch1"));
-    branch2 = db.components().insertProjectBranch(projectDto, b -> b.setKey("branch2"));
-    pr1 = db.components().insertProjectBranch(projectDto, b -> b.setKey("pr1").setBranchType(BranchType.PULL_REQUEST).setMergeBranchUuid(branch1.uuid()));
-    pr2 = db.components().insertProjectBranch(projectDto, b -> b.setKey("pr2").setBranchType(BranchType.PULL_REQUEST).setMergeBranchUuid(branch1.uuid()));
-    branch1File = ComponentTesting.newFileDto(branch1, null, "file").setUuid("branch1File");
-    branch2File = ComponentTesting.newFileDto(branch2, null, "file").setUuid("branch2File");
-    pr1File = ComponentTesting.newFileDto(pr1, null, "file").setUuid("file1");
-    pr2File = ComponentTesting.newFileDto(pr2, null, "file").setUuid("file2");
-    db.components().insertComponents(branch1File, pr1File, pr2File, branch2File);
-  }
-
-  @Test
-  public void should_support_db_key_when_looking_for_reference_component() {
-    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "branch1", null));
-    db.components().insertSnapshot(newAnalysis(branch1));
-    assertThat(underTest.getComponentUuid(pr1File.getKey())).isEqualTo(branch1File.uuid());
-  }
-
-  @Test
-  public void should_support_key_when_looking_for_reference_component() {
-    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "branch1", null));
-    db.components().insertSnapshot(newAnalysis(branch1));
-    assertThat(underTest.getComponentUuid(pr1File.getKey())).isEqualTo(branch1File.uuid());
-  }
-
-  @Test
-  public void return_null_if_file_doesnt_exist() {
-    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "branch1", null));
-    db.components().insertSnapshot(newAnalysis(branch1));
-    assertThat(underTest.getComponentUuid("doesnt exist")).isNull();
-  }
-
-  @Test
-  public void skip_init_if_no_reference_branch_analysis() {
-    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "branch1", null));
-    assertThat(underTest.getComponentUuid(pr1File.getKey())).isNull();
-  }
-
-  @Test
-  public void skip_init_if_branch_not_found() {
-    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "unknown", null));
-    assertThat(underTest.getComponentUuid(pr1File.getKey())).isNull();
-  }
-
-  @Test
-  public void throw_ise_if_mode_is_not_reference_branch() {
-    periodHolder.setPeriod(new Period(NewCodePeriodType.NUMBER_OF_DAYS.name(), "10", 1000L));
-    assertThatThrownBy(() -> underTest.getComponentUuid(pr1File.getKey()))
-      .isInstanceOf(IllegalStateException.class);
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/source/DbLineHashVersionTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/source/DbLineHashVersionTest.java
deleted file mode 100644 (file)
index 1df18ee..0000000
+++ /dev/null
@@ -1,120 +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.source;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
-import org.sonar.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.component.ReferenceBranchComponentUuids;
-import org.sonar.ce.task.projectanalysis.component.ReportComponent;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.source.LineHashVersion;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class DbLineHashVersionTest {
-  @Rule
-  public DbTester db = DbTester.create();
-
-  private AnalysisMetadataHolder analysisMetadataHolder = mock(AnalysisMetadataHolder.class);
-  private ReferenceBranchComponentUuids referenceBranchComponentUuids = mock(ReferenceBranchComponentUuids.class);
-  private DbLineHashVersion underTest = new DbLineHashVersion(db.getDbClient(), analysisMetadataHolder, referenceBranchComponentUuids);
-
-  @Test
-  public void hasLineHashWithSignificantCode_should_return_true() {
-    ComponentDto project = db.components().insertPublicProject();
-    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
-
-    db.fileSources().insertFileSource(file, dto -> dto.setLineHashesVersion(LineHashVersion.WITH_SIGNIFICANT_CODE.getDbValue()));
-    Component component = ReportComponent.builder(Component.Type.FILE, 1).setKey("key").setUuid(file.uuid()).build();
-    assertThat(underTest.hasLineHashesWithSignificantCode(component)).isTrue();
-  }
-
-  @Test
-  public void hasLineHashWithSignificantCode_should_return_false_if_file_is_not_found() {
-    Component component = ReportComponent.builder(Component.Type.FILE, 1).setKey("key").setUuid("123").build();
-    assertThat(underTest.hasLineHashesWithSignificantCode(component)).isFalse();
-  }
-
-  @Test
-  public void hasLineHashWithSignificantCode_should_return_false_if_pr_reference_doesnt_have_file() {
-    when(analysisMetadataHolder.isPullRequest()).thenReturn(true);
-    Component component = ReportComponent.builder(Component.Type.FILE, 1).setKey("key").setUuid("123").build();
-    assertThat(underTest.hasLineHashesWithSignificantCode(component)).isFalse();
-
-    verify(analysisMetadataHolder).isPullRequest();
-    verify(referenceBranchComponentUuids).getComponentUuid(component.getKey());
-  }
-
-  @Test
-  public void hasLineHashWithSignificantCode_should_return_false_if_pr_reference_has_file_but_it_is_not_in_db() {
-    ComponentDto project = db.components().insertPublicProject();
-    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
-
-    when(analysisMetadataHolder.isPullRequest()).thenReturn(true);
-    when(referenceBranchComponentUuids.getComponentUuid("key")).thenReturn(file.uuid());
-
-    Component component = ReportComponent.builder(Component.Type.FILE, 1).setKey("key").setUuid("123").build();
-    assertThat(underTest.hasLineHashesWithSignificantCode(component)).isFalse();
-
-    verify(analysisMetadataHolder).isPullRequest();
-    verify(referenceBranchComponentUuids).getComponentUuid(component.getKey());
-  }
-
-  @Test
-  public void hasLineHashWithSignificantCode_should_return_true_if_pr_reference_has_file_and_it_is_in_db() {
-    ComponentDto project = db.components().insertPublicProject();
-    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
-    db.fileSources().insertFileSource(file, dto -> dto.setLineHashesVersion(LineHashVersion.WITH_SIGNIFICANT_CODE.getDbValue()));
-
-    when(analysisMetadataHolder.isPullRequest()).thenReturn(true);
-    when(referenceBranchComponentUuids.getComponentUuid("key")).thenReturn(file.uuid());
-
-    Component component = ReportComponent.builder(Component.Type.FILE, 1).setKey("key").setUuid("123").build();
-    assertThat(underTest.hasLineHashesWithSignificantCode(component)).isTrue();
-
-    verify(analysisMetadataHolder).isPullRequest();
-    verify(referenceBranchComponentUuids).getComponentUuid(component.getKey());
-  }
-
-  @Test
-  public void should_cache_line_hash_version_from_db() {
-    ComponentDto project = db.components().insertPublicProject();
-    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
-
-    db.fileSources().insertFileSource(file, dto -> dto.setLineHashesVersion(LineHashVersion.WITH_SIGNIFICANT_CODE.getDbValue()));
-    Component component = ReportComponent.builder(Component.Type.FILE, 1).setKey("key").setUuid(file.uuid()).build();
-    assertThat(underTest.hasLineHashesWithSignificantCode(component)).isTrue();
-
-    assertThat(db.countRowsOfTable("file_sources")).isOne();
-    db.executeUpdateSql("delete from file_sources");
-    db.commit();
-    assertThat(db.countRowsOfTable("file_sources")).isZero();
-
-    // still true because it uses cache
-    assertThat(underTest.hasLineHashesWithSignificantCode(component)).isTrue();
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/source/PersistFileSourcesStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/source/PersistFileSourcesStepTest.java
deleted file mode 100644 (file)
index 2b0dae4..0000000
+++ /dev/null
@@ -1,482 +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.source;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-import java.util.function.Consumer;
-import org.apache.commons.codec.digest.DigestUtils;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.component.FileAttributes;
-import org.sonar.ce.task.projectanalysis.component.PreviousSourceHashRepository;
-import org.sonar.ce.task.projectanalysis.component.ReportComponent;
-import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
-import org.sonar.ce.task.projectanalysis.scm.Changeset;
-import org.sonar.ce.task.projectanalysis.step.BaseStepTest;
-import org.sonar.ce.task.step.ComputationStep;
-import org.sonar.ce.task.step.TestComputationStepContext;
-import org.sonar.core.util.SequenceUuidFactory;
-import org.sonar.core.util.Uuids;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.protobuf.DbFileSources;
-import org.sonar.db.source.FileHashesDto;
-import org.sonar.db.source.FileSourceDto;
-import org.sonar.db.source.LineHashVersion;
-
-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 PersistFileSourcesStepTest extends BaseStepTest {
-
-  private static final int FILE1_REF = 3;
-  private static final String PROJECT_UUID = "PROJECT";
-  private static final String PROJECT_KEY = "PROJECT_KEY";
-  private static final String FILE1_UUID = "FILE1";
-  private static final long NOW = 123456789L;
-  private static final long PAST = 15000L;
-
-  private final System2 system2 = mock(System2.class);
-
-  @Rule
-  public DbTester dbTester = DbTester.create(system2);
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-
-  private final SourceLinesHashRepository sourceLinesHashRepository = mock(SourceLinesHashRepository.class);
-  private final SourceLinesHashRepositoryImpl.LineHashesComputer lineHashesComputer = mock(SourceLinesHashRepositoryImpl.LineHashesComputer.class);
-  private final FileSourceDataComputer fileSourceDataComputer = mock(FileSourceDataComputer.class);
-  private final FileSourceDataWarnings fileSourceDataWarnings = mock(FileSourceDataWarnings.class);
-  private final PreviousSourceHashRepository previousSourceHashRepository = mock(PreviousSourceHashRepository.class);
-
-  private final DbClient dbClient = dbTester.getDbClient();
-  private final DbSession session = dbTester.getSession();
-
-  private PersistFileSourcesStep underTest;
-
-  @Before
-  public void setup() {
-    when(system2.now()).thenReturn(NOW);
-    when(sourceLinesHashRepository.getLineHashesComputerToPersist(any(Component.class))).thenReturn(lineHashesComputer);
-    underTest = new PersistFileSourcesStep(dbClient, system2, treeRootHolder, sourceLinesHashRepository, fileSourceDataComputer, fileSourceDataWarnings,
-      new SequenceUuidFactory(), previousSourceHashRepository);
-    initBasicReport(1);
-  }
-
-  @Override
-  protected ComputationStep step() {
-    return underTest;
-  }
-
-  @Test
-  public void persist_sources() {
-    List<String> lineHashes = Arrays.asList("137f72c3708c6bd0de00a0e5a69c699b", "e6251bcf1a7dc3ba5e7933e325bbe605");
-    String sourceHash = "ee5a58024a155466b43bc559d953e018";
-    DbFileSources.Data fileSourceData = DbFileSources.Data.newBuilder()
-      .addAllLines(Arrays.asList(
-        DbFileSources.Line.newBuilder().setSource("line1").setLine(1).build(),
-        DbFileSources.Line.newBuilder().setSource("line2").setLine(2).build()))
-      .build();
-    when(fileSourceDataComputer.compute(fileComponent().build(), fileSourceDataWarnings))
-      .thenReturn(new FileSourceDataComputer.Data(fileSourceData, lineHashes, sourceHash, null));
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbTester.countRowsOfTable("file_sources")).isOne();
-    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
-    assertThat(fileSourceDto.getProjectUuid()).isEqualTo(PROJECT_UUID);
-    assertThat(fileSourceDto.getFileUuid()).isEqualTo(FILE1_UUID);
-    assertThat(fileSourceDto.getBinaryData()).isNotEmpty();
-    assertThat(fileSourceDto.getDataHash()).isNotEmpty();
-    assertThat(fileSourceDto.getLineHashesVersion()).isEqualTo(LineHashVersion.WITHOUT_SIGNIFICANT_CODE.getDbValue());
-    assertThat(fileSourceDto.getLineHashes()).isNotEmpty();
-    assertThat(fileSourceDto.getCreatedAt()).isEqualTo(NOW);
-    assertThat(fileSourceDto.getUpdatedAt()).isEqualTo(NOW);
-
-    DbFileSources.Data data = fileSourceDto.getSourceData();
-    assertThat(data.getLinesCount()).isEqualTo(2);
-    assertThat(data.getLines(0).getLine()).isOne();
-    assertThat(data.getLines(0).getSource()).isEqualTo("line1");
-    assertThat(data.getLines(1).getLine()).isEqualTo(2);
-    assertThat(data.getLines(1).getSource()).isEqualTo("line2");
-    verify(fileSourceDataWarnings).commitWarnings();
-  }
-
-  @Test
-  public void persist_source_hashes() {
-    List<String> lineHashes = Arrays.asList("137f72c3708c6bd0de00a0e5a69c699b", "e6251bcf1a7dc3ba5e7933e325bbe605");
-    String sourceHash = "ee5a58024a155466b43bc559d953e018";
-    setComputedData(DbFileSources.Data.newBuilder().build(), lineHashes, sourceHash, null);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbTester.countRowsOfTable("file_sources")).isOne();
-    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
-    assertThat(fileSourceDto.getLineHashes()).containsExactly("137f72c3708c6bd0de00a0e5a69c699b", "e6251bcf1a7dc3ba5e7933e325bbe605");
-    assertThat(fileSourceDto.getSrcHash()).isEqualTo("ee5a58024a155466b43bc559d953e018");
-    verify(fileSourceDataWarnings).commitWarnings();
-  }
-
-  @Test
-  public void persist_coverage() {
-    DbFileSources.Data dbData = DbFileSources.Data.newBuilder().addLines(
-      DbFileSources.Line.newBuilder()
-        .setConditions(10)
-        .setCoveredConditions(2)
-        .setLineHits(1)
-        .setLine(1)
-        .build())
-      .build();
-    setComputedData(dbData);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbTester.countRowsOfTable("file_sources")).isOne();
-    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
-    assertThat(fileSourceDto.getSourceData()).isEqualTo(dbData);
-    verify(fileSourceDataWarnings).commitWarnings();
-  }
-
-  private ReportComponent.Builder fileComponent() {
-    return ReportComponent.builder(Component.Type.FILE, FILE1_REF).setUuid(FILE1_UUID).setKey("PROJECT_KEY" + ":src/Foo.java");
-  }
-
-  @Test
-  public void persist_scm() {
-    DbFileSources.Data dbData = DbFileSources.Data.newBuilder().addLines(
-      DbFileSources.Line.newBuilder()
-        .setScmAuthor("john")
-        .setScmDate(123456789L)
-        .setScmRevision("rev-1")
-        .build())
-      .build();
-    setComputedData(dbData);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbTester.countRowsOfTable("file_sources")).isOne();
-    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
-    assertThat(fileSourceDto.getSourceData()).isEqualTo(dbData);
-    assertThat(fileSourceDto.getRevision()).isNull();
-    verify(fileSourceDataWarnings).commitWarnings();
-  }
-
-  @Test
-  public void persist_scm_some_lines() {
-    DbFileSources.Data dbData = DbFileSources.Data.newBuilder().addAllLines(Arrays.asList(
-      DbFileSources.Line.newBuilder()
-        .setScmAuthor("john")
-        .setScmDate(123456789L)
-        .setScmRevision("rev-1")
-        .build(),
-      DbFileSources.Line.newBuilder()
-        .setScmDate(223456789L)
-        .build(),
-      DbFileSources.Line.newBuilder()
-        .build()))
-      .build();
-    setComputedData(dbData);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbTester.countRowsOfTable("file_sources")).isOne();
-    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
-
-    DbFileSources.Data data = fileSourceDto.getSourceData();
-
-    assertThat(data.getLinesList()).hasSize(3);
-
-    assertThat(data.getLines(0).getScmAuthor()).isEqualTo("john");
-    assertThat(data.getLines(0).getScmDate()).isEqualTo(123456789L);
-    assertThat(data.getLines(0).getScmRevision()).isEqualTo("rev-1");
-
-    assertThat(data.getLines(1).getScmAuthor()).isEmpty();
-    assertThat(data.getLines(1).getScmDate()).isEqualTo(223456789L);
-    assertThat(data.getLines(1).getScmRevision()).isEmpty();
-
-    assertThat(data.getLines(2).getScmAuthor()).isEmpty();
-    assertThat(data.getLines(2).getScmDate()).isZero();
-    assertThat(data.getLines(2).getScmRevision()).isEmpty();
-    verify(fileSourceDataWarnings).commitWarnings();
-  }
-
-  @Test
-  public void persist_highlighting() {
-    DbFileSources.Data dbData = DbFileSources.Data.newBuilder().addLines(
-      DbFileSources.Line.newBuilder()
-        .setHighlighting("2,4,a")
-        .build())
-      .build();
-    setComputedData(dbData);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbTester.countRowsOfTable("file_sources")).isOne();
-    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
-    DbFileSources.Data data = fileSourceDto.getSourceData();
-    assertThat(data).isEqualTo(dbData);
-    assertThat(data.getLinesList()).hasSize(1);
-    assertThat(data.getLines(0).getHighlighting()).isEqualTo("2,4,a");
-    verify(fileSourceDataWarnings).commitWarnings();
-  }
-
-  @Test
-  public void persist_symbols() {
-    DbFileSources.Data dbData = DbFileSources.Data.newBuilder().addAllLines(Arrays.asList(
-      DbFileSources.Line.newBuilder()
-        .setSymbols("2,4,1")
-        .build(),
-      DbFileSources.Line.newBuilder().build(),
-      DbFileSources.Line.newBuilder()
-        .setSymbols("1,3,1")
-        .build()))
-      .build();
-    setComputedData(dbData);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbTester.countRowsOfTable("file_sources")).isOne();
-    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
-    assertThat(fileSourceDto.getSourceData()).isEqualTo(dbData);
-    verify(fileSourceDataWarnings).commitWarnings();
-  }
-
-  @Test
-  public void persist_duplication() {
-    DbFileSources.Data dbData = DbFileSources.Data.newBuilder().addLines(
-      DbFileSources.Line.newBuilder()
-        .addDuplication(2)
-        .build())
-      .build();
-    setComputedData(dbData);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbTester.countRowsOfTable("file_sources")).isOne();
-    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
-    assertThat(fileSourceDto.getSourceData()).isEqualTo(dbData);
-    verify(fileSourceDataWarnings).commitWarnings();
-  }
-
-  @Test
-  public void save_revision() {
-    Changeset latest = Changeset.newChangesetBuilder().setDate(0L).setRevision("rev-1").build();
-    setComputedData(DbFileSources.Data.newBuilder().build(), Collections.singletonList("lineHashes"), "srcHash", latest);
-
-    underTest.execute(new TestComputationStepContext());
-
-    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
-    assertThat(fileSourceDto.getRevision()).isEqualTo("rev-1");
-    verify(fileSourceDataWarnings).commitWarnings();
-  }
-
-  @Test
-  public void not_save_revision() {
-    setComputedData(DbFileSources.Data.newBuilder().build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
-    assertThat(fileSourceDto.getRevision()).isNull();
-    verify(fileSourceDataWarnings).commitWarnings();
-  }
-
-  @Test
-  public void not_update_sources_when_nothing_has_changed() {
-    setPastAnalysisHashes();
-    dbClient.fileSourceDao().insert(dbTester.getSession(), createDto());
-    dbTester.getSession().commit();
-
-    Changeset changeset = Changeset.newChangesetBuilder().setDate(1L).setRevision("rev-1").build();
-    setComputedData(DbFileSources.Data.newBuilder().build(), Collections.singletonList("lineHash"), "sourceHash", changeset);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbTester.countRowsOfTable("file_sources")).isOne();
-    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
-    assertThat(fileSourceDto.getSrcHash()).isEqualTo("sourceHash");
-    assertThat(fileSourceDto.getLineHashes()).isEqualTo(Collections.singletonList("lineHash"));
-    assertThat(fileSourceDto.getCreatedAt()).isEqualTo(PAST);
-    assertThat(fileSourceDto.getUpdatedAt()).isEqualTo(PAST);
-    verify(fileSourceDataWarnings).commitWarnings();
-  }
-
-  @Test
-  public void update_sources_when_source_updated() {
-    // Existing sources
-    long past = 150000L;
-    FileSourceDto dbFileSources = new FileSourceDto()
-      .setUuid(Uuids.createFast())
-      .setProjectUuid(PROJECT_UUID)
-      .setFileUuid(FILE1_UUID)
-      .setSrcHash("5b4bd9815cdb17b8ceae19eb1810c34c")
-      .setLineHashes(Collections.singletonList("6438c669e0d0de98e6929c2cc0fac474"))
-      .setDataHash("6cad150e3d065976c230cddc5a09efaa")
-      .setSourceData(DbFileSources.Data.newBuilder()
-        .addLines(DbFileSources.Line.newBuilder()
-          .setLine(1)
-          .setSource("old line")
-          .build())
-        .build())
-      .setCreatedAt(past)
-      .setUpdatedAt(past)
-      .setRevision("rev-0");
-    dbClient.fileSourceDao().insert(dbTester.getSession(), dbFileSources);
-    dbTester.getSession().commit();
-    setPastAnalysisHashes(dbFileSources);
-
-    DbFileSources.Data newSourceData = DbFileSources.Data.newBuilder()
-      .addLines(DbFileSources.Line.newBuilder()
-        .setLine(1)
-        .setSource("old line")
-        .setScmDate(123456789L)
-        .setScmRevision("rev-1")
-        .setScmAuthor("john")
-        .build())
-      .build();
-
-    Changeset changeset = Changeset.newChangesetBuilder().setDate(1L).setRevision("rev-1").build();
-    setComputedData(newSourceData, Collections.singletonList("6438c669e0d0de98e6929c2cc0fac474"), "5b4bd9815cdb17b8ceae19eb1810c34c", changeset);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbTester.countRowsOfTable("file_sources")).isOne();
-    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
-    assertThat(fileSourceDto.getCreatedAt()).isEqualTo(past);
-    assertThat(fileSourceDto.getUpdatedAt()).isEqualTo(NOW);
-    assertThat(fileSourceDto.getRevision()).isEqualTo("rev-1");
-    verify(fileSourceDataWarnings).commitWarnings();
-  }
-
-  @Test
-  public void update_sources_when_src_hash_is_missing() {
-    FileSourceDto dbFileSources = createDto(dto -> dto.setSrcHash(null));
-    dbClient.fileSourceDao().insert(dbTester.getSession(), dbFileSources);
-    dbTester.getSession().commit();
-    setPastAnalysisHashes(dbFileSources);
-
-    DbFileSources.Data sourceData = DbFileSources.Data.newBuilder().build();
-    setComputedData(sourceData, Collections.singletonList("lineHash"), "newSourceHash", null);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbTester.countRowsOfTable("file_sources")).isOne();
-    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
-    assertThat(fileSourceDto.getCreatedAt()).isEqualTo(PAST);
-    assertThat(fileSourceDto.getUpdatedAt()).isEqualTo(NOW);
-    assertThat(fileSourceDto.getSrcHash()).isEqualTo("newSourceHash");
-    verify(fileSourceDataWarnings).commitWarnings();
-  }
-
-  @Test
-  public void update_sources_when_revision_is_missing() {
-    DbFileSources.Data sourceData = DbFileSources.Data.newBuilder()
-      .addLines(DbFileSources.Line.newBuilder()
-        .setLine(1)
-        .setSource("line")
-        .build())
-      .build();
-
-    FileSourceDto dbFileSources = createDto(dto -> dto.setRevision(null));
-    dbClient.fileSourceDao().insert(dbTester.getSession(), dbFileSources);
-    dbTester.getSession().commit();
-    setPastAnalysisHashes(dbFileSources);
-
-    Changeset changeset = Changeset.newChangesetBuilder().setDate(1L).setRevision("revision").build();
-    setComputedData(sourceData, Collections.singletonList("137f72c3708c6bd0de00a0e5a69c699b"), "29f25900140c94db38035128cb6de6a2", changeset);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbTester.countRowsOfTable("file_sources")).isOne();
-    FileSourceDto fileSourceDto = dbClient.fileSourceDao().selectByFileUuid(session, FILE1_UUID);
-    assertThat(fileSourceDto.getCreatedAt()).isEqualTo(PAST);
-    assertThat(fileSourceDto.getUpdatedAt()).isEqualTo(NOW);
-    assertThat(fileSourceDto.getRevision()).isEqualTo("revision");
-    verify(fileSourceDataWarnings).commitWarnings();
-  }
-
-  private FileSourceDto createDto() {
-    return createDto(dto -> {
-    });
-  }
-
-  private FileSourceDto createDto(Consumer<FileSourceDto> modifier) {
-    DbFileSources.Data sourceData = DbFileSources.Data.newBuilder().build();
-    byte[] data = FileSourceDto.encodeSourceData(sourceData);
-    String dataHash = DigestUtils.md5Hex(data);
-
-    FileSourceDto dto = new FileSourceDto()
-      .setUuid(Uuids.createFast())
-      .setProjectUuid(PROJECT_UUID)
-      .setFileUuid(FILE1_UUID)
-      .setSrcHash("sourceHash")
-      .setLineHashes(Collections.singletonList("lineHash"))
-      .setDataHash(dataHash)
-      .setRevision("rev-1")
-      .setSourceData(sourceData)
-      .setCreatedAt(PAST)
-      .setUpdatedAt(PAST);
-
-    modifier.accept(dto);
-    return dto;
-  }
-
-  private void setPastAnalysisHashes() {
-    DbFileSources.Data sourceData = DbFileSources.Data.newBuilder().build();
-    byte[] data = FileSourceDto.encodeSourceData(sourceData);
-    String dataHash = DigestUtils.md5Hex(data);
-    FileHashesDto fileHashesDto = new FileHashesDto()
-      .setSrcHash("sourceHash")
-      .setDataHash(dataHash)
-      .setRevision("rev-1");
-    setPastAnalysisHashes(fileHashesDto);
-  }
-
-  private void setPastAnalysisHashes(FileHashesDto fileHashesDto) {
-    when(previousSourceHashRepository.getDbFile(any(Component.class))).thenReturn(Optional.of(fileHashesDto));
-  }
-
-  private void setComputedData(DbFileSources.Data data, List<String> lineHashes, String sourceHash, Changeset latestChangeWithRevision) {
-    FileSourceDataComputer.Data computedData = new FileSourceDataComputer.Data(data, lineHashes, sourceHash, latestChangeWithRevision);
-    when(fileSourceDataComputer.compute(fileComponent().build(), fileSourceDataWarnings)).thenReturn(computedData);
-  }
-
-  private void setComputedData(DbFileSources.Data data) {
-    FileSourceDataComputer.Data computedData = new FileSourceDataComputer.Data(data, Collections.emptyList(), "", null);
-    when(fileSourceDataComputer.compute(fileComponent().build(), fileSourceDataWarnings)).thenReturn(computedData);
-  }
-
-  private void initBasicReport(int numberOfLines) {
-    ReportComponent root = ReportComponent.builder(Component.Type.PROJECT, 1).setUuid(PROJECT_UUID).setKey(PROJECT_KEY).addChildren(
-      fileComponent().setFileAttributes(new FileAttributes(false, null, numberOfLines)).build())
-      .build();
-    treeRootHolder.setRoots(root, root);
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/CleanIssueChangesStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/CleanIssueChangesStepTest.java
deleted file mode 100644 (file)
index d950499..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.ce.task.projectanalysis.step;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.ce.task.projectanalysis.issue.IssueChangesToDeleteRepository;
-import org.sonar.ce.task.step.TestComputationStepContext;
-import org.sonar.db.DbTester;
-import org.sonar.db.issue.IssueChangeDto;
-import org.sonar.db.issue.IssueDto;
-
-import static java.util.Collections.singleton;
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class CleanIssueChangesStepTest {
-  @Rule
-  public DbTester db = DbTester.create();
-  private final IssueChangesToDeleteRepository repository = new IssueChangesToDeleteRepository();
-  private final CleanIssueChangesStep cleanIssueChangesStep = new CleanIssueChangesStep(repository, db.getDbClient());
-  private final TestComputationStepContext context = new TestComputationStepContext();
-
-  @Test
-  public void steps_deletes_all_changes_in_repository() {
-    IssueDto issue1 = db.issues().insert();
-    IssueChangeDto change1 = db.issues().insertChange(issue1);
-    IssueChangeDto change2 = db.issues().insertChange(issue1);
-
-    repository.add(change1.getUuid());
-
-    cleanIssueChangesStep.execute(context);
-    assertThat(db.getDbClient().issueChangeDao().selectByIssueKeys(db.getSession(), singleton(issue1.getKey())))
-      .extracting(IssueChangeDto::getUuid)
-      .containsOnly(change2.getUuid());
-  }
-
-  @Test
-  public void steps_does_nothing_if_no_uuid() {
-    IssueDto issue1 = db.issues().insert();
-    IssueChangeDto change1 = db.issues().insertChange(issue1);
-    IssueChangeDto change2 = db.issues().insertChange(issue1);
-
-    cleanIssueChangesStep.execute(context);
-
-    assertThat(db.getDbClient().issueChangeDao().selectByIssueKeys(db.getSession(), singleton(issue1.getKey())))
-      .extracting(IssueChangeDto::getUuid)
-      .containsOnly(change1.getUuid(), change2.getUuid());
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/EnableAnalysisStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/EnableAnalysisStepTest.java
deleted file mode 100644 (file)
index 82bd32e..0000000
+++ /dev/null
@@ -1,99 +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.Optional;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.projectanalysis.analysis.MutableAnalysisMetadataHolderRule;
-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.step.TestComputationStepContext;
-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 static org.assertj.core.api.Assertions.assertThat;
-
-public class EnableAnalysisStepTest {
-
-  private static final ReportComponent REPORT_PROJECT = ReportComponent.builder(Component.Type.PROJECT, 1).build();
-  private static final String PREVIOUS_ANALYSIS_UUID = "ANALYSIS_1";
-  private static final String CURRENT_ANALYSIS_UUID = "ANALYSIS_2";
-
-  @Rule
-  public DbTester db = DbTester.create(System2.INSTANCE);
-
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-
-  @Rule
-  public MutableAnalysisMetadataHolderRule analysisMetadataHolder = new MutableAnalysisMetadataHolderRule();
-
-  private EnableAnalysisStep underTest = new EnableAnalysisStep(db.getDbClient(), treeRootHolder, analysisMetadataHolder);
-
-  @Test
-  public void switch_islast_flag_and_mark_analysis_as_processed() {
-    ComponentDto project = ComponentTesting.newPrivateProjectDto(REPORT_PROJECT.getUuid());
-    db.components().insertComponent(project);
-    insertAnalysis(project, PREVIOUS_ANALYSIS_UUID, SnapshotDto.STATUS_PROCESSED, true);
-    insertAnalysis(project, CURRENT_ANALYSIS_UUID, SnapshotDto.STATUS_UNPROCESSED, false);
-    db.commit();
-    treeRootHolder.setRoot(REPORT_PROJECT);
-    analysisMetadataHolder.setUuid(CURRENT_ANALYSIS_UUID);
-
-    underTest.execute(new TestComputationStepContext());
-
-    verifyAnalysis(PREVIOUS_ANALYSIS_UUID, SnapshotDto.STATUS_PROCESSED, false);
-    verifyAnalysis(CURRENT_ANALYSIS_UUID, SnapshotDto.STATUS_PROCESSED, true);
-  }
-
-  @Test
-  public void set_islast_flag_and_mark_as_processed_if_no_previous_analysis() {
-    ComponentDto project = ComponentTesting.newPrivateProjectDto(REPORT_PROJECT.getUuid());
-    db.components().insertComponent(project);
-    insertAnalysis(project, CURRENT_ANALYSIS_UUID, SnapshotDto.STATUS_UNPROCESSED, false);
-    db.commit();
-    treeRootHolder.setRoot(REPORT_PROJECT);
-    analysisMetadataHolder.setUuid(CURRENT_ANALYSIS_UUID);
-
-    underTest.execute(new TestComputationStepContext());
-
-    verifyAnalysis(CURRENT_ANALYSIS_UUID, SnapshotDto.STATUS_PROCESSED, true);
-  }
-
-  private void verifyAnalysis(String uuid, String expectedStatus, boolean expectedLastFlag) {
-    Optional<SnapshotDto> analysis = db.getDbClient().snapshotDao().selectByUuid(db.getSession(), uuid);
-    assertThat(analysis.get().getStatus()).isEqualTo(expectedStatus);
-    assertThat(analysis.get().getLast()).isEqualTo(expectedLastFlag);
-  }
-
-  private void insertAnalysis(ComponentDto project, String uuid, String status, boolean isLastFlag) {
-    SnapshotDto snapshot = SnapshotTesting.newAnalysis(project)
-      .setLast(isLastFlag)
-      .setStatus(status)
-      .setUuid(uuid);
-    db.getDbClient().snapshotDao().insert(db.getSession(), snapshot);
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ExtractReportStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ExtractReportStepTest.java
deleted file mode 100644 (file)
index faa5afa..0000000
+++ /dev/null
@@ -1,119 +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.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import org.apache.commons.io.FileUtils;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.impl.utils.JUnitTempFolder;
-import org.sonar.api.utils.MessageException;
-import org.sonar.api.utils.System2;
-import org.sonar.api.utils.ZipUtils;
-import org.sonar.api.utils.log.LogTester;
-import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.ce.task.CeTask;
-import org.sonar.ce.task.projectanalysis.batch.BatchReportDirectoryHolderImpl;
-import org.sonar.ce.task.projectanalysis.batch.MutableBatchReportDirectoryHolder;
-import org.sonar.ce.task.step.TestComputationStepContext;
-import org.sonar.db.DbTester;
-import org.sonar.db.ce.CeTaskTypes;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-
-public class ExtractReportStepTest {
-
-  private static final String TASK_UUID = "1";
-
-  @Rule
-  public JUnitTempFolder tempFolder = new JUnitTempFolder();
-
-  @Rule
-  public LogTester logTester = new LogTester();
-
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private MutableBatchReportDirectoryHolder reportDirectoryHolder = new BatchReportDirectoryHolderImpl();
-  private CeTask ceTask = new CeTask.Builder()
-    .setType(CeTaskTypes.REPORT)
-    .setUuid(TASK_UUID)
-    .build();
-
-  private ExtractReportStep underTest = new ExtractReportStep(dbTester.getDbClient(), ceTask, tempFolder, reportDirectoryHolder);
-
-  @Test
-  public void fail_if_report_zip_does_not_exist() {
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(MessageException.class)
-      .hasMessage("Analysis report 1 is missing in database");
-  }
-
-  @Test
-  public void unzip_report() throws Exception {
-    logTester.setLevel(LoggerLevel.DEBUG);
-    File reportFile = generateReport();
-    try (InputStream input = FileUtils.openInputStream(reportFile)) {
-      dbTester.getDbClient().ceTaskInputDao().insert(dbTester.getSession(), TASK_UUID, input);
-    }
-    dbTester.getSession().commit();
-    dbTester.getSession().close();
-
-    underTest.execute(new TestComputationStepContext());
-
-    // directory contains the uncompressed report (which contains only metadata.pb in this test)
-    File unzippedDir = reportDirectoryHolder.getDirectory();
-    assertThat(unzippedDir).isDirectory().exists();
-    assertThat(unzippedDir.listFiles()).hasSize(1);
-    assertThat(new File(unzippedDir, "metadata.pb")).hasContent("{metadata}");
-
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).anyMatch(log -> log.matches("Analysis report is \\d+ bytes uncompressed"));
-  }
-
-  @Test
-  public void unzip_report_should_fail_if_unzip_size_exceed_threshold() throws Exception {
-    logTester.setLevel(LoggerLevel.DEBUG);
-    URL zipBombFile = getClass().getResource("/org/sonar/ce/task/projectanalysis/step/ExtractReportStepTest/zip-bomb.zip");
-    try (InputStream input = zipBombFile.openStream()) {
-      dbTester.getDbClient().ceTaskInputDao().insert(dbTester.getSession(), TASK_UUID, input);
-    }
-    dbTester.getSession().commit();
-    dbTester.getSession().close();
-
-    TestComputationStepContext testComputationStepContext = new TestComputationStepContext();
-    assertThatThrownBy(() -> underTest.execute(testComputationStepContext))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Decompression failed because unzipped size reached threshold: 4000000000 bytes");
-  }
-
-  private File generateReport() throws IOException {
-    File zipDir = tempFolder.newDir();
-    File metadataFile = new File(zipDir, "metadata.pb");
-    FileUtils.write(metadataFile, "{metadata}");
-    File zip = tempFolder.newFile();
-    ZipUtils.zipDir(zipDir, zip);
-    return zip;
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/IndexAnalysisStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/IndexAnalysisStepTest.java
deleted file mode 100644 (file)
index 24dfe9f..0000000
+++ /dev/null
@@ -1,124 +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.Set;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
-import org.sonar.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.component.FileStatuses;
-import org.sonar.ce.task.projectanalysis.component.ReportComponent;
-import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
-import org.sonar.ce.task.projectanalysis.component.ViewsComponent;
-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.component.BranchDao;
-import org.sonar.server.es.ProjectIndexer;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT;
-import static org.sonar.ce.task.projectanalysis.component.Component.Type.VIEW;
-
-public class IndexAnalysisStepTest extends BaseStepTest {
-
-  private static final String PROJECT_KEY = "PROJECT_KEY";
-  private static final String PROJECT_UUID = "PROJECT_UUID";
-
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-  @Rule
-  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
-
-  private final DbClient dbClient = mock(DbClient.class);
-
-  private final FileStatuses fileStatuses = mock(FileStatuses.class);
-
-  private final ProjectIndexer projectIndexer = mock(ProjectIndexer.class);
-
-  private final DbSession dbSession = mock(DbSession.class);
-
-  private final BranchDao branchDao = mock(BranchDao.class);
-
-  private final IndexAnalysisStep underTest = new IndexAnalysisStep(treeRootHolder, fileStatuses, dbClient, projectIndexer);
-
-  private TestComputationStepContext testComputationStepContext;
-
-  @Before
-  public void init() {
-    testComputationStepContext = new TestComputationStepContext();
-
-    when(dbClient.openSession(false)).thenReturn(dbSession);
-    when(dbClient.branchDao()).thenReturn(branchDao);
-  }
-
-  @Test
-  public void call_indexByProjectUuid_of_indexer_for_project() {
-    Component project = ReportComponent.builder(PROJECT, 1).setUuid(PROJECT_UUID).setKey(PROJECT_KEY).build();
-    treeRootHolder.setRoot(project);
-
-    underTest.execute(testComputationStepContext);
-
-    verify(projectIndexer).indexOnAnalysis(PROJECT_UUID, Set.of());
-  }
-
-  @Test
-  public void call_indexByProjectUuid_of_indexer_for_view() {
-    Component view = ViewsComponent.builder(VIEW, PROJECT_KEY).setUuid(PROJECT_UUID).build();
-    treeRootHolder.setRoot(view);
-
-    underTest.execute(testComputationStepContext);
-
-    verify(projectIndexer).indexOnAnalysis(PROJECT_UUID, Set.of());
-  }
-
-  @Test
-  public void execute_whenMarkAsUnchangedFlagActivated_shouldCallIndexOnAnalysisWithChangedComponents() {
-    Component project = ReportComponent.builder(PROJECT, 1).setUuid(PROJECT_UUID).setKey(PROJECT_KEY).build();
-    treeRootHolder.setRoot(project);
-    Set<String> anyUuids = Set.of("any-uuid");
-    when(fileStatuses.getFileUuidsMarkedAsUnchanged()).thenReturn(anyUuids);
-
-    underTest.execute(testComputationStepContext);
-
-    verify(projectIndexer).indexOnAnalysis(PROJECT_UUID, anyUuids);
-  }
-
-  @Test
-  public void execute_whenBranchIsNeedIssueSync_shouldReindexEverything() {
-    Component project = ReportComponent.builder(PROJECT, 1).setUuid(PROJECT_UUID).setKey(PROJECT_KEY).build();
-    treeRootHolder.setRoot(project);
-    when(branchDao.isBranchNeedIssueSync(dbSession, PROJECT_UUID)).thenReturn(true);
-
-    underTest.execute(testComputationStepContext);
-
-    verify(projectIndexer).indexOnAnalysis(PROJECT_UUID);
-  }
-
-  @Override
-  protected ComputationStep step() {
-    return underTest;
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadFileHashesAndStatusStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadFileHashesAndStatusStepTest.java
deleted file mode 100644 (file)
index 6a9d30a..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.ce.task.projectanalysis.step;
-
-import java.util.ArrayList;
-import java.util.List;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
-import org.sonar.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.component.FileStatusesImpl;
-import org.sonar.ce.task.projectanalysis.component.PreviousSourceHashRepositoryImpl;
-import org.sonar.ce.task.projectanalysis.component.ReportComponent;
-import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
-import org.sonar.ce.task.step.TestComputationStepContext;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.source.FileHashesDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class LoadFileHashesAndStatusStepTest {
-  @Rule
-  public DbTester db = DbTester.create();
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-  public PreviousSourceHashRepositoryImpl previousFileHashesRepository = new PreviousSourceHashRepositoryImpl();
-  public FileStatusesImpl fileStatuses = mock(FileStatusesImpl.class);
-  public AnalysisMetadataHolder analysisMetadataHolder = mock(AnalysisMetadataHolder.class);
-
-  private final LoadFileHashesAndStatusStep underTest = new LoadFileHashesAndStatusStep(db.getDbClient(), previousFileHashesRepository, fileStatuses,
-    db.getDbClient().fileSourceDao(), treeRootHolder);
-
-  @Before
-  public void before() {
-    when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false);
-  }
-
-  @Test
-  public void initialized_file_statuses() {
-    Component project = ReportComponent.builder(Component.Type.PROJECT, 1, "project").build();
-    treeRootHolder.setRoot(project);
-    underTest.execute(new TestComputationStepContext());
-    verify(fileStatuses).initialize();
-  }
-
-  @Test
-  public void loads_file_hashes_for_project_branch() {
-    ComponentDto project1 = db.components().insertPublicProject();
-    ComponentDto project2 = db.components().insertPublicProject();
-
-    ComponentDto dbFile1 = db.components().insertComponent(ComponentTesting.newFileDto(project1));
-    ComponentDto dbFile2 = db.components().insertComponent(ComponentTesting.newFileDto(project1));
-
-    insertFileSources(dbFile1, dbFile2);
-
-    Component reportFile1 = ReportComponent.builder(Component.Type.FILE, 2, dbFile1.getKey()).setUuid(dbFile1.uuid()).build();
-    Component reportFile2 = ReportComponent.builder(Component.Type.FILE, 3, dbFile2.getKey()).setUuid(dbFile2.uuid()).build();
-    Component reportFile3 = ReportComponent.builder(Component.Type.FILE, 4, dbFile2.getKey()).build();
-
-    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1, project1.getKey()).setUuid(project1.uuid())
-      .addChildren(reportFile1, reportFile2, reportFile3).build());
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(previousFileHashesRepository.getMap()).hasSize(2);
-    assertThat(previousFileHashesRepository.getDbFile(reportFile1).get())
-      .extracting(FileHashesDto::getSrcHash, FileHashesDto::getRevision, FileHashesDto::getDataHash)
-      .containsOnly("srcHash" + dbFile1.getKey(), "revision" + dbFile1.getKey(), "dataHash" + dbFile1.getKey());
-    assertThat(previousFileHashesRepository.getDbFile(reportFile2).get())
-      .extracting(FileHashesDto::getSrcHash, FileHashesDto::getRevision, FileHashesDto::getDataHash)
-      .containsOnly("srcHash" + dbFile2.getKey(), "revision" + dbFile2.getKey(), "dataHash" + dbFile2.getKey());
-    assertThat(previousFileHashesRepository.getDbFile(reportFile3)).isEmpty();
-  }
-
-  @Test
-  public void loads_high_number_of_files() {
-    ComponentDto project1 = db.components().insertPublicProject();
-    List<Component> files = new ArrayList<>(2000);
-
-    for (int i = 0; i < 2000; i++) {
-      ComponentDto dbFile = db.components().insertComponent(ComponentTesting.newFileDto(project1));
-      insertFileSources(dbFile);
-      files.add(ReportComponent.builder(Component.Type.FILE, 2, dbFile.getKey()).setUuid(dbFile.uuid()).build());
-    }
-
-    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1, project1.getKey()).setUuid(project1.uuid())
-      .addChildren(files.toArray(Component[]::new)).build());
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(previousFileHashesRepository.getMap()).hasSize(2000);
-    for (Component file : files) {
-      assertThat(previousFileHashesRepository.getDbFile(file)).isPresent();
-    }
-  }
-
-  private void insertFileSources(ComponentDto... files) {
-    for (ComponentDto file : files) {
-      db.fileSources().insertFileSource(file, f -> f
-        .setSrcHash("srcHash" + file.getKey())
-        .setRevision("revision" + file.getKey())
-        .setDataHash("dataHash" + file.getKey()));
-    }
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStepTest.java
deleted file mode 100644 (file)
index 9864d24..0000000
+++ /dev/null
@@ -1,561 +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.time.ZoneId;
-import java.time.ZonedDateTime;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Optional;
-import java.util.Random;
-import java.util.stream.Stream;
-import javax.annotation.Nullable;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.sonar.api.utils.MessageException;
-import org.sonar.api.utils.System2;
-import org.sonar.api.utils.log.LogAndArguments;
-import org.sonar.api.utils.log.LogTester;
-import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.ce.task.log.CeTaskMessages;
-import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
-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.period.NewCodePeriodResolver;
-import org.sonar.ce.task.projectanalysis.period.Period;
-import org.sonar.ce.task.projectanalysis.period.PeriodHolderImpl;
-import org.sonar.ce.task.step.ComputationStep;
-import org.sonar.ce.task.step.TestComputationStepContext;
-import org.sonar.core.util.SequenceUuidFactory;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.SnapshotDto;
-import org.sonar.db.event.EventTesting;
-import org.sonar.db.newcodeperiod.NewCodePeriodDao;
-import org.sonar.db.newcodeperiod.NewCodePeriodType;
-import org.sonar.server.project.Project;
-
-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.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.verifyNoInteractions;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-import static org.sonar.db.component.SnapshotDto.STATUS_PROCESSED;
-import static org.sonar.db.component.SnapshotDto.STATUS_UNPROCESSED;
-import static org.sonar.db.event.EventDto.CATEGORY_VERSION;
-import static org.sonar.db.event.EventTesting.newEvent;
-
-@RunWith(DataProviderRunner.class)
-public class LoadPeriodsStepTest extends BaseStepTest {
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-  @Rule
-  public LogTester logTester = new LogTester();
-
-  private final AnalysisMetadataHolder analysisMetadataHolder = mock(AnalysisMetadataHolder.class);
-  private final PeriodHolderImpl periodsHolder = new PeriodHolderImpl();
-  private final System2 system2Mock = mock(System2.class);
-  private final NewCodePeriodDao dao = new NewCodePeriodDao(system2Mock, new SequenceUuidFactory());
-  private final NewCodePeriodResolver newCodePeriodResolver = new NewCodePeriodResolver(dbTester.getDbClient(), analysisMetadataHolder);
-  private final ZonedDateTime analysisDate = ZonedDateTime.of(2019, 3, 20, 5, 30, 40, 0, ZoneId.systemDefault());
-  private final CeTaskMessages ceTaskMessages = mock(CeTaskMessages.class);
-  private final LoadPeriodsStep underTest = new LoadPeriodsStep(analysisMetadataHolder, dao, treeRootHolder, periodsHolder, dbTester.getDbClient(), newCodePeriodResolver,
-    ceTaskMessages, system2Mock);
-
-  private ComponentDto project;
-
-  @Override
-  protected ComputationStep step() {
-    return underTest;
-  }
-
-  @Before
-  public void setUp() {
-    project = dbTester.components().insertPublicProject();
-
-    when(analysisMetadataHolder.isBranch()).thenReturn(true);
-    when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(false);
-    when(analysisMetadataHolder.getAnalysisDate()).thenReturn(analysisDate.toInstant().toEpochMilli());
-  }
-
-  @Test
-  public void no_period_on_first_analysis() {
-    setupRoot(project);
-
-    when(analysisMetadataHolder.isFirstAnalysis()).thenReturn(true);
-    underTest.execute(new TestComputationStepContext());
-
-    verify(analysisMetadataHolder).isFirstAnalysis();
-    verify(analysisMetadataHolder).isBranch();
-    verify(analysisMetadataHolder).getProject();
-    verify(analysisMetadataHolder).getNewCodeReferenceBranch();
-    assertThat(periodsHolder.hasPeriod()).isFalse();
-    verifyNoMoreInteractions(analysisMetadataHolder);
-  }
-
-  @Test
-  public void no_period_date_if_not_branch() {
-    when(analysisMetadataHolder.isBranch()).thenReturn(false);
-    underTest.execute(new TestComputationStepContext());
-
-    verify(analysisMetadataHolder).isBranch();
-    assertThat(periodsHolder.hasPeriod()).isFalse();
-    verifyNoMoreInteractions(analysisMetadataHolder);
-  }
-
-  @Test
-  public void load_default_if_nothing_defined() {
-    setupRoot(project);
-
-    SnapshotDto analysis = dbTester.components().insertSnapshot(project,
-      snapshot -> snapshot.setCreatedAt(milisSinceEpoch(2019, 3, 15, 0)));
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertPeriod(NewCodePeriodType.PREVIOUS_VERSION, null, analysis.getCreatedAt());
-    verifyDebugLogs("Resolving first analysis as new code period as there is only one existing version");
-  }
-
-  @Test
-  public void load_number_of_days_global() {
-    setGlobalPeriod(NewCodePeriodType.NUMBER_OF_DAYS, "10");
-
-    testNumberOfDays(project);
-  }
-
-  @Test
-  public void load_number_of_days_on_project() {
-    setGlobalPeriod(NewCodePeriodType.PREVIOUS_VERSION, null);
-    setProjectPeriod(project.uuid(), NewCodePeriodType.NUMBER_OF_DAYS, "10");
-
-    testNumberOfDays(project);
-  }
-
-  @Test
-  public void load_number_of_days_on_branch() {
-    ComponentDto branch = dbTester.components().insertProjectBranch(project);
-
-    setGlobalPeriod(NewCodePeriodType.PREVIOUS_VERSION, null);
-    setProjectPeriod(project.uuid(), NewCodePeriodType.PREVIOUS_VERSION, null);
-    setBranchPeriod(project.uuid(), branch.uuid(), NewCodePeriodType.NUMBER_OF_DAYS, "10");
-
-    testNumberOfDays(branch);
-    verifyNoInteractions(ceTaskMessages);
-  }
-
-  @Test
-  public void load_reference_branch() {
-    ComponentDto branch = dbTester.components().insertProjectBranch(project);
-    setupRoot(branch);
-
-    setProjectPeriod(project.uuid(), NewCodePeriodType.REFERENCE_BRANCH, "master");
-
-    underTest.execute(new TestComputationStepContext());
-    assertPeriod(NewCodePeriodType.REFERENCE_BRANCH, "master", null);
-    verifyNoInteractions(ceTaskMessages);
-  }
-
-  @Test
-  public void scanner_overrides_branch_new_code_reference_branch() {
-    ComponentDto branch = dbTester.components().insertProjectBranch(project);
-    setupRoot(branch);
-
-    setBranchPeriod(project.uuid(), branch.uuid(), NewCodePeriodType.REFERENCE_BRANCH, "master");
-
-    String newCodeReferenceBranch = "newCodeReferenceBranch";
-    when(analysisMetadataHolder.getNewCodeReferenceBranch()).thenReturn(Optional.of(newCodeReferenceBranch));
-
-    underTest.execute(new TestComputationStepContext());
-    assertPeriod(NewCodePeriodType.REFERENCE_BRANCH, newCodeReferenceBranch, null);
-    verify(ceTaskMessages).add(any(CeTaskMessages.Message.class));
-  }
-
-  @Test
-  public void scanner_defines_new_code_reference_branch() {
-    ComponentDto branch = dbTester.components().insertProjectBranch(project);
-    setupRoot(branch);
-
-    String newCodeReferenceBranch = "newCodeReferenceBranch";
-    when(analysisMetadataHolder.getNewCodeReferenceBranch()).thenReturn(Optional.of(newCodeReferenceBranch));
-
-    underTest.execute(new TestComputationStepContext());
-    assertPeriod(NewCodePeriodType.REFERENCE_BRANCH, newCodeReferenceBranch, null);
-    verifyNoInteractions(ceTaskMessages);
-  }
-
-  @Test
-  public void scanner_overrides_project_new_code_reference_branch() {
-    ComponentDto branch = dbTester.components().insertProjectBranch(project);
-    setupRoot(branch);
-
-    setProjectPeriod(project.uuid(), NewCodePeriodType.REFERENCE_BRANCH, "master");
-
-    String newCodeReferenceBranch = "newCodeReferenceBranch";
-    when(analysisMetadataHolder.getNewCodeReferenceBranch()).thenReturn(Optional.of(newCodeReferenceBranch));
-
-    underTest.execute(new TestComputationStepContext());
-    assertPeriod(NewCodePeriodType.REFERENCE_BRANCH, newCodeReferenceBranch, null);
-    verifyNoInteractions(ceTaskMessages);
-  }
-
-  @Test
-  public void load_reference_branch_without_fork_date_in_report() {
-    ComponentDto branch = dbTester.components().insertProjectBranch(project);
-    setupRoot(branch);
-
-    setProjectPeriod(project.uuid(), NewCodePeriodType.REFERENCE_BRANCH, "master");
-
-    underTest.execute(new TestComputationStepContext());
-    assertPeriod(NewCodePeriodType.REFERENCE_BRANCH, "master", null);
-    verifyNoInteractions(ceTaskMessages);
-  }
-
-  private void testNumberOfDays(ComponentDto projectOrBranch) {
-    setupRoot(projectOrBranch);
-
-    SnapshotDto analysis = dbTester.components().insertSnapshot(projectOrBranch,
-      snapshot -> snapshot.setCreatedAt(milisSinceEpoch(2019, 3, 15, 0)));
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertPeriod(NewCodePeriodType.NUMBER_OF_DAYS, "10", analysis.getCreatedAt());
-    verifyDebugLogs("Resolving new code period by 10 days: 2019-03-10");
-  }
-
-  @Test
-  public void load_specific_analysis() {
-    ComponentDto branch = dbTester.components().insertProjectBranch(project);
-    SnapshotDto selectedAnalysis = dbTester.components().insertSnapshot(branch);
-    SnapshotDto aVersionAnalysis = dbTester.components().insertSnapshot(branch, snapshot -> snapshot.setCreatedAt(milisSinceEpoch(2019, 3, 12, 0)).setLast(false));
-    dbTester.events().insertEvent(EventTesting.newEvent(aVersionAnalysis).setName("a_version").setCategory(CATEGORY_VERSION));
-    dbTester.components().insertSnapshot(branch, snapshot -> snapshot.setCreatedAt(milisSinceEpoch(2019, 3, 15, 0)).setLast(true));
-
-    setBranchPeriod(project.uuid(), branch.uuid(), NewCodePeriodType.SPECIFIC_ANALYSIS, selectedAnalysis.getUuid());
-    setupRoot(branch);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertPeriod(NewCodePeriodType.SPECIFIC_ANALYSIS, selectedAnalysis.getUuid(), selectedAnalysis.getCreatedAt());
-    verifyDebugLogs("Resolving new code period with a specific analysis");
-    verifyNoInteractions(ceTaskMessages);
-  }
-
-  @Test
-  public void throw_ISE_if_no_analysis_found_for_number_of_days() {
-    setProjectPeriod(project.uuid(), NewCodePeriodType.NUMBER_OF_DAYS, "10");
-
-    setupRoot(project);
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessageContaining("Attempting to resolve period while no analysis exist");
-  }
-
-  @Test
-  public void throw_ISE_if_no_analysis_found_with_default() {
-    setupRoot(project);
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessageContaining("Attempting to resolve period while no analysis exist");
-  }
-
-  @Test
-  public void ignore_unprocessed_snapshots() {
-    SnapshotDto analysis1 = dbTester.components()
-      .insertSnapshot(project, snapshot -> snapshot.setStatus(STATUS_UNPROCESSED).setCreatedAt(milisSinceEpoch(2019, 3, 12, 0)).setLast(false));
-    SnapshotDto analysis2 = dbTester.components().insertSnapshot(project,
-      snapshot -> snapshot.setStatus(STATUS_PROCESSED).setProjectVersion("not provided").setCreatedAt(milisSinceEpoch(2019, 3, 15, 0)).setLast(false));
-    dbTester.events().insertEvent(newEvent(analysis1).setName("not provided").setCategory(CATEGORY_VERSION).setDate(analysis1.getCreatedAt()));
-    dbTester.events().insertEvent(newEvent(analysis2).setName("not provided").setCategory(CATEGORY_VERSION).setDate(analysis2.getCreatedAt()));
-    setupRoot(project);
-    setProjectPeriod(project.uuid(), NewCodePeriodType.NUMBER_OF_DAYS, "10");
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertPeriod(NewCodePeriodType.NUMBER_OF_DAYS, "10", analysis2.getCreatedAt());
-    verifyDebugLogs("Resolving new code period by 10 days: 2019-03-10");
-  }
-
-  @Test
-  public void throw_ISE_when_specific_analysis_is_set_but_does_not_exist_in_DB() {
-    ComponentDto project = dbTester.components().insertPublicProject();
-    setProjectPeriod(project.uuid(), NewCodePeriodType.SPECIFIC_ANALYSIS, "nonexistent");
-    setupRoot(project);
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Analysis 'nonexistent' of project '" + project.uuid() + "' defined as the baseline does not exist");
-  }
-
-  @Test
-  public void throw_ISE_when_specific_analysis_is_set_but_does_not_belong_to_current_project() {
-    ComponentDto otherProject = dbTester.components().insertPublicProject();
-    SnapshotDto otherProjectAnalysis = dbTester.components().insertSnapshot(otherProject);
-    setBranchPeriod(project.uuid(), project.uuid(), NewCodePeriodType.SPECIFIC_ANALYSIS, otherProjectAnalysis.getUuid());
-    setupRoot(project);
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Analysis '" + otherProjectAnalysis.getUuid() + "' of project '" + project.uuid() + "' defined as the baseline does not exist");
-  }
-
-  @Test
-  public void throw_ISE_when_specific_analysis_is_set_but_does_not_belong_to_current_branch() {
-    ComponentDto otherBranch = dbTester.components().insertProjectBranch(project);
-    SnapshotDto otherBranchAnalysis = dbTester.components().insertSnapshot(otherBranch);
-    setBranchPeriod(project.uuid(), project.uuid(), NewCodePeriodType.SPECIFIC_ANALYSIS, otherBranchAnalysis.getUuid());
-    setupRoot(project);
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Analysis '" + otherBranchAnalysis.getUuid() + "' of project '" + project.uuid() + "' defined as the baseline does not exist");
-  }
-
-  @Test
-  public void load_previous_version() {
-    SnapshotDto analysis1 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226379600000L).setProjectVersion("0.9").setLast(false)); // 2008-11-11
-    SnapshotDto analysis2 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226494680000L).setProjectVersion("1.0").setLast(false)); // 2008-11-12
-    SnapshotDto analysis3 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227157200000L).setProjectVersion("1.1").setLast(false)); // 2008-11-20
-    SnapshotDto analysis4 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227358680000L).setProjectVersion("1.1").setLast(false)); // 2008-11-22
-    SnapshotDto analysis5 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227934800000L).setProjectVersion("1.1").setLast(true)); // 2008-11-29
-    dbTester.events().insertEvent(newEvent(analysis1).setName("0.9").setCategory(CATEGORY_VERSION).setDate(analysis1.getCreatedAt()));
-    dbTester.events().insertEvent(newEvent(analysis2).setName("1.0").setCategory(CATEGORY_VERSION).setDate(analysis2.getCreatedAt()));
-    dbTester.events().insertEvent(newEvent(analysis5).setName("1.1").setCategory(CATEGORY_VERSION).setDate(analysis5.getCreatedAt()));
-    setupRoot(project, "1.1");
-    setProjectPeriod(project.uuid(), NewCodePeriodType.PREVIOUS_VERSION, null);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertPeriod(NewCodePeriodType.PREVIOUS_VERSION, "1.0", analysis2.getCreatedAt());
-
-    verifyDebugLogs("Resolving new code period by previous version: 1.0");
-  }
-
-  @Test
-  public void load_previous_version_when_version_is_changing() {
-    SnapshotDto analysis1 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226379600000L).setProjectVersion("0.9").setLast(false)); // 2008-11-11
-    SnapshotDto analysis2 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226494680000L).setProjectVersion("0.9").setLast(true)); // 2008-11-12
-
-    dbTester.events().insertEvent(newEvent(analysis2).setName("0.9").setCategory(CATEGORY_VERSION).setDate(analysis2.getCreatedAt()));
-    setupRoot(project, "1.0");
-    setProjectPeriod(project.uuid(), NewCodePeriodType.PREVIOUS_VERSION, null);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertPeriod(NewCodePeriodType.PREVIOUS_VERSION, "0.9", analysis2.getCreatedAt());
-
-    verifyDebugLogs("Resolving new code period by previous version: 0.9");
-  }
-
-  @Test
-  @UseDataProvider("zeroOrLess")
-  public void fail_with_MessageException_if_period_is_0_or_less(int zeroOrLess) {
-    setupRoot(project);
-    setProjectPeriod(project.uuid(), NewCodePeriodType.NUMBER_OF_DAYS, String.valueOf(zeroOrLess));
-
-    verifyFailWithInvalidValueMessageException(String.valueOf(zeroOrLess),
-      "Invalid code period '" + zeroOrLess + "': number of days is <= 0");
-  }
-
-  @Test
-  public void load_previous_version_with_previous_version_deleted() {
-    SnapshotDto analysis1 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226379600000L).setProjectVersion("0.9").setLast(false)); // 2008-11-11
-    SnapshotDto analysis2 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226494680000L).setProjectVersion("1.0").setLast(false)); // 2008-11-12
-    SnapshotDto analysis3 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227157200000L).setProjectVersion("1.1").setLast(true)); // 2008-11-20
-    dbTester.events().insertEvent(newEvent(analysis1).setName("0.9").setCategory(CATEGORY_VERSION));
-    // The "1.0" version was deleted from the history
-    setupRoot(project, "1.1");
-
-    underTest.execute(new TestComputationStepContext());
-
-    // Analysis form 2008-11-11
-    assertPeriod(NewCodePeriodType.PREVIOUS_VERSION, "0.9", analysis1.getCreatedAt());
-  }
-
-  @Test
-  public void load_previous_version_with_first_analysis_when_no_previous_version_found() {
-    SnapshotDto analysis1 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226379600000L).setProjectVersion("1.1").setLast(false)); // 2008-11-11
-    SnapshotDto analysis2 = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227934800000L).setProjectVersion("1.1").setLast(true)); // 2008-11-29
-    dbTester.events().insertEvent(newEvent(analysis2).setName("1.1").setCategory(CATEGORY_VERSION));
-    setupRoot(project, "1.1");
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertPeriod(NewCodePeriodType.PREVIOUS_VERSION, null, analysis1.getCreatedAt());
-
-    verifyDebugLogs("Resolving first analysis as new code period as there is only one existing version");
-  }
-
-  @Test
-  public void load_previous_version_with_first_analysis_when_previous_snapshot_is_the_last_one() {
-    SnapshotDto analysis = dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226379600000L).setProjectVersion("0.9").setLast(true)); // 2008-11-11
-    dbTester.events().insertEvent(newEvent(analysis).setName("0.9").setCategory(CATEGORY_VERSION));
-    setupRoot(project, "1.1");
-
-    dbTester.newCodePeriods().insert(NewCodePeriodType.PREVIOUS_VERSION, null);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertPeriod(NewCodePeriodType.PREVIOUS_VERSION, "0.9", analysis.getCreatedAt());
-    verifyDebugLogs("Resolving new code period by previous version: 0.9");
-  }
-
-  @Test
-  @UseDataProvider("anyValidLeakPeriodSettingValue")
-  public void leak_period_setting_is_ignored_for_PR(NewCodePeriodType type, @Nullable String value) {
-    when(analysisMetadataHolder.isBranch()).thenReturn(false);
-
-    dbTester.newCodePeriods().insert(type, value);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(periodsHolder.hasPeriod()).isFalse();
-  }
-
-  private void verifyFailWithInvalidValueMessageException(String propertyValue, String debugLog, String... otherDebugLogs) {
-    try {
-      underTest.execute(new TestComputationStepContext());
-      fail("a Message Exception should have been thrown");
-    } catch (MessageException e) {
-      verifyInvalidValueMessage(e, propertyValue);
-      verifyDebugLogs(debugLog, otherDebugLogs);
-    }
-  }
-
-  @DataProvider
-  public static Object[][] zeroOrLess() {
-    return new Object[][] {
-      {0},
-      {-1 - new Random().nextInt(30)}
-    };
-  }
-
-  @DataProvider
-  public static Object[][] stringConsideredAsVersions() {
-    return new Object[][] {
-      {randomAlphabetic(5)},
-      {"1,3"},
-      {"1.3"},
-      {"0 1"},
-      {"1-SNAPSHOT"},
-      {"01-12-2018"}, // unsupported date format
-    };
-  }
-
-  @DataProvider
-  public static Object[][] projectVersionNullOrNot() {
-    return new Object[][] {
-      {null},
-      {randomAlphabetic(15)},
-    };
-  }
-
-  @DataProvider
-  public static Object[][] anyValidLeakPeriodSettingValue() {
-    return new Object[][] {
-      // days
-      {NewCodePeriodType.NUMBER_OF_DAYS, "100"},
-      // previous_version
-      {NewCodePeriodType.PREVIOUS_VERSION, null}
-    };
-  }
-
-  private List<SnapshotDto> createSnapshots(ComponentDto project) {
-    ArrayList<SnapshotDto> list = new ArrayList<>();
-    list.add(dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226379600000L).setLast(false))); // 2008-11-11
-    list.add(dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1226494680000L).setLast(false))); // 2008-11-12
-    list.add(dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227157200000L).setLast(false))); // 2008-11-20
-    list.add(dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227358680000L).setLast(false))); // 2008-11-22
-    list.add(dbTester.components().insertSnapshot(project, snapshot -> snapshot.setCreatedAt(1227934800000L).setLast(true))); // 2008-11-29
-    return list;
-  }
-
-  private long milisSinceEpoch(int year, int month, int day, int hour) {
-    return ZonedDateTime.of(year, month, day, hour, 0, 0, 0, ZoneId.systemDefault())
-      .toInstant().toEpochMilli();
-  }
-
-  private void setProjectPeriod(String projectUuid, NewCodePeriodType type, @Nullable String value) {
-    dbTester.newCodePeriods().insert(projectUuid, type, value);
-  }
-
-  private void setBranchPeriod(String projectUuid, String branchUuid, NewCodePeriodType type, @Nullable String value) {
-    dbTester.newCodePeriods().insert(projectUuid, branchUuid, type, value);
-  }
-
-  private void setGlobalPeriod(NewCodePeriodType type, @Nullable String value) {
-    dbTester.newCodePeriods().insert(type, value);
-  }
-
-  private void assertPeriod(NewCodePeriodType type, @Nullable String value, @Nullable Long date) {
-    Period period = periodsHolder.getPeriod();
-    assertThat(period).isNotNull();
-    assertThat(period.getMode()).isEqualTo(type.name());
-    assertThat(period.getModeParameter()).isEqualTo(value);
-    assertThat(period.getDate()).isEqualTo(date);
-  }
-
-  private void verifyDebugLogs(String log, String... otherLogs) {
-    assertThat(logTester.getLogs()).hasSize(1 + otherLogs.length);
-    assertThat(logTester.getLogs(LoggerLevel.DEBUG))
-      .extracting(LogAndArguments::getFormattedMsg)
-      .containsOnly(Stream.concat(Stream.of(log), Arrays.stream(otherLogs)).toArray(String[]::new));
-  }
-
-  private void setupRoot(ComponentDto project) {
-    setupRoot(project, randomAlphanumeric(3));
-  }
-
-  private void setupRoot(ComponentDto projectDto, String version) {
-    treeRootHolder.setRoot(ReportComponent
-      .builder(Component.Type.PROJECT, 1)
-      .setUuid(projectDto.uuid())
-      .setKey(projectDto.getKey())
-      .setProjectVersion(version)
-      .build());
-
-    Project project = mock(Project.class);
-    when(project.getUuid()).thenReturn(projectDto.getMainBranchProjectUuid() != null ? projectDto.getMainBranchProjectUuid() : projectDto.uuid());
-    when(analysisMetadataHolder.getProject()).thenReturn(project);
-  }
-
-  private static void verifyInvalidValueMessage(MessageException e, String propertyValue) {
-    assertThat(e).hasMessage("Invalid new code period. '" + propertyValue
-      + "' is not one of: integer > 0, date before current analysis j, \"previous_version\", or version string that exists in the project' \n" +
-      "Please contact a project administrator to correct this setting");
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStepTest.java
deleted file mode 100644 (file)
index e8ef7de..0000000
+++ /dev/null
@@ -1,352 +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.DataProviderRunner;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-import org.assertj.core.api.Assertions;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.sonar.api.Plugin;
-import org.sonar.api.utils.MessageException;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.CeTask;
-import org.sonar.ce.task.projectanalysis.analysis.MutableAnalysisMetadataHolderRule;
-import org.sonar.ce.task.projectanalysis.analysis.ScannerPlugin;
-import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
-import org.sonar.ce.task.projectanalysis.component.BranchLoader;
-import org.sonar.ce.task.step.ComputationStep;
-import org.sonar.ce.task.step.TestComputationStepContext;
-import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.platform.PluginRepository;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.scanner.protocol.output.ScannerReport;
-import org.sonar.server.project.DefaultBranchNameResolver;
-import org.sonar.server.project.Project;
-
-import static java.util.Arrays.stream;
-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;
-
-@RunWith(DataProviderRunner.class)
-public class LoadReportAnalysisMetadataHolderStepTest {
-
-  private static final String PROJECT_KEY = "project_key";
-  private static final long ANALYSIS_DATE = 123456789L;
-
-  @Rule
-  public DbTester db = DbTester.create(System2.INSTANCE);
-  @Rule
-  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
-  @Rule
-  public MutableAnalysisMetadataHolderRule analysisMetadataHolder = new MutableAnalysisMetadataHolderRule();
-
-  private final DbClient dbClient = db.getDbClient();
-  private final TestPluginRepository pluginRepository = new TestPluginRepository();
-  private ComponentDto project;
-  private ComputationStep underTest;
-
-  @Before
-  public void setUp() {
-    CeTask defaultOrgCeTask = createCeTask(PROJECT_KEY);
-    underTest = createStep(defaultOrgCeTask);
-    project = db.components().insertPublicProject(p -> p.setKey(PROJECT_KEY));
-  }
-
-  @Test
-  public void set_root_component_ref() {
-    reportReader.setMetadata(
-      newBatchReportBuilder()
-        .setRootComponentRef(1)
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(analysisMetadataHolder.getRootComponentRef()).isOne();
-  }
-
-  @Test
-  public void set_analysis_date() {
-    reportReader.setMetadata(
-      newBatchReportBuilder()
-        .setAnalysisDate(ANALYSIS_DATE)
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(analysisMetadataHolder.getAnalysisDate()).isEqualTo(ANALYSIS_DATE);
-  }
-
-  @Test
-  public void set_new_code_reference_branch() {
-    String newCodeReferenceBranch = "newCodeReferenceBranch";
-    reportReader.setMetadata(
-      newBatchReportBuilder()
-        .setNewCodeReferenceBranch(newCodeReferenceBranch)
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(analysisMetadataHolder.getNewCodeReferenceBranch()).hasValue(newCodeReferenceBranch);
-  }
-
-  @Test
-  public void set_project_from_dto() {
-    reportReader.setMetadata(
-      newBatchReportBuilder()
-        .setRootComponentRef(1)
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    Project project = analysisMetadataHolder.getProject();
-    assertThat(project.getUuid()).isEqualTo(this.project.uuid());
-    assertThat(project.getKey()).isEqualTo(this.project.getKey());
-    assertThat(project.getName()).isEqualTo(this.project.name());
-    assertThat(project.getDescription()).isEqualTo(this.project.description());
-  }
-
-  @Test
-  public void set_cross_project_duplication_to_true() {
-    reportReader.setMetadata(
-      newBatchReportBuilder()
-        .setCrossProjectDuplicationActivated(true)
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(analysisMetadataHolder.isCrossProjectDuplicationEnabled()).isTrue();
-  }
-
-  @Test
-  public void set_cross_project_duplication_to_false() {
-    reportReader.setMetadata(
-      newBatchReportBuilder()
-        .setCrossProjectDuplicationActivated(false)
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(analysisMetadataHolder.isCrossProjectDuplicationEnabled()).isFalse();
-  }
-
-  @Test
-  public void set_cross_project_duplication_to_false_when_nothing_in_the_report() {
-    reportReader.setMetadata(
-      newBatchReportBuilder()
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(analysisMetadataHolder.isCrossProjectDuplicationEnabled()).isFalse();
-  }
-
-  @Test
-  public void execute_fails_with_ISE_if_component_is_null_in_CE_task() {
-    CeTask res = mock(CeTask.class);
-    when(res.getComponent()).thenReturn(Optional.empty());
-    reportReader.setMetadata(ScannerReport.Metadata.newBuilder().build());
-
-    ComputationStep underTest = createStep(res);
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("component missing on ce task");
-  }
-
-  @Test
-  public void execute_fails_with_MessageException_if_main_projectKey_is_null_in_CE_task() {
-    CeTask res = mock(CeTask.class);
-    Optional<CeTask.Component> component = Optional.of(new CeTask.Component("main_prj_uuid", null, null));
-    when(res.getComponent()).thenReturn(component);
-    when(res.getMainComponent()).thenReturn(component);
-    reportReader.setMetadata(ScannerReport.Metadata.newBuilder().build());
-
-    ComputationStep underTest = createStep(res);
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(MessageException.class)
-      .hasMessage("Compute Engine task main component key is null. Project with UUID main_prj_uuid must have been deleted since report was uploaded. Can not proceed.");
-  }
-
-  @Test
-  public void execute_fails_with_MessageException_if_projectKey_is_null_in_CE_task() {
-    CeTask res = mock(CeTask.class);
-    Optional<CeTask.Component> component = Optional.of(new CeTask.Component("prj_uuid", null, null));
-    when(res.getComponent()).thenReturn(component);
-    when(res.getMainComponent()).thenReturn(Optional.of(new CeTask.Component("main_prj_uuid", "main_prj_key", null)));
-    reportReader.setMetadata(ScannerReport.Metadata.newBuilder().build());
-
-    ComputationStep underTest = createStep(res);
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(MessageException.class)
-      .hasMessage("Compute Engine task component key is null. Project with UUID prj_uuid must have been deleted since report was uploaded. Can not proceed.");
-  }
-
-  @Test
-  public void execute_fails_with_MessageException_when_projectKey_in_report_is_different_from_componentKey_in_CE_task() {
-    ComponentDto otherProject = db.components().insertPublicProject();
-    reportReader.setMetadata(
-      ScannerReport.Metadata.newBuilder()
-        .setProjectKey(otherProject.getKey())
-        .build());
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(MessageException.class)
-      .hasMessage("ProjectKey in report (" + otherProject.getKey() + ") is not consistent with projectKey under which the report has been submitted (" + PROJECT_KEY + ")");
-
-  }
-
-  @Test
-  public void execute_sets_branch_even_if_MessageException_is_thrown_because_projectKey_in_report_is_different_from_componentKey_in_CE_task() {
-    ComponentDto otherProject = db.components().insertPublicProject();
-    reportReader.setMetadata(
-      ScannerReport.Metadata.newBuilder()
-        .setProjectKey(otherProject.getKey())
-        .build());
-
-    try {
-      underTest.execute(new TestComputationStepContext());
-    } catch (MessageException e) {
-      assertThat(analysisMetadataHolder.getBranch()).isNotNull();
-    }
-  }
-
-  @Test
-  public void execute_sets_analysis_date_even_if_MessageException_is_thrown_because_projectKey_is_different_from_componentKey_in_CE_task() {
-    ComponentDto otherProject = db.components().insertPublicProject();
-    reportReader.setMetadata(
-      ScannerReport.Metadata.newBuilder()
-        .setProjectKey(otherProject.getKey())
-        .setAnalysisDate(ANALYSIS_DATE)
-        .build());
-
-    try {
-      underTest.execute(new TestComputationStepContext());
-    } catch (MessageException e) {
-      assertThat(analysisMetadataHolder.getAnalysisDate()).isEqualTo(ANALYSIS_DATE);
-    }
-  }
-
-  @Test
-  public void execute_does_not_fail_when_report_has_a_quality_profile_that_does_not_exist_anymore() {
-    ComponentDto project = db.components().insertPublicProject();
-    ScannerReport.Metadata.Builder metadataBuilder = newBatchReportBuilder();
-    metadataBuilder
-      .setProjectKey(project.getKey());
-    metadataBuilder.putQprofilesPerLanguage("js", ScannerReport.Metadata.QProfile.newBuilder().setKey("p1").setName("Sonar way").setLanguage("js").build());
-    reportReader.setMetadata(metadataBuilder.build());
-
-    ComputationStep underTest = createStep(createCeTask(project.getKey()));
-
-    underTest.execute(new TestComputationStepContext());
-  }
-
-  @Test
-  public void execute_read_plugins_from_report() {
-    // the installed plugins
-    pluginRepository.add(
-      new PluginInfo("java"),
-      new PluginInfo("customjava").setBasePlugin("java"),
-      new PluginInfo("php"));
-
-    // the plugins sent by scanner
-    ScannerReport.Metadata.Builder metadataBuilder = newBatchReportBuilder();
-    metadataBuilder.putPluginsByKey("java", ScannerReport.Metadata.Plugin.newBuilder().setKey("java").setUpdatedAt(10L).build());
-    metadataBuilder.putPluginsByKey("php", ScannerReport.Metadata.Plugin.newBuilder().setKey("php").setUpdatedAt(20L).build());
-    metadataBuilder.putPluginsByKey("customjava", ScannerReport.Metadata.Plugin.newBuilder().setKey("customjava").setUpdatedAt(30L).build());
-    metadataBuilder.putPluginsByKey("uninstalled", ScannerReport.Metadata.Plugin.newBuilder().setKey("uninstalled").setUpdatedAt(40L).build());
-    reportReader.setMetadata(metadataBuilder.build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    Assertions.assertThat(analysisMetadataHolder.getScannerPluginsByKey().values()).extracting(ScannerPlugin::getKey, ScannerPlugin::getBasePluginKey, ScannerPlugin::getUpdatedAt)
-      .containsExactlyInAnyOrder(
-        tuple("java", null, 10L),
-        tuple("php", null, 20L),
-        tuple("customjava", "java", 30L),
-        tuple("uninstalled", null, 40L));
-  }
-
-  private LoadReportAnalysisMetadataHolderStep createStep(CeTask ceTask) {
-    return new LoadReportAnalysisMetadataHolderStep(ceTask, reportReader, analysisMetadataHolder,
-      dbClient, new BranchLoader(analysisMetadataHolder, mock(DefaultBranchNameResolver.class)), pluginRepository);
-  }
-
-  private static ScannerReport.Metadata.Builder newBatchReportBuilder() {
-    return ScannerReport.Metadata.newBuilder()
-      .setProjectKey(PROJECT_KEY);
-  }
-
-  private CeTask createCeTask(String projectKey) {
-    CeTask res = mock(CeTask.class);
-    Optional<CeTask.Component> component = Optional.of(new CeTask.Component(projectKey + "_uuid", projectKey, projectKey + "_name"));
-    when(res.getComponent()).thenReturn(component);
-    when(res.getMainComponent()).thenReturn(component);
-    return res;
-  }
-
-  private static class TestPluginRepository implements PluginRepository {
-    private final Map<String, PluginInfo> pluginsMap = new HashMap<>();
-
-    void add(PluginInfo... plugins) {
-      stream(plugins).forEach(p -> pluginsMap.put(p.getKey(), p));
-    }
-
-    @Override
-    public Collection<PluginInfo> getPluginInfos() {
-      return pluginsMap.values();
-    }
-
-    @Override
-    public PluginInfo getPluginInfo(String key) {
-      if (!pluginsMap.containsKey(key)) {
-        throw new IllegalArgumentException();
-      }
-      return pluginsMap.get(key);
-    }
-
-    @Override
-    public Plugin getPluginInstance(String key) {
-      throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public Collection<Plugin> getPluginInstances() {
-      throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean hasPlugin(String key) {
-      return pluginsMap.containsKey(key);
-    }
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistAdHocRulesStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistAdHocRulesStepTest.java
deleted file mode 100644 (file)
index 56046b6..0000000
+++ /dev/null
@@ -1,111 +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.Optional;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.projectanalysis.issue.AdHocRuleCreator;
-import org.sonar.ce.task.projectanalysis.issue.NewAdHocRule;
-import org.sonar.ce.task.projectanalysis.issue.RuleRepositoryImpl;
-import org.sonar.ce.task.step.ComputationStep;
-import org.sonar.ce.task.step.TestComputationStepContext;
-import org.sonar.core.util.SequenceUuidFactory;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.db.rule.RuleDao;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.scanner.protocol.output.ScannerReport;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.rule.index.RuleIndexDefinition;
-import org.sonar.server.rule.index.RuleIndexer;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class PersistAdHocRulesStepTest extends BaseStepTest {
-
-  @Rule
-  public DbTester db = DbTester.create(System2.INSTANCE);
-
-  private DbClient dbClient = db.getDbClient();
-
-  private ComputationStep underTest;
-  private RuleRepositoryImpl ruleRepository;
-
-  @org.junit.Rule
-  public EsTester es = EsTester.create();
-
-  private RuleIndexer indexer = new RuleIndexer(es.client(), dbClient);
-  private AdHocRuleCreator adHocRuleCreator = new AdHocRuleCreator(dbClient, System2.INSTANCE, indexer, new SequenceUuidFactory());
-
-  @Override
-  protected ComputationStep step() {
-    return underTest;
-  }
-
-  @Before
-  public void setup() {
-    ruleRepository = new RuleRepositoryImpl(adHocRuleCreator, dbClient);
-    underTest = new PersistAdHocRulesStep(dbClient, ruleRepository);
-  }
-
-  @Test
-  public void persist_and_index_new_ad_hoc_rules() {
-    RuleKey ruleKey = RuleKey.of("external_eslint", "no-cond-assign");
-    ruleRepository.addOrUpdateAddHocRuleIfNeeded(ruleKey,
-      () -> new NewAdHocRule(ScannerReport.ExternalIssue.newBuilder().setEngineId("eslint").setRuleId("no-cond-assign").build()));
-
-    underTest.execute(new TestComputationStepContext());
-
-    RuleDao ruleDao = dbClient.ruleDao();
-    Optional<RuleDto> ruleDefinitionDtoOptional = ruleDao.selectByKey(dbClient.openSession(false), ruleKey);
-    assertThat(ruleDefinitionDtoOptional).isPresent();
-
-    RuleDto reloaded = ruleDefinitionDtoOptional.get();
-    assertThat(reloaded.getRuleKey()).isEqualTo("no-cond-assign");
-    assertThat(reloaded.getRepositoryKey()).isEqualTo("external_eslint");
-    assertThat(reloaded.isExternal()).isTrue();
-    assertThat(reloaded.isAdHoc()).isTrue();
-    assertThat(reloaded.getType()).isZero();
-    assertThat(reloaded.getSeverity()).isNull();
-    assertThat(reloaded.getName()).isEqualTo("eslint:no-cond-assign");
-
-    assertThat(es.countDocuments(RuleIndexDefinition.TYPE_RULE)).isOne();
-    assertThat(es.getDocuments(RuleIndexDefinition.TYPE_RULE).iterator().next().getId()).isEqualTo(reloaded.getUuid());
-  }
-
-  @Test
-  public void do_not_persist_existing_external_rules() {
-    RuleKey ruleKey = RuleKey.of("eslint", "no-cond-assign");
-    db.rules().insert(ruleKey, r -> r.setIsExternal(true));
-    ruleRepository.addOrUpdateAddHocRuleIfNeeded(ruleKey,
-      () -> new NewAdHocRule(ScannerReport.ExternalIssue.newBuilder().setEngineId("eslint").setRuleId("no-cond-assign").build()));
-
-    underTest.execute(new TestComputationStepContext());
-
-    RuleDao ruleDao = dbClient.ruleDao();
-    assertThat(ruleDao.selectAll(dbClient.openSession(false))).hasSize(1);
-    assertThat(es.countDocuments(RuleIndexDefinition.TYPE_RULE)).isZero();
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistDuplicationDataStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistDuplicationDataStepTest.java
deleted file mode 100644 (file)
index 3488d4f..0000000
+++ /dev/null
@@ -1,189 +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.Optional;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.measures.Metric;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.projectanalysis.analysis.MutableAnalysisMetadataHolderRule;
-import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
-import org.sonar.ce.task.projectanalysis.duplication.DuplicationRepositoryRule;
-import org.sonar.ce.task.projectanalysis.duplication.TextBlock;
-import org.sonar.ce.task.projectanalysis.measure.MeasureToMeasureDto;
-import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule;
-import org.sonar.ce.task.step.ComputationStep;
-import org.sonar.ce.task.step.TestComputationStepContext;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.measure.LiveMeasureDto;
-import org.sonar.db.metric.MetricDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.api.measures.CoreMetrics.DUPLICATIONS_DATA_KEY;
-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;
-
-public class PersistDuplicationDataStepTest extends BaseStepTest {
-
-  private static final int ROOT_REF = 1;
-  private static final String PROJECT_KEY = "PROJECT_KEY";
-  private static final String PROJECT_UUID = "u1";
-
-  private static final int FILE_1_REF = 2;
-  private static final String FILE_1_KEY = "FILE_1_KEY";
-  private static final String FILE_1_UUID = "u2";
-
-  private static final int FILE_2_REF = 3;
-  private static final String FILE_2_KEY = "FILE_2_KEY";
-  private static final String FILE_2_UUID = "u3";
-
-  @Rule
-  public DbTester db = DbTester.create(System2.INSTANCE);
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule()
-    .setRoot(
-      builder(PROJECT, ROOT_REF).setKey(PROJECT_KEY).setUuid(PROJECT_UUID)
-        .addChildren(
-          builder(FILE, FILE_1_REF).setKey(FILE_1_KEY).setUuid(FILE_1_UUID)
-            .build(),
-          builder(FILE, FILE_2_REF).setKey(FILE_2_KEY).setUuid(FILE_2_UUID)
-            .build())
-        .build());
-
-  @Rule
-  public MutableAnalysisMetadataHolderRule analysisMetadataHolder = new MutableAnalysisMetadataHolderRule();
-  @Rule
-  public DuplicationRepositoryRule duplicationRepository = DuplicationRepositoryRule.create(treeRootHolder);
-  @Rule
-  public MetricRepositoryRule metricRepository = new MetricRepositoryRule();
-
-  @Before
-  public void setUp() {
-    MetricDto metric = db.measures().insertMetric(m -> m.setKey(DUPLICATIONS_DATA_KEY).setValueType(Metric.ValueType.STRING.name()));
-    insertComponent(PROJECT_KEY, PROJECT_UUID);
-    insertComponent(FILE_1_KEY, FILE_1_UUID);
-    insertComponent(FILE_2_KEY, FILE_2_UUID);
-    db.commit();
-    metricRepository.add(metric.getUuid(), new Metric.Builder(DUPLICATIONS_DATA_KEY, DUPLICATIONS_DATA_KEY, Metric.ValueType.STRING).create());
-  }
-
-  @Override
-  protected ComputationStep step() {
-    return underTest();
-  }
-
-  @Test
-  public void nothing_to_persist_when_no_duplication() {
-    TestComputationStepContext context = new TestComputationStepContext();
-
-    underTest().execute(context);
-
-    assertThatNothingPersisted();
-    verifyStatistics(context, 0);
-  }
-
-  @Test
-  public void compute_duplications_on_same_file() {
-    duplicationRepository.addDuplication(FILE_1_REF, new TextBlock(1, 5), new TextBlock(6, 10));
-    TestComputationStepContext context = new TestComputationStepContext();
-
-    underTest().execute(context);
-
-    assertThat(selectMeasureData(FILE_1_UUID)).hasValue("<duplications><g><b s=\"1\" l=\"5\" t=\"false\" r=\"" + FILE_1_KEY + "\"/><b s=\"6\" l=\"5\" t=\"false\" r=\""
-        + FILE_1_KEY + "\"/></g></duplications>");
-    assertThat(selectMeasureData(FILE_2_UUID)).isEmpty();
-    assertThat(selectMeasureData(PROJECT_UUID)).isEmpty();
-  }
-
-  @Test
-  public void compute_duplications_on_different_files() {
-    duplicationRepository.addDuplication(FILE_1_REF, new TextBlock(1, 5), FILE_2_REF, new TextBlock(6, 10));
-    TestComputationStepContext context = new TestComputationStepContext();
-
-    underTest().execute(context);
-
-    assertThat(selectMeasureData(FILE_1_UUID)).hasValue(
-      "<duplications><g><b s=\"1\" l=\"5\" t=\"false\" r=\"" + FILE_1_KEY + "\"/><b s=\"6\" l=\"5\" t=\"false\" r=\""
-        + FILE_2_KEY + "\"/></g></duplications>");
-    assertThat(selectMeasureData(FILE_2_UUID)).isEmpty();
-    assertThat(selectMeasureData(PROJECT_UUID)).isEmpty();
-  }
-
-  @Test
-  public void compute_duplications_on_unchanged_file() {
-    duplicationRepository.addExtendedProjectDuplication(FILE_1_REF, new TextBlock(1, 5), FILE_2_REF, new TextBlock(6, 10));
-    TestComputationStepContext context = new TestComputationStepContext();
-
-    underTest().execute(context);
-
-    assertThat(selectMeasureData(FILE_1_UUID)).hasValue(
-      "<duplications><g><b s=\"1\" l=\"5\" t=\"false\" r=\"" + FILE_1_KEY + "\"/><b s=\"6\" l=\"5\" t=\"true\" r=\""
-        + FILE_2_KEY + "\"/></g></duplications>");
-    assertThat(selectMeasureData(FILE_2_UUID)).isEmpty();
-    assertThat(selectMeasureData(PROJECT_UUID)).isEmpty();
-  }
-
-  @Test
-  public void compute_duplications_on_different_projects() {
-    String fileKeyFromOtherProject = "PROJECT2_KEY:file2";
-    duplicationRepository.addCrossProjectDuplication(FILE_1_REF, new TextBlock(1, 5), fileKeyFromOtherProject, new TextBlock(6, 10));
-    TestComputationStepContext context = new TestComputationStepContext();
-
-    underTest().execute(context);
-
-    assertThat(selectMeasureData(FILE_1_UUID)).hasValue(
-      "<duplications><g><b s=\"1\" l=\"5\" t=\"false\" r=\"" + FILE_1_KEY + "\"/><b s=\"6\" l=\"5\" t=\"false\" r=\""
-        + fileKeyFromOtherProject + "\"/></g></duplications>");
-    assertThat(selectMeasureData(FILE_2_UUID)).isEmpty();
-    assertThat(selectMeasureData(PROJECT_UUID)).isEmpty();
-  }
-
-  private PersistDuplicationDataStep underTest() {
-    return new PersistDuplicationDataStep(db.getDbClient(), treeRootHolder, metricRepository, duplicationRepository,
-      new MeasureToMeasureDto(analysisMetadataHolder, treeRootHolder));
-  }
-
-  private void assertThatNothingPersisted() {
-    assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isZero();
-  }
-
-  private Optional<String> selectMeasureData(String componentUuid) {
-      return db.getDbClient().liveMeasureDao().selectMeasure(db.getSession(), componentUuid, "duplications_data")
-      .map(LiveMeasureDto::getTextValue);
-  }
-
-  private ComponentDto insertComponent(String key, String uuid) {
-    ComponentDto componentDto = new ComponentDto()
-      .setKey(key)
-      .setUuid(uuid)
-      .setUuidPath(uuid + ".")
-      .setBranchUuid(uuid);
-    db.components().insertComponent(componentDto);
-    return componentDto;
-  }
-
-  private static void verifyStatistics(TestComputationStepContext context, int expectedInsertsOrUpdates) {
-    context.getStatistics().assertValue("insertsOrUpdates", expectedInsertsOrUpdates);
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistIssuesStepTest.java
deleted file mode 100644 (file)
index eaea64b..0000000
+++ /dev/null
@@ -1,631 +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.Date;
-import java.util.List;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.mockito.ArgumentCaptor;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rules.RuleType;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
-import org.sonar.ce.task.projectanalysis.issue.AdHocRuleCreator;
-import org.sonar.ce.task.projectanalysis.issue.ProtoIssueCache;
-import org.sonar.ce.task.projectanalysis.issue.RuleRepositoryImpl;
-import org.sonar.ce.task.projectanalysis.issue.UpdateConflictResolver;
-import org.sonar.ce.task.projectanalysis.period.Period;
-import org.sonar.ce.task.projectanalysis.period.PeriodHolderRule;
-import org.sonar.ce.task.projectanalysis.util.cache.DiskCache;
-import org.sonar.ce.task.step.ComputationStep;
-import org.sonar.ce.task.step.TestComputationStepContext;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.DefaultIssueComment;
-import org.sonar.core.issue.FieldDiffs;
-import org.sonar.core.util.UuidFactoryImpl;
-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.issue.IssueChangeDto;
-import org.sonar.db.issue.IssueDto;
-import org.sonar.db.issue.IssueMapper;
-import org.sonar.db.newcodeperiod.NewCodePeriodType;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.db.rule.RuleTesting;
-import org.sonar.scanner.protocol.output.ScannerReport;
-import org.sonar.server.issue.IssueStorage;
-
-import static java.util.Collections.singletonList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.data.MapEntry.entry;
-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.when;
-import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
-import static org.sonar.api.issue.Issue.STATUS_CLOSED;
-import static org.sonar.api.issue.Issue.STATUS_OPEN;
-import static org.sonar.api.rule.Severity.BLOCKER;
-import static org.sonar.db.component.ComponentTesting.newFileDto;
-import static org.sonar.db.issue.IssueTesting.newCodeReferenceIssue;
-
-public class PersistIssuesStepTest extends BaseStepTest {
-
-  private static final long NOW = 1_400_000_000_000L;
-
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-  @Rule
-  public DbTester db = DbTester.create(System2.INSTANCE);
-  @Rule
-  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
-  @Rule
-  public PeriodHolderRule periodHolder = new PeriodHolderRule();
-
-  private System2 system2 = mock(System2.class);
-  private DbSession session = db.getSession();
-  private DbClient dbClient = db.getDbClient();
-  private UpdateConflictResolver conflictResolver = mock(UpdateConflictResolver.class);
-  private ProtoIssueCache protoIssueCache;
-  private ComputationStep underTest;
-
-  private AdHocRuleCreator adHocRuleCreator = mock(AdHocRuleCreator.class);
-
-  @Override
-  protected ComputationStep step() {
-    return underTest;
-  }
-
-  @Before
-  public void setup() throws Exception {
-    periodHolder.setPeriod(new Period(NewCodePeriodType.NUMBER_OF_DAYS.name(), "10", 1000L));
-
-    protoIssueCache = new ProtoIssueCache(temp.newFile(), System2.INSTANCE);
-    reportReader.setMetadata(ScannerReport.Metadata.getDefaultInstance());
-
-    underTest = new PersistIssuesStep(dbClient, system2, conflictResolver, new RuleRepositoryImpl(adHocRuleCreator, dbClient), periodHolder,
-      protoIssueCache, new IssueStorage(), UuidFactoryImpl.INSTANCE);
-  }
-
-  @After
-  public void tearDown() {
-    session.close();
-  }
-
-  @Test
-  public void insert_copied_issue() {
-    RuleDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01"));
-    db.rules().insert(rule);
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project, null));
-    when(system2.now()).thenReturn(NOW);
-    String issueKey = "ISSUE-1";
-
-    protoIssueCache.newAppender().append(new DefaultIssue()
-        .setKey(issueKey)
-        .setType(RuleType.CODE_SMELL)
-        .setRuleKey(rule.getKey())
-        .setComponentUuid(file.uuid())
-        .setComponentKey(file.getKey())
-        .setProjectUuid(project.uuid())
-        .setProjectKey(project.getKey())
-        .setSeverity(BLOCKER)
-        .setStatus(STATUS_OPEN)
-        .setTags(singletonList("test"))
-        .setNew(false)
-        .setCopied(true)
-        .setType(RuleType.BUG)
-        .setCreationDate(new Date(NOW))
-        .setSelectedAt(NOW)
-        .addComment(new DefaultIssueComment()
-          .setKey("COMMENT")
-          .setIssueKey(issueKey)
-          .setUserUuid("john_uuid")
-          .setMarkdownText("Some text")
-          .setCreatedAt(new Date(NOW))
-          .setUpdatedAt(new Date(NOW))
-          .setNew(true))
-        .setCurrentChange(
-          new FieldDiffs()
-            .setIssueKey(issueKey)
-            .setUserUuid("john_uuid")
-            .setDiff("technicalDebt", null, 1L)
-            .setCreationDate(new Date(NOW))))
-      .close();
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    IssueDto result = dbClient.issueDao().selectOrFailByKey(session, issueKey);
-
-    assertThat(result.getKey()).isEqualTo(issueKey);
-    assertThat(result.getRuleKey()).isEqualTo(rule.getKey());
-    assertThat(result.getComponentUuid()).isEqualTo(file.uuid());
-    assertThat(result.getProjectUuid()).isEqualTo(project.uuid());
-    assertThat(result.getSeverity()).isEqualTo(BLOCKER);
-    assertThat(result.getStatus()).isEqualTo(STATUS_OPEN);
-    assertThat(result.getType()).isEqualTo(RuleType.BUG.getDbConstant());
-    assertThat(result.getTags()).containsExactlyInAnyOrder("test");
-    assertThat(result.isNewCodeReferenceIssue()).isFalse();
-
-    List<IssueChangeDto> changes = dbClient.issueChangeDao().selectByIssueKeys(session, Arrays.asList(issueKey));
-    assertThat(changes).extracting(IssueChangeDto::getChangeType).containsExactly(IssueChangeDto.TYPE_COMMENT, IssueChangeDto.TYPE_FIELD_CHANGE);
-    assertThat(context.getStatistics().getAll()).contains(
-      entry("inserts", "1"), entry("updates", "0"), entry("merged", "0"));
-  }
-
-  @Test
-  public void insert_copied_issue_with_minimal_info() {
-    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "master", null));
-
-    RuleDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01"));
-    db.rules().insert(rule);
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project, null));
-    when(system2.now()).thenReturn(NOW);
-    String issueKey = "ISSUE-2";
-
-    protoIssueCache.newAppender().append(new DefaultIssue()
-        .setKey(issueKey)
-        .setType(RuleType.CODE_SMELL)
-        .setRuleKey(rule.getKey())
-        .setComponentUuid(file.uuid())
-        .setComponentKey(file.getKey())
-        .setProjectUuid(project.uuid())
-        .setProjectKey(project.getKey())
-        .setSeverity(BLOCKER)
-        .setStatus(STATUS_OPEN)
-        .setNew(false)
-        .setCopied(true)
-        .setType(RuleType.BUG)
-        .setCreationDate(new Date(NOW))
-        .setSelectedAt(NOW))
-      .close();
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    IssueDto result = dbClient.issueDao().selectOrFailByKey(session, issueKey);
-    assertThat(result.getKey()).isEqualTo(issueKey);
-    assertThat(result.getRuleKey()).isEqualTo(rule.getKey());
-    assertThat(result.getComponentUuid()).isEqualTo(file.uuid());
-    assertThat(result.getProjectUuid()).isEqualTo(project.uuid());
-    assertThat(result.getSeverity()).isEqualTo(BLOCKER);
-    assertThat(result.getStatus()).isEqualTo(STATUS_OPEN);
-    assertThat(result.getType()).isEqualTo(RuleType.BUG.getDbConstant());
-    assertThat(result.getTags()).isEmpty();
-    assertThat(result.isNewCodeReferenceIssue()).isFalse();
-
-    assertThat(dbClient.issueChangeDao().selectByIssueKeys(session, Arrays.asList(issueKey))).isEmpty();
-    assertThat(context.getStatistics().getAll()).contains(
-      entry("inserts", "1"), entry("updates", "0"), entry("merged", "0"));
-  }
-
-  @Test
-  public void insert_merged_issue() {
-    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "master", null));
-    RuleDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01"));
-    db.rules().insert(rule);
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project, null));
-    when(system2.now()).thenReturn(NOW);
-    String issueKey = "ISSUE-3";
-
-    protoIssueCache.newAppender().append(new DefaultIssue()
-        .setKey(issueKey)
-        .setType(RuleType.CODE_SMELL)
-        .setRuleKey(rule.getKey())
-        .setComponentUuid(file.uuid())
-        .setComponentKey(file.getKey())
-        .setProjectUuid(project.uuid())
-        .setProjectKey(project.getKey())
-        .setSeverity(BLOCKER)
-        .setStatus(STATUS_OPEN)
-        .setNew(true)
-        .setIsOnChangedLine(true)
-        .setCopied(true)
-        .setType(RuleType.BUG)
-        .setCreationDate(new Date(NOW))
-        .setSelectedAt(NOW)
-        .addComment(new DefaultIssueComment()
-          .setKey("COMMENT")
-          .setIssueKey(issueKey)
-          .setUserUuid("john_uuid")
-          .setMarkdownText("Some text")
-          .setUpdatedAt(new Date(NOW))
-          .setCreatedAt(new Date(NOW))
-          .setNew(true))
-        .setCurrentChange(new FieldDiffs()
-          .setIssueKey(issueKey)
-          .setUserUuid("john_uuid")
-          .setDiff("technicalDebt", null, 1L)
-          .setCreationDate(new Date(NOW))))
-      .close();
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    IssueDto result = dbClient.issueDao().selectOrFailByKey(session, issueKey);
-    assertThat(result.getKey()).isEqualTo(issueKey);
-    assertThat(result.getRuleKey()).isEqualTo(rule.getKey());
-    assertThat(result.getComponentUuid()).isEqualTo(file.uuid());
-    assertThat(result.getProjectUuid()).isEqualTo(project.uuid());
-    assertThat(result.getSeverity()).isEqualTo(BLOCKER);
-    assertThat(result.getStatus()).isEqualTo(STATUS_OPEN);
-    assertThat(result.getType()).isEqualTo(RuleType.BUG.getDbConstant());
-    assertThat(result.isNewCodeReferenceIssue()).isTrue();
-
-    List<IssueChangeDto> changes = dbClient.issueChangeDao().selectByIssueKeys(session, Arrays.asList(issueKey));
-    assertThat(changes).extracting(IssueChangeDto::getChangeType).containsExactly(IssueChangeDto.TYPE_COMMENT, IssueChangeDto.TYPE_FIELD_CHANGE);
-    assertThat(context.getStatistics().getAll()).contains(
-      entry("inserts", "1"), entry("updates", "0"), entry("merged", "0"));
-  }
-
-  @Test
-  public void update_conflicting_issue() {
-    RuleDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01"));
-    db.rules().insert(rule);
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project, null));
-    IssueDto issue = db.issues().insert(rule, project, file,
-      i -> i.setStatus(STATUS_OPEN)
-        .setResolution(null)
-        .setCreatedAt(NOW - 1_000_000_000L)
-        // simulate the issue has been updated after the analysis ran
-        .setUpdatedAt(NOW + 1_000_000_000L));
-    issue = dbClient.issueDao().selectByKey(db.getSession(), issue.getKey()).get();
-    DiskCache.CacheAppender issueCacheAppender = protoIssueCache.newAppender();
-    when(system2.now()).thenReturn(NOW);
-
-    DefaultIssue defaultIssue = issue.toDefaultIssue()
-      .setStatus(STATUS_CLOSED)
-      .setResolution(RESOLUTION_FIXED)
-      .setSelectedAt(NOW)
-      .setNew(false)
-      .setChanged(true);
-    issueCacheAppender.append(defaultIssue).close();
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    ArgumentCaptor<IssueDto> issueDtoCaptor = ArgumentCaptor.forClass(IssueDto.class);
-    verify(conflictResolver).resolve(eq(defaultIssue), issueDtoCaptor.capture(), any(IssueMapper.class));
-    assertThat(issueDtoCaptor.getValue().getKey()).isEqualTo(issue.getKey());
-    assertThat(context.getStatistics().getAll()).contains(
-      entry("inserts", "0"), entry("updates", "1"), entry("merged", "1"));
-  }
-
-  @Test
-  public void insert_new_issue() {
-    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "master", null));
-    RuleDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01"));
-    db.rules().insert(rule);
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project, null));
-    session.commit();
-    String issueKey = "ISSUE-4";
-
-    protoIssueCache.newAppender().append(new DefaultIssue()
-      .setKey(issueKey)
-      .setType(RuleType.CODE_SMELL)
-      .setRuleKey(rule.getKey())
-      .setComponentUuid(file.uuid())
-      .setComponentKey(file.getKey())
-      .setProjectUuid(project.uuid())
-      .setProjectKey(project.getKey())
-      .setSeverity(BLOCKER)
-      .setStatus(STATUS_OPEN)
-      .setCreationDate(new Date(NOW))
-      .setNew(true)
-      .setIsOnChangedLine(true)
-      .setType(RuleType.BUG)).close();
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    IssueDto result = dbClient.issueDao().selectOrFailByKey(session, issueKey);
-    assertThat(result.getKey()).isEqualTo(issueKey);
-    assertThat(result.getRuleKey()).isEqualTo(rule.getKey());
-    assertThat(result.getComponentUuid()).isEqualTo(file.uuid());
-    assertThat(result.getProjectUuid()).isEqualTo(project.uuid());
-    assertThat(result.getSeverity()).isEqualTo(BLOCKER);
-    assertThat(result.getStatus()).isEqualTo(STATUS_OPEN);
-    assertThat(result.getType()).isEqualTo(RuleType.BUG.getDbConstant());
-    assertThat(context.getStatistics().getAll()).contains(
-      entry("inserts", "1"), entry("updates", "0"), entry("merged", "0"));
-    assertThat(result.isNewCodeReferenceIssue()).isTrue();
-  }
-
-  @Test
-  public void close_issue() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project));
-    RuleDto rule = db.rules().insert();
-    IssueDto issue = db.issues().insert(rule, project, file,
-      i -> i.setStatus(STATUS_OPEN)
-        .setResolution(null)
-        .setCreatedAt(NOW - 1_000_000_000L)
-        .setUpdatedAt(NOW - 1_000_000_000L));
-    DiskCache.CacheAppender issueCacheAppender = protoIssueCache.newAppender();
-
-    issueCacheAppender.append(
-        issue.toDefaultIssue()
-          .setStatus(STATUS_CLOSED)
-          .setResolution(RESOLUTION_FIXED)
-          .setSelectedAt(NOW)
-          .setNew(false)
-          .setChanged(true))
-      .close();
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    IssueDto issueReloaded = db.getDbClient().issueDao().selectByKey(db.getSession(), issue.getKey()).get();
-    assertThat(issueReloaded.getStatus()).isEqualTo(STATUS_CLOSED);
-    assertThat(issueReloaded.getResolution()).isEqualTo(RESOLUTION_FIXED);
-    assertThat(context.getStatistics().getAll()).contains(
-      entry("inserts", "0"), entry("updates", "1"), entry("merged", "0"));
-  }
-
-  @Test
-  public void handle_no_longer_new_issue() {
-    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "master", null));
-    RuleDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01"));
-    db.rules().insert(rule);
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project, null));
-    when(system2.now()).thenReturn(NOW);
-    String issueKey = "ISSUE-5";
-
-    DefaultIssue defaultIssue = new DefaultIssue()
-      .setKey(issueKey)
-      .setType(RuleType.CODE_SMELL)
-      .setRuleKey(rule.getKey())
-      .setComponentUuid(file.uuid())
-      .setComponentKey(file.getKey())
-      .setProjectUuid(project.uuid())
-      .setProjectKey(project.getKey())
-      .setSeverity(BLOCKER)
-      .setStatus(STATUS_OPEN)
-      .setNew(true)
-      .setIsOnChangedLine(true)
-      .setIsNewCodeReferenceIssue(true)
-      .setIsNoLongerNewCodeReferenceIssue(false)
-      .setCopied(false)
-      .setType(RuleType.BUG)
-      .setCreationDate(new Date(NOW))
-      .setSelectedAt(NOW);
-
-    IssueDto issueDto = IssueDto.toDtoForComputationInsert(defaultIssue, rule.getUuid(), NOW);
-    dbClient.issueDao().insert(session, issueDto);
-    dbClient.issueDao().insertAsNewCodeOnReferenceBranch(session, newCodeReferenceIssue(issueDto));
-    session.commit();
-
-    IssueDto result = dbClient.issueDao().selectOrFailByKey(session, issueKey);
-    assertThat(result.isNewCodeReferenceIssue()).isTrue();
-
-    protoIssueCache.newAppender().append(defaultIssue.setNew(false)
-        .setIsOnChangedLine(false)
-        .setIsNewCodeReferenceIssue(false)
-        .setIsNoLongerNewCodeReferenceIssue(true))
-      .close();
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(context.getStatistics().getAll()).contains(
-      entry("inserts", "0"), entry("updates", "1"), entry("merged", "0"));
-
-    result = dbClient.issueDao().selectOrFailByKey(session, issueKey);
-    assertThat(result.isNewCodeReferenceIssue()).isFalse();
-  }
-
-  @Test
-  public void handle_existing_new_code_issue_migration() {
-    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "master", null));
-    RuleDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01"));
-    db.rules().insert(rule);
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project, null));
-    when(system2.now()).thenReturn(NOW);
-    String issueKey = "ISSUE-6";
-
-    DefaultIssue defaultIssue = new DefaultIssue()
-      .setKey(issueKey)
-      .setType(RuleType.CODE_SMELL)
-      .setRuleKey(rule.getKey())
-      .setComponentUuid(file.uuid())
-      .setComponentKey(file.getKey())
-      .setProjectUuid(project.uuid())
-      .setProjectKey(project.getKey())
-      .setSeverity(BLOCKER)
-      .setStatus(STATUS_OPEN)
-      .setNew(true)
-      .setCopied(false)
-      .setType(RuleType.BUG)
-      .setCreationDate(new Date(NOW))
-      .setSelectedAt(NOW);
-
-    IssueDto issueDto = IssueDto.toDtoForComputationInsert(defaultIssue, rule.getUuid(), NOW);
-    dbClient.issueDao().insert(session, issueDto);
-    session.commit();
-
-    IssueDto result = dbClient.issueDao().selectOrFailByKey(session, issueKey);
-    assertThat(result.isNewCodeReferenceIssue()).isFalse();
-
-    protoIssueCache.newAppender().append(defaultIssue.setNew(false)
-        .setIsOnChangedLine(true)
-        .setIsNewCodeReferenceIssue(false)
-        .setIsNoLongerNewCodeReferenceIssue(false))
-      .close();
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(context.getStatistics().getAll()).contains(
-      entry("inserts", "0"), entry("updates", "1"), entry("merged", "0"));
-
-    result = dbClient.issueDao().selectOrFailByKey(session, issueKey);
-    assertThat(result.isNewCodeReferenceIssue()).isTrue();
-  }
-
-  @Test
-  public void handle_existing_without_need_for_new_code_issue_migration() {
-    periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), "master", null));
-    RuleDto rule = RuleTesting.newRule(RuleKey.of("xoo", "S01"));
-    db.rules().insert(rule);
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project, null));
-    when(system2.now()).thenReturn(NOW);
-    String issueKey = "ISSUE-7";
-
-    DefaultIssue defaultIssue = new DefaultIssue()
-      .setKey(issueKey)
-      .setType(RuleType.CODE_SMELL)
-      .setRuleKey(rule.getKey())
-      .setComponentUuid(file.uuid())
-      .setComponentKey(file.getKey())
-      .setProjectUuid(project.uuid())
-      .setProjectKey(project.getKey())
-      .setSeverity(BLOCKER)
-      .setStatus(STATUS_OPEN)
-      .setNew(true)
-      .setIsOnChangedLine(true)
-      .setIsNewCodeReferenceIssue(true)
-      .setIsNoLongerNewCodeReferenceIssue(false)
-      .setCopied(false)
-      .setType(RuleType.BUG)
-      .setCreationDate(new Date(NOW))
-      .setSelectedAt(NOW);
-
-    IssueDto issueDto = IssueDto.toDtoForComputationInsert(defaultIssue, rule.getUuid(), NOW);
-    dbClient.issueDao().insert(session, issueDto);
-    dbClient.issueDao().insertAsNewCodeOnReferenceBranch(session, newCodeReferenceIssue(issueDto));
-    session.commit();
-
-    IssueDto result = dbClient.issueDao().selectOrFailByKey(session, issueKey);
-    assertThat(result.isNewCodeReferenceIssue()).isTrue();
-
-    protoIssueCache.newAppender().append(defaultIssue.setNew(false)
-        .setIsOnChangedLine(false)
-        .setIsNewCodeReferenceIssue(true)
-        .setIsOnChangedLine(true)
-        .setIsNoLongerNewCodeReferenceIssue(false))
-      .close();
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    assertThat(context.getStatistics().getAll()).contains(
-      entry("inserts", "0"), entry("updates", "0"), entry("merged", "0"));
-
-    result = dbClient.issueDao().selectOrFailByKey(session, issueKey);
-    assertThat(result.isNewCodeReferenceIssue()).isTrue();
-  }
-
-  @Test
-  public void add_comment() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project));
-    RuleDto rule = db.rules().insert();
-    IssueDto issue = db.issues().insert(rule, project, file,
-      i -> i.setStatus(STATUS_OPEN)
-        .setResolution(null)
-        .setCreatedAt(NOW - 1_000_000_000L)
-        .setUpdatedAt(NOW - 1_000_000_000L));
-    DiskCache.CacheAppender issueCacheAppender = protoIssueCache.newAppender();
-
-    issueCacheAppender.append(
-        issue.toDefaultIssue()
-          .setStatus(STATUS_CLOSED)
-          .setResolution(RESOLUTION_FIXED)
-          .setSelectedAt(NOW)
-          .setNew(false)
-          .setChanged(true)
-          .addComment(new DefaultIssueComment()
-            .setKey("COMMENT")
-            .setIssueKey(issue.getKey())
-            .setUserUuid("john_uuid")
-            .setMarkdownText("Some text")
-            .setCreatedAt(new Date(NOW))
-            .setUpdatedAt(new Date(NOW))
-            .setNew(true)))
-      .close();
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    IssueChangeDto issueChangeDto = db.getDbClient().issueChangeDao().selectByIssueKeys(db.getSession(), singletonList(issue.getKey())).get(0);
-    assertThat(issueChangeDto)
-      .extracting(IssueChangeDto::getChangeType, IssueChangeDto::getUserUuid, IssueChangeDto::getChangeData, IssueChangeDto::getIssueKey,
-        IssueChangeDto::getIssueChangeCreationDate)
-      .containsOnly(IssueChangeDto.TYPE_COMMENT, "john_uuid", "Some text", issue.getKey(), NOW);
-    assertThat(context.getStatistics().getAll()).contains(
-      entry("inserts", "0"), entry("updates", "1"), entry("merged", "0"));
-  }
-
-  @Test
-  public void add_change() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(newFileDto(project));
-    RuleDto rule = db.rules().insert();
-    IssueDto issue = db.issues().insert(rule, project, file,
-      i -> i.setStatus(STATUS_OPEN)
-        .setResolution(null)
-        .setCreatedAt(NOW - 1_000_000_000L)
-        .setUpdatedAt(NOW - 1_000_000_000L));
-    DiskCache.CacheAppender issueCacheAppender = protoIssueCache.newAppender();
-
-    issueCacheAppender.append(
-        issue.toDefaultIssue()
-          .setStatus(STATUS_CLOSED)
-          .setResolution(RESOLUTION_FIXED)
-          .setSelectedAt(NOW)
-          .setNew(false)
-          .setChanged(true)
-          .setIsOnChangedLine(false)
-          .setIsNewCodeReferenceIssue(false)
-          .setCurrentChange(new FieldDiffs()
-            .setIssueKey("ISSUE")
-            .setUserUuid("john_uuid")
-            .setDiff("technicalDebt", null, 1L)
-            .setCreationDate(new Date(NOW))))
-      .close();
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    IssueChangeDto issueChangeDto = db.getDbClient().issueChangeDao().selectByIssueKeys(db.getSession(), singletonList(issue.getKey())).get(0);
-    assertThat(issueChangeDto)
-      .extracting(IssueChangeDto::getChangeType, IssueChangeDto::getUserUuid, IssueChangeDto::getChangeData, IssueChangeDto::getIssueKey,
-        IssueChangeDto::getIssueChangeCreationDate)
-      .containsOnly(IssueChangeDto.TYPE_FIELD_CHANGE, "john_uuid", "technicalDebt=1", issue.getKey(), NOW);
-    assertThat(context.getStatistics().getAll()).contains(
-      entry("inserts", "0"), entry("updates", "1"), entry("merged", "0"));
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistLiveMeasuresStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistLiveMeasuresStepTest.java
deleted file mode 100644 (file)
index 1620edc..0000000
+++ /dev/null
@@ -1,323 +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.Optional;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.measures.Metric;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.projectanalysis.analysis.MutableAnalysisMetadataHolderRule;
-import org.sonar.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.component.FileStatuses;
-import org.sonar.ce.task.projectanalysis.component.ReportComponent;
-import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
-import org.sonar.ce.task.projectanalysis.component.ViewsComponent;
-import org.sonar.ce.task.projectanalysis.measure.MeasureRepositoryRule;
-import org.sonar.ce.task.projectanalysis.measure.MeasureToMeasureDto;
-import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule;
-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.db.component.ComponentDto;
-import org.sonar.db.measure.LiveMeasureDto;
-import org.sonar.db.metric.MetricDto;
-import org.sonar.server.project.Project;
-
-import static java.util.Collections.emptyList;
-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.api.measures.CoreMetrics.BUGS;
-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.Component.Type.PROJECT_VIEW;
-import static org.sonar.ce.task.projectanalysis.component.Component.Type.SUBVIEW;
-import static org.sonar.ce.task.projectanalysis.component.Component.Type.VIEW;
-import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilder;
-import static org.sonar.db.measure.MeasureTesting.newLiveMeasure;
-
-public class PersistLiveMeasuresStepTest extends BaseStepTest {
-
-  private static final Metric STRING_METRIC = new Metric.Builder("string-metric", "String metric", Metric.ValueType.STRING).create();
-  private static final Metric INT_METRIC = new Metric.Builder("int-metric", "int metric", Metric.ValueType.INT).create();
-  private static final Metric METRIC_WITH_BEST_VALUE = new Metric.Builder("best-value-metric", "best value metric", Metric.ValueType.INT)
-    .setBestValue(0.0)
-    .setOptimizedBestValue(true)
-    .create();
-
-  private static final int REF_1 = 1;
-  private static final int REF_2 = 2;
-  private static final int REF_3 = 3;
-  private static final int REF_4 = 4;
-
-  @Rule
-  public DbTester db = DbTester.create(System2.INSTANCE);
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-  @Rule
-  public MetricRepositoryRule metricRepository = new MetricRepositoryRule();
-  @Rule
-  public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
-  @Rule
-  public MutableAnalysisMetadataHolderRule analysisMetadataHolder = new MutableAnalysisMetadataHolderRule();
-
-  private final FileStatuses fileStatuses = mock(FileStatuses.class);
-  private final DbClient dbClient = db.getDbClient();
-  private final TestComputationStepContext context = new TestComputationStepContext();
-
-  @Before
-  public void setUp() {
-    MetricDto stringMetricDto = db.measures().insertMetric(m -> m.setKey(STRING_METRIC.getKey()).setValueType(Metric.ValueType.STRING.name()));
-    MetricDto intMetricDto = db.measures().insertMetric(m -> m.setKey(INT_METRIC.getKey()).setValueType(Metric.ValueType.INT.name()));
-    MetricDto bestValueMMetricDto = db.measures()
-      .insertMetric(m -> m.setKey(METRIC_WITH_BEST_VALUE.getKey()).setValueType(Metric.ValueType.INT.name()).setOptimizedBestValue(true).setBestValue(0.0));
-    MetricDto bugs = db.measures().insertMetric(m -> m.setKey(BUGS.getKey()));
-    metricRepository.add(stringMetricDto.getUuid(), STRING_METRIC);
-    metricRepository.add(intMetricDto.getUuid(), INT_METRIC);
-    metricRepository.add(bestValueMMetricDto.getUuid(), METRIC_WITH_BEST_VALUE);
-    metricRepository.add(bugs.getUuid(), BUGS);
-  }
-
-  @Test
-  public void persist_live_measures_of_project_analysis() {
-    prepareProject();
-
-    // the computed measures
-    measureRepository.addRawMeasure(REF_1, STRING_METRIC.getKey(), newMeasureBuilder().create("project-value"));
-    measureRepository.addRawMeasure(REF_3, STRING_METRIC.getKey(), newMeasureBuilder().create("dir-value"));
-    measureRepository.addRawMeasure(REF_4, STRING_METRIC.getKey(), newMeasureBuilder().create("file-value"));
-
-    step().execute(context);
-
-    // all measures are persisted, from project to file
-    assertThat(db.countRowsOfTable("live_measures")).isEqualTo(3);
-    assertThat(selectMeasure("project-uuid", STRING_METRIC).get().getDataAsString()).isEqualTo("project-value");
-    assertThat(selectMeasure("dir-uuid", STRING_METRIC).get().getDataAsString()).isEqualTo("dir-value");
-    assertThat(selectMeasure("file-uuid", STRING_METRIC).get().getDataAsString()).isEqualTo("file-value");
-    verifyStatistics(context, 3);
-  }
-
-  @Test
-  public void measures_without_value_are_not_persisted() {
-    prepareProject();
-    measureRepository.addRawMeasure(REF_1, STRING_METRIC.getKey(), newMeasureBuilder().createNoValue());
-    measureRepository.addRawMeasure(REF_1, INT_METRIC.getKey(), newMeasureBuilder().createNoValue());
-
-    step().execute(context);
-
-    assertThatMeasureIsNotPersisted("project-uuid", STRING_METRIC);
-    assertThatMeasureIsNotPersisted("project-uuid", INT_METRIC);
-    verifyStatistics(context, 0);
-  }
-
-  @Test
-  public void measures_on_new_code_period_are_persisted() {
-    prepareProject();
-    measureRepository.addRawMeasure(REF_1, INT_METRIC.getKey(), newMeasureBuilder().create(42.0));
-
-    step().execute(context);
-
-    LiveMeasureDto persistedMeasure = selectMeasure("project-uuid", INT_METRIC).get();
-    assertThat(persistedMeasure.getValue()).isEqualTo(42.0);
-    verifyStatistics(context, 1);
-  }
-
-  @Test
-  public void delete_measures_from_db_if_no_longer_computed() {
-    prepareProject();
-    // measure to be updated
-    LiveMeasureDto measureOnFileInProject = insertMeasure("file-uuid", "project-uuid", INT_METRIC);
-    // measure to be deleted because not computed anymore
-    LiveMeasureDto otherMeasureOnFileInProject = insertMeasure("file-uuid", "project-uuid", STRING_METRIC);
-    // measure in another project, not touched
-    LiveMeasureDto measureInOtherProject = insertMeasure("other-file-uuid", "other-project-uuid", INT_METRIC);
-    db.commit();
-
-    measureRepository.addRawMeasure(REF_4, INT_METRIC.getKey(), newMeasureBuilder().create(42));
-
-    step().execute(context);
-
-    assertThatMeasureHasValue(measureOnFileInProject, 42);
-    assertThatMeasureDoesNotExist(otherMeasureOnFileInProject);
-    assertThatMeasureHasValue(measureInOtherProject, (int) measureInOtherProject.getValue().doubleValue());
-    verifyStatistics(context, 1);
-  }
-
-  @Test
-  public void do_not_persist_file_measures_with_best_value() {
-    prepareProject();
-    // measure to be deleted because new value matches the metric best value
-    LiveMeasureDto oldMeasure = insertMeasure("file-uuid", "project-uuid", INT_METRIC);
-    db.commit();
-
-    // project measure with metric best value -> persist with value 0
-    measureRepository.addRawMeasure(REF_1, METRIC_WITH_BEST_VALUE.getKey(), newMeasureBuilder().create(0));
-    // file measure with metric best value -> do not persist
-    measureRepository.addRawMeasure(REF_4, METRIC_WITH_BEST_VALUE.getKey(), newMeasureBuilder().create(0));
-
-    step().execute(context);
-
-    assertThatMeasureDoesNotExist(oldMeasure);
-    assertThatMeasureHasValue("project-uuid", METRIC_WITH_BEST_VALUE, 0);
-    verifyStatistics(context, 1);
-  }
-
-  @Test
-  public void keep_measures_for_unchanged_files() {
-    prepareProject();
-    LiveMeasureDto oldMeasure = insertMeasure("file-uuid", "project-uuid", BUGS);
-    db.commit();
-    when(fileStatuses.isDataUnchanged(any(Component.class))).thenReturn(true);
-    // this new value won't be persisted
-    measureRepository.addRawMeasure(REF_4, BUGS.getKey(), newMeasureBuilder().create(oldMeasure.getValue() + 1, 0));
-    step().execute(context);
-    assertThat(selectMeasure("file-uuid", BUGS).get().getValue()).isEqualTo(oldMeasure.getValue());
-  }
-
-  @Test
-  public void dont_keep_measures_for_unchanged_files() {
-    prepareProject();
-    LiveMeasureDto oldMeasure = insertMeasure("file-uuid", "project-uuid", BUGS);
-    db.commit();
-    when(fileStatuses.isDataUnchanged(any(Component.class))).thenReturn(false);
-    // this new value will be persisted
-    measureRepository.addRawMeasure(REF_4, BUGS.getKey(), newMeasureBuilder().create(oldMeasure.getValue() + 1, 0));
-    step().execute(context);
-    assertThat(selectMeasure("file-uuid", BUGS).get().getValue()).isEqualTo(oldMeasure.getValue() + 1);
-  }
-
-  @Test
-  public void persist_live_measures_of_portfolio_analysis() {
-    preparePortfolio();
-
-    // the computed measures
-    measureRepository.addRawMeasure(REF_1, STRING_METRIC.getKey(), newMeasureBuilder().create("view-value"));
-    measureRepository.addRawMeasure(REF_2, STRING_METRIC.getKey(), newMeasureBuilder().create("subview-value"));
-    measureRepository.addRawMeasure(REF_3, STRING_METRIC.getKey(), newMeasureBuilder().create("project-value"));
-
-    step().execute(context);
-
-    assertThat(db.countRowsOfTable("live_measures")).isEqualTo(3);
-    assertThat(selectMeasure("view-uuid", STRING_METRIC).get().getDataAsString()).isEqualTo("view-value");
-    assertThat(selectMeasure("subview-uuid", STRING_METRIC).get().getDataAsString()).isEqualTo("subview-value");
-    assertThat(selectMeasure("project-uuid", STRING_METRIC).get().getDataAsString()).isEqualTo("project-value");
-    verifyStatistics(context, 3);
-  }
-
-  private LiveMeasureDto insertMeasure(String componentUuid, String projectUuid, Metric metric) {
-    LiveMeasureDto measure = newLiveMeasure()
-      .setComponentUuid(componentUuid)
-      .setProjectUuid(projectUuid)
-      .setMetricUuid(metricRepository.getByKey(metric.getKey()).getUuid());
-    dbClient.liveMeasureDao().insertOrUpdate(db.getSession(), measure);
-    return measure;
-  }
-
-  private void assertThatMeasureHasValue(LiveMeasureDto template, int expectedValue) {
-    Optional<LiveMeasureDto> persisted = dbClient.liveMeasureDao().selectMeasure(db.getSession(),
-      template.getComponentUuid(), metricRepository.getByUuid(template.getMetricUuid()).getKey());
-    assertThat(persisted).isPresent();
-    assertThat(persisted.get().getValue()).isEqualTo(expectedValue);
-  }
-
-  private void assertThatMeasureHasValue(String componentUuid, Metric metric, int expectedValue) {
-    Optional<LiveMeasureDto> persisted = dbClient.liveMeasureDao().selectMeasure(db.getSession(),
-      componentUuid, metric.getKey());
-    assertThat(persisted).isPresent();
-    assertThat(persisted.get().getValue()).isEqualTo(expectedValue);
-  }
-
-  private void assertThatMeasureDoesNotExist(LiveMeasureDto template) {
-    assertThat(dbClient.liveMeasureDao().selectMeasure(db.getSession(),
-      template.getComponentUuid(), metricRepository.getByUuid(template.getMetricUuid()).getKey()))
-      .isEmpty();
-  }
-
-  private void prepareProject() {
-    // tree of components as defined by scanner report
-    Component project = ReportComponent.builder(PROJECT, REF_1).setUuid("project-uuid")
-      .addChildren(
-        ReportComponent.builder(DIRECTORY, REF_3).setUuid("dir-uuid")
-          .addChildren(
-            ReportComponent.builder(FILE, REF_4).setUuid("file-uuid")
-              .build())
-          .build())
-      .build();
-    treeRootHolder.setRoot(project);
-    analysisMetadataHolder.setProject(new Project(project.getUuid(), project.getKey(), project.getName(), project.getDescription(), emptyList()));
-
-    // components as persisted in db
-    ComponentDto projectDto = insertComponent("project-key", "project-uuid");
-    ComponentDto dirDto = insertComponent("dir-key", "dir-uuid");
-    ComponentDto fileDto = insertComponent("file-key", "file-uuid");
-  }
-
-  private void preparePortfolio() {
-    // tree of components
-    Component portfolio = ViewsComponent.builder(VIEW, REF_1).setUuid("view-uuid")
-      .addChildren(
-        ViewsComponent.builder(SUBVIEW, REF_2).setUuid("subview-uuid")
-          .addChildren(
-            ViewsComponent.builder(PROJECT_VIEW, REF_3).setUuid("project-uuid")
-              .build())
-          .build())
-      .build();
-    treeRootHolder.setRoot(portfolio);
-
-    // components as persisted in db
-    ComponentDto portfolioDto = insertComponent("view-key", "view-uuid");
-    ComponentDto subViewDto = insertComponent("subview-key", "subview-uuid");
-    ComponentDto projectDto = insertComponent("project-key", "project-uuid");
-    analysisMetadataHolder.setProject(Project.from(portfolioDto));
-  }
-
-  private void assertThatMeasureIsNotPersisted(String componentUuid, Metric metric) {
-    assertThat(selectMeasure(componentUuid, metric)).isEmpty();
-  }
-
-  private Optional<LiveMeasureDto> selectMeasure(String componentUuid, Metric metric) {
-    return dbClient.liveMeasureDao().selectMeasure(db.getSession(), componentUuid, metric.getKey());
-  }
-
-  private ComponentDto insertComponent(String key, String uuid) {
-    ComponentDto componentDto = new ComponentDto()
-      .setKey(key)
-      .setUuid(uuid)
-      .setUuidPath(uuid + ".")
-      .setBranchUuid(uuid);
-    db.components().insertComponent(componentDto);
-    return componentDto;
-  }
-
-  @Override
-  protected ComputationStep step() {
-    return new PersistLiveMeasuresStep(dbClient, metricRepository, new MeasureToMeasureDto(analysisMetadataHolder, treeRootHolder), treeRootHolder, measureRepository,
-      Optional.of(fileStatuses));
-  }
-
-  private static void verifyStatistics(TestComputationStepContext context, int expectedInsertsOrUpdates) {
-    context.getStatistics().assertValue("insertsOrUpdates", expectedInsertsOrUpdates);
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistMeasuresStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistMeasuresStepTest.java
deleted file mode 100644 (file)
index f1db842..0000000
+++ /dev/null
@@ -1,241 +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.Optional;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.measures.Metric;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.projectanalysis.analysis.MutableAnalysisMetadataHolderRule;
-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.component.ViewsComponent;
-import org.sonar.ce.task.projectanalysis.measure.MeasureRepositoryRule;
-import org.sonar.ce.task.projectanalysis.measure.MeasureToMeasureDto;
-import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule;
-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.db.component.ComponentDto;
-import org.sonar.db.measure.MeasureDto;
-import org.sonar.db.metric.MetricDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-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.Component.Type.PROJECT_VIEW;
-import static org.sonar.ce.task.projectanalysis.component.Component.Type.SUBVIEW;
-import static org.sonar.ce.task.projectanalysis.component.Component.Type.VIEW;
-import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilder;
-
-public class PersistMeasuresStepTest extends BaseStepTest {
-
-  private static final Metric STRING_METRIC = new Metric.Builder("string-metric", "String metric", Metric.ValueType.STRING).create();
-  private static final Metric INT_METRIC = new Metric.Builder("int-metric", "int metric", Metric.ValueType.INT).create();
-  private static final Metric NON_HISTORICAL_METRIC = new Metric.Builder("nh-metric", "nh metric", Metric.ValueType.INT).setDeleteHistoricalData(true).create();
-
-  private static final String ANALYSIS_UUID = "a1";
-
-  private static final int REF_1 = 1;
-  private static final int REF_2 = 2;
-  private static final int REF_3 = 3;
-  private static final int REF_4 = 4;
-
-  @Rule
-  public DbTester db = DbTester.create(System2.INSTANCE);
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-  @Rule
-  public MetricRepositoryRule metricRepository = new MetricRepositoryRule();
-  @Rule
-  public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
-  @Rule
-  public MutableAnalysisMetadataHolderRule analysisMetadataHolder = new MutableAnalysisMetadataHolderRule();
-
-  private DbClient dbClient = db.getDbClient();
-
-  @Before
-  public void setUp() {
-    analysisMetadataHolder.setUuid(ANALYSIS_UUID);
-    MetricDto stringMetricDto = db.measures().insertMetric(m -> m.setKey(STRING_METRIC.getKey()).setValueType(Metric.ValueType.STRING.name()));
-    MetricDto intMetricDto = db.measures().insertMetric(m -> m.setKey(INT_METRIC.getKey()).setValueType(Metric.ValueType.INT.name()));
-    MetricDto nhMetricDto = db.measures().insertMetric(m -> m.setKey(NON_HISTORICAL_METRIC.getKey()).setValueType(Metric.ValueType.INT.name()));
-    metricRepository.add(stringMetricDto.getUuid(), STRING_METRIC);
-    metricRepository.add(intMetricDto.getUuid(), INT_METRIC);
-    metricRepository.add(nhMetricDto.getUuid(), NON_HISTORICAL_METRIC);
-  }
-
-  @Test
-  public void measures_on_non_historical_metrics_are_not_persisted() {
-    prepareProject();
-    measureRepository.addRawMeasure(REF_1, NON_HISTORICAL_METRIC.getKey(), newMeasureBuilder().create(1));
-    measureRepository.addRawMeasure(REF_1, INT_METRIC.getKey(), newMeasureBuilder().create(2));
-
-    TestComputationStepContext context = execute();
-
-    assertThatMeasureIsNotPersisted("project-uuid", NON_HISTORICAL_METRIC);
-    MeasureDto persistedMeasure = selectMeasure("project-uuid", INT_METRIC).get();
-    assertThat(persistedMeasure.getValue()).isEqualTo(2);
-    assertNbOfInserts(context, 1);
-  }
-
-  @Test
-  public void persist_measures_of_project_analysis_excluding_directories() {
-    prepareProject();
-
-    // the computed measures
-    measureRepository.addRawMeasure(REF_1, STRING_METRIC.getKey(), newMeasureBuilder().create("project-value"));
-    measureRepository.addRawMeasure(REF_3, STRING_METRIC.getKey(), newMeasureBuilder().create("dir-value"));
-    measureRepository.addRawMeasure(REF_4, STRING_METRIC.getKey(), newMeasureBuilder().create("file-value"));
-
-    TestComputationStepContext context = execute();
-
-    // project and dir measures are persisted, but not file measures
-    assertThat(db.countRowsOfTable("project_measures")).isOne();
-    assertThat(selectMeasure("project-uuid", STRING_METRIC).get().getData()).isEqualTo("project-value");
-    assertThatMeasuresAreNotPersisted("dir-uuid");
-    assertThatMeasuresAreNotPersisted("file-uuid");
-    assertNbOfInserts(context, 1);
-  }
-
-  @Test
-  public void measures_without_value_are_not_persisted() {
-    prepareProject();
-    measureRepository.addRawMeasure(REF_1, STRING_METRIC.getKey(), newMeasureBuilder().createNoValue());
-    measureRepository.addRawMeasure(REF_1, INT_METRIC.getKey(), newMeasureBuilder().createNoValue());
-
-    TestComputationStepContext context = execute();
-
-    assertThatMeasureIsNotPersisted("project-uuid", STRING_METRIC);
-    assertThatMeasureIsNotPersisted("project-uuid", INT_METRIC);
-    assertNbOfInserts(context, 0);
-  }
-
-  @Test
-  public void measures_on_new_code_period_are_persisted() {
-    prepareProject();
-    measureRepository.addRawMeasure(REF_1, INT_METRIC.getKey(), newMeasureBuilder().create(42.0));
-
-    TestComputationStepContext context = execute();
-
-    MeasureDto persistedMeasure = selectMeasure("project-uuid", INT_METRIC).get();
-    assertThat(persistedMeasure.getValue()).isEqualTo(42.0);
-    assertNbOfInserts(context, 1);
-  }
-
-  @Test
-  public void persist_all_measures_of_portfolio_analysis() {
-    preparePortfolio();
-
-    // the computed measures
-    measureRepository.addRawMeasure(REF_1, STRING_METRIC.getKey(), newMeasureBuilder().create("view-value"));
-    measureRepository.addRawMeasure(REF_2, STRING_METRIC.getKey(), newMeasureBuilder().create("subview-value"));
-    measureRepository.addRawMeasure(REF_3, STRING_METRIC.getKey(), newMeasureBuilder().create("project-value"));
-
-    TestComputationStepContext context = execute();
-
-    assertThat(db.countRowsOfTable("project_measures")).isEqualTo(2);
-    assertThat(selectMeasure("view-uuid", STRING_METRIC).get().getData()).isEqualTo("view-value");
-    assertThat(selectMeasure("subview-uuid", STRING_METRIC).get().getData()).isEqualTo("subview-value");
-    assertNbOfInserts(context, 2);
-  }
-
-  private void prepareProject() {
-    // tree of components as defined by scanner report
-    Component project = ReportComponent.builder(PROJECT, REF_1).setUuid("project-uuid")
-      .addChildren(
-        ReportComponent.builder(DIRECTORY, REF_3).setUuid("dir-uuid")
-          .addChildren(
-            ReportComponent.builder(FILE, REF_4).setUuid("file-uuid")
-              .build())
-          .build())
-      .build();
-    treeRootHolder.setRoot(project);
-
-    // components as persisted in db
-    ComponentDto projectDto = insertComponent("project-key", "project-uuid");
-    ComponentDto dirDto = insertComponent("dir-key", "dir-uuid");
-    ComponentDto fileDto = insertComponent("file-key", "file-uuid");
-    db.components().insertSnapshot(projectDto, s -> s.setUuid(ANALYSIS_UUID));
-  }
-
-  private void preparePortfolio() {
-    // tree of components
-    Component portfolio = ViewsComponent.builder(VIEW, REF_1).setUuid("view-uuid")
-      .addChildren(
-        ViewsComponent.builder(SUBVIEW, REF_2).setUuid("subview-uuid")
-          .addChildren(
-            ViewsComponent.builder(PROJECT_VIEW, REF_3).setUuid("project-uuid")
-              .build())
-          .build())
-      .build();
-    treeRootHolder.setRoot(portfolio);
-
-    // components as persisted in db
-    ComponentDto viewDto = insertComponent("view-key", "view-uuid");
-    ComponentDto subViewDto = insertComponent("subview-key", "subview-uuid");
-    ComponentDto projectDto = insertComponent("project-key", "project-uuid");
-    db.components().insertSnapshot(viewDto, s -> s.setUuid(ANALYSIS_UUID));
-  }
-
-  private void assertThatMeasureIsNotPersisted(String componentUuid, Metric metric) {
-    assertThat(selectMeasure(componentUuid, metric)).isEmpty();
-  }
-
-  private void assertThatMeasuresAreNotPersisted(String componentUuid) {
-    assertThatMeasureIsNotPersisted(componentUuid, STRING_METRIC);
-    assertThatMeasureIsNotPersisted(componentUuid, INT_METRIC);
-  }
-
-  private TestComputationStepContext execute() {
-    TestComputationStepContext context = new TestComputationStepContext();
-    new PersistMeasuresStep(dbClient, metricRepository, new MeasureToMeasureDto(analysisMetadataHolder, treeRootHolder), treeRootHolder, measureRepository)
-      .execute(context);
-    return context;
-  }
-
-  private Optional<MeasureDto> selectMeasure(String componentUuid, Metric metric) {
-    return dbClient.measureDao().selectMeasure(db.getSession(), ANALYSIS_UUID, componentUuid, metric.getKey());
-  }
-
-  private ComponentDto insertComponent(String key, String uuid) {
-    ComponentDto componentDto = new ComponentDto()
-      .setKey(key)
-      .setUuid(uuid)
-      .setUuidPath(uuid + ".")
-      .setBranchUuid(uuid);
-    db.components().insertComponent(componentDto);
-    return componentDto;
-  }
-
-  private static void assertNbOfInserts(TestComputationStepContext context, int expected) {
-    context.getStatistics().assertValue("inserts", expected);
-  }
-
-  @Override
-  protected ComputationStep step() {
-    return new PersistMeasuresStep(dbClient, metricRepository, new MeasureToMeasureDto(analysisMetadataHolder, treeRootHolder), treeRootHolder, measureRepository);
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistProjectLinksStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistProjectLinksStepTest.java
deleted file mode 100644 (file)
index f487969..0000000
+++ /dev/null
@@ -1,258 +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 org.junit.Rule;
-import org.junit.Test;
-import org.mockito.Mockito;
-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.ce.task.projectanalysis.batch.BatchReportReader;
-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.TreeRootHolder;
-import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
-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.UuidFactoryFast;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ProjectLinkDto;
-import org.sonar.scanner.protocol.output.ScannerReport;
-import org.sonar.scanner.protocol.output.ScannerReport.Component.ComponentType;
-
-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.verifyNoInteractions;
-import static org.mockito.Mockito.when;
-import static org.sonar.scanner.protocol.output.ScannerReport.ComponentLink.ComponentLinkType.CI;
-import static org.sonar.scanner.protocol.output.ScannerReport.ComponentLink.ComponentLinkType.HOME;
-import static org.sonar.scanner.protocol.output.ScannerReport.ComponentLink.ComponentLinkType.ISSUE;
-import static org.sonar.scanner.protocol.output.ScannerReport.ComponentLink.ComponentLinkType.SCM;
-
-public class PersistProjectLinksStepTest extends BaseStepTest {
-
-  @Rule
-  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
-  @Rule
-  public DbTester db = DbTester.create(System2.INSTANCE);
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-  @Rule
-  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
-
-  private PersistProjectLinksStep underTest = new PersistProjectLinksStep(analysisMetadataHolder, db.getDbClient(), treeRootHolder, reportReader, UuidFactoryFast.getInstance());
-
-  @Override
-  protected ComputationStep step() {
-    return underTest;
-  }
-
-  @Test
-  public void no_effect_if_branch_is_not_main() {
-    DbClient dbClient = mock(DbClient.class);
-    TreeRootHolder treeRootHolder = mock(TreeRootHolder.class);
-    BatchReportReader reportReader = mock(BatchReportReader.class);
-    UuidFactory uuidFactory = mock(UuidFactory.class);
-    mockBranch(false);
-    PersistProjectLinksStep underTest = new PersistProjectLinksStep(analysisMetadataHolder, dbClient, treeRootHolder, reportReader, uuidFactory);
-
-    underTest.execute(new TestComputationStepContext());
-
-    verifyNoInteractions(uuidFactory, reportReader, treeRootHolder, dbClient);
-  }
-
-  @Test
-  public void add_links_on_project() {
-    mockBranch(true);
-    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").build());
-
-    // project
-    reportReader.putComponent(ScannerReport.Component.newBuilder()
-      .setRef(1)
-      .setType(ComponentType.PROJECT)
-      .addChildRef(2)
-      .addLink(ScannerReport.ComponentLink.newBuilder().setType(HOME).setHref("http://www.sonarqube.org").build())
-      .addLink(ScannerReport.ComponentLink.newBuilder().setType(SCM).setHref("https://github.com/SonarSource/sonar").build())
-      .addLink(ScannerReport.ComponentLink.newBuilder().setType(ISSUE).setHref("http://jira.sonarsource.com/").build())
-      .addLink(ScannerReport.ComponentLink.newBuilder().setType(CI).setHref("http://bamboo.ci.codehaus.org/browse/SONAR").build())
-      .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(db.getDbClient().projectLinkDao().selectByProjectUuid(db.getSession(), "ABCD"))
-      .extracting(ProjectLinkDto::getType, ProjectLinkDto::getHref, ProjectLinkDto::getName)
-      .containsExactlyInAnyOrder(
-        tuple("homepage", "http://www.sonarqube.org", null),
-        tuple("scm", "https://github.com/SonarSource/sonar", null),
-        tuple("issue", "http://jira.sonarsource.com/", null),
-        tuple("ci", "http://bamboo.ci.codehaus.org/browse/SONAR", null));
-  }
-
-  @Test
-  public void nothing_to_do_when_link_already_exists() {
-    mockBranch(true);
-    ComponentDto project = db.components().insertPrivateProject(p -> p.setUuid("ABCD"));
-    db.componentLinks().insertProvidedLink(project, l -> l.setType("homepage").setName("Home").setHref("http://www.sonarqube.org"));
-
-    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").build());
-
-    reportReader.putComponent(ScannerReport.Component.newBuilder()
-      .setRef(1)
-      .setType(ComponentType.PROJECT)
-      .addLink(ScannerReport.ComponentLink.newBuilder().setType(HOME).setHref("http://www.sonarqube.org").build())
-      .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(db.getDbClient().projectLinkDao().selectByProjectUuid(db.getSession(), "ABCD"))
-      .extracting(ProjectLinkDto::getType, ProjectLinkDto::getHref)
-      .containsExactlyInAnyOrder(tuple("homepage", "http://www.sonarqube.org"));
-  }
-
-  @Test
-  public void do_not_add_links_on_module() {
-    mockBranch(true);
-    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").build());
-    reportReader.putComponent(ScannerReport.Component.newBuilder()
-      .setRef(1)
-      .setType(ComponentType.PROJECT)
-      .addChildRef(2)
-      .build());
-    reportReader.putComponent(ScannerReport.Component.newBuilder()
-      .setRef(2)
-      .setType(ComponentType.MODULE)
-      .addLink(ScannerReport.ComponentLink.newBuilder().setType(HOME).setHref("http://www.sonarqube.org").build())
-      .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(db.countRowsOfTable("project_links")).isZero();
-  }
-
-  @Test
-  public void do_not_add_links_on_file() {
-    mockBranch(true);
-    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").addChildren(
-      ReportComponent.builder(Component.Type.FILE, 2).setUuid("BCDE").build())
-      .build());
-
-    reportReader.putComponent(ScannerReport.Component.newBuilder()
-      .setRef(1)
-      .setType(ComponentType.PROJECT)
-      .addChildRef(2)
-      .build());
-    reportReader.putComponent(ScannerReport.Component.newBuilder()
-      .setRef(2)
-      .setType(ComponentType.FILE)
-      .addLink(ScannerReport.ComponentLink.newBuilder().setType(HOME).setHref("http://www.sonarqube.org").build())
-      .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(db.countRowsOfTable("project_links")).isZero();
-  }
-
-  @Test
-  public void update_link() {
-    mockBranch(true);
-    ComponentDto project = db.components().insertPrivateProject(p -> p.setUuid("ABCD"));
-    db.componentLinks().insertProvidedLink(project, l -> l.setType("homepage").setName("Home").setHref("http://www.sonar.org"));
-
-    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").build());
-
-    reportReader.putComponent(ScannerReport.Component.newBuilder()
-      .setRef(1)
-      .setType(ComponentType.PROJECT)
-      .addLink(ScannerReport.ComponentLink.newBuilder().setType(HOME).setHref("http://www.sonarqube.org").build())
-      .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(db.getDbClient().projectLinkDao().selectByProjectUuid(db.getSession(), "ABCD"))
-      .extracting(ProjectLinkDto::getType, ProjectLinkDto::getHref)
-      .containsExactlyInAnyOrder(tuple("homepage", "http://www.sonarqube.org"));
-  }
-
-  @Test
-  public void delete_link() {
-    mockBranch(true);
-    ComponentDto project = db.components().insertPrivateProject(p -> p.setUuid("ABCD"));
-    db.componentLinks().insertProvidedLink(project, l -> l.setType("homepage").setName("Home").setHref("http://www.sonar.org"));
-
-    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").build());
-
-    reportReader.putComponent(ScannerReport.Component.newBuilder()
-      .setRef(1)
-      .setType(ComponentType.PROJECT)
-      .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(db.countRowsOfTable("project_links")).isZero();
-  }
-
-  @Test
-  public void not_delete_custom_link() {
-    mockBranch(true);
-    ComponentDto project = db.components().insertPrivateProject(p -> p.setUuid("ABCD"));
-    db.componentLinks().insertCustomLink(project);
-
-    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").build());
-
-    reportReader.putComponent(ScannerReport.Component.newBuilder()
-      .setRef(1)
-      .setType(ComponentType.PROJECT)
-      .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(db.countRowsOfTable("project_links")).isOne();
-  }
-
-  @Test
-  public void fail_when_trying_to_add_same_link_type_multiple_times() {
-    mockBranch(true);
-    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").build());
-
-    reportReader.putComponent(ScannerReport.Component.newBuilder()
-      .setRef(1)
-      .setType(ComponentType.PROJECT)
-      .addLink(ScannerReport.ComponentLink.newBuilder().setType(HOME).setHref("http://www.sonarqube.org").build())
-      .addLink(ScannerReport.ComponentLink.newBuilder().setType(HOME).setHref("http://www.sonarqube.org").build())
-      .build());
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(IllegalArgumentException.class)
-      .hasMessage("Link of type 'homepage' has already been declared on component 'ABCD'");
-  }
-
-  private void mockBranch(boolean isMain) {
-    Branch branch = Mockito.mock(Branch.class);
-    when(branch.isMain()).thenReturn(isMain);
-    analysisMetadataHolder.setBranch(branch);
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistPushEventsStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistPushEventsStepTest.java
deleted file mode 100644 (file)
index ff91809..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.step;
-
-import java.io.IOException;
-import java.util.Date;
-import java.util.Optional;
-import java.util.stream.IntStream;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.api.impl.utils.TestSystem2;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rules.RuleType;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.component.MutableTreeRootHolderRule;
-import org.sonar.ce.task.projectanalysis.component.ReportComponent;
-import org.sonar.ce.task.projectanalysis.issue.ProtoIssueCache;
-import org.sonar.ce.task.projectanalysis.pushevent.PushEventFactory;
-import org.sonar.ce.task.step.ComputationStep;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.db.DbTester;
-import org.sonar.db.pushevent.PushEventDto;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatCode;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class PersistPushEventsStepTest {
-
-  private final TestSystem2 system2 = new TestSystem2().setNow(1L);
-
-  @Rule
-  public DbTester db = DbTester.create(system2);
-
-  public final PushEventFactory pushEventFactory = mock(PushEventFactory.class);
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-  @Rule
-  public MutableTreeRootHolderRule treeRootHolder = new MutableTreeRootHolderRule();
-  private ProtoIssueCache protoIssueCache;
-  private PersistPushEventsStep underTest;
-
-  @Before
-  public void before() throws IOException {
-    protoIssueCache = new ProtoIssueCache(temp.newFile(), System2.INSTANCE);
-    buildComponentTree();
-    underTest = new PersistPushEventsStep(db.getDbClient(), protoIssueCache, pushEventFactory, treeRootHolder);
-  }
-
-  @Test
-  public void description() {
-    assertThat(underTest.getDescription()).isEqualTo("Publishing taint vulnerabilities events");
-  }
-
-  @Test
-  public void do_nothing_if_no_issues() {
-    underTest.execute(mock(ComputationStep.Context.class));
-
-    assertThat(db.countSql(db.getSession(), "SELECT count(uuid) FROM push_events")).isZero();
-  }
-
-  @Test
-  public void resilient_to_failure() {
-    protoIssueCache.newAppender().append(
-      createIssue("key1").setType(RuleType.VULNERABILITY))
-      .close();
-
-    when(pushEventFactory.raiseEventOnIssue(any(), any())).thenThrow(new RuntimeException("I have a bad feelings about this"));
-
-    assertThatCode(() -> underTest.execute(mock(ComputationStep.Context.class)))
-      .doesNotThrowAnyException();
-
-    assertThat(db.countSql(db.getSession(), "SELECT count(uuid) FROM push_events")).isZero();
-  }
-
-  @Test
-  public void skip_persist_if_no_push_events() {
-    protoIssueCache.newAppender().append(
-      createIssue("key1").setType(RuleType.VULNERABILITY))
-      .close();
-
-    underTest.execute(mock(ComputationStep.Context.class));
-
-    assertThat(db.countSql(db.getSession(), "SELECT count(uuid) FROM push_events")).isZero();
-  }
-
-  @Test
-  public void do_nothing_if_issue_does_not_have_component() {
-    protoIssueCache.newAppender().append(
-      createIssue("key1").setType(RuleType.VULNERABILITY)
-        .setComponentUuid(null))
-      .close();
-
-    underTest.execute(mock(ComputationStep.Context.class));
-
-    assertThat(db.countSql(db.getSession(), "SELECT count(uuid) FROM push_events")).isZero();
-  }
-
-  @Test
-  public void store_push_events() {
-    protoIssueCache.newAppender()
-      .append(createIssue("key1").setType(RuleType.VULNERABILITY)
-        .setComponentUuid("cu1")
-        .setComponentKey("ck1"))
-      .append(createIssue("key2").setType(RuleType.VULNERABILITY)
-        .setComponentUuid("cu2")
-        .setComponentKey("ck2"))
-      .close();
-
-    when(pushEventFactory.raiseEventOnIssue(eq("uuid_1"), any(DefaultIssue.class))).thenReturn(
-      Optional.of(createPushEvent()),
-      Optional.of(createPushEvent()));
-
-    underTest.execute(mock(ComputationStep.Context.class));
-
-    assertThat(db.countSql(db.getSession(), "SELECT count(uuid) FROM push_events")).isEqualTo(2);
-  }
-
-  @Test
-  public void store_push_events_for_branch() {
-    var project = db.components().insertPrivateProject();
-    db.components().insertProjectBranch(project, b -> b.setUuid("uuid_1"));
-
-    protoIssueCache.newAppender()
-      .append(createIssue("key1").setType(RuleType.VULNERABILITY)
-        .setComponentUuid("cu1")
-        .setComponentKey("ck1"))
-      .append(createIssue("key2").setType(RuleType.VULNERABILITY)
-        .setComponentUuid("cu2")
-        .setComponentKey("ck2"))
-      .close();
-
-    when(pushEventFactory.raiseEventOnIssue(eq(project.uuid()), any(DefaultIssue.class))).thenReturn(
-      Optional.of(createPushEvent()),
-      Optional.of(createPushEvent()));
-
-    underTest.execute(mock(ComputationStep.Context.class));
-
-    assertThat(db.countSql(db.getSession(), "SELECT count(uuid) FROM push_events")).isEqualTo(2);
-  }
-
-  @Test
-  public void store_push_events_in_batches() {
-    var appender = protoIssueCache.newAppender();
-
-    IntStream.range(1, 252)
-      .forEach(value -> {
-        var defaultIssue = createIssue("key-" + value).setType(RuleType.VULNERABILITY)
-          .setComponentUuid("cu" + value)
-          .setComponentKey("ck" + value);
-        appender.append(defaultIssue);
-        when(pushEventFactory.raiseEventOnIssue(anyString(), eq(defaultIssue))).thenReturn(Optional.of(createPushEvent()));
-      });
-
-    appender.close();
-
-    underTest.execute(mock(ComputationStep.Context.class));
-
-    assertThat(db.countSql(db.getSession(), "SELECT count(uuid) FROM push_events")).isEqualTo(251);
-  }
-
-  private DefaultIssue createIssue(String key) {
-    return new DefaultIssue()
-      .setKey(key)
-      .setProjectKey("p")
-      .setStatus("OPEN")
-      .setProjectUuid("project-uuid")
-      .setComponentKey("c")
-      .setRuleKey(RuleKey.of("r", "r"))
-      .setCreationDate(new Date());
-  }
-
-  private PushEventDto createPushEvent() {
-    return new PushEventDto().setProjectUuid("project-uuid").setName("event").setPayload("test".getBytes(UTF_8));
-  }
-
-  private void buildComponentTree() {
-    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1)
-      .setUuid("uuid_1")
-      .addChildren(ReportComponent.builder(Component.Type.FILE, 2)
-        .setUuid("issue-component-uuid")
-        .build())
-      .addChildren(ReportComponent.builder(Component.Type.FILE, 3)
-        .setUuid("location-component-uuid")
-        .build())
-      .build());
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistScannerAnalysisCacheStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistScannerAnalysisCacheStepTest.java
deleted file mode 100644 (file)
index b2540b8..0000000
+++ /dev/null
@@ -1,75 +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.io.ByteArrayInputStream;
-import java.io.IOException;
-import org.apache.commons.io.IOUtils;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
-import org.sonar.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
-import org.sonar.ce.task.step.ComputationStep;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbInputStream;
-import org.sonar.db.DbTester;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class PersistScannerAnalysisCacheStepTest {
-  @Rule
-  public BatchReportReaderRule reader = new BatchReportReaderRule();
-  @Rule
-  public DbTester dbTester = DbTester.create();
-  private final DbClient client = dbTester.getDbClient();
-  private final TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-  private final PersistScannerAnalysisCacheStep step = new PersistScannerAnalysisCacheStep(reader, dbTester.getDbClient(), treeRootHolder);
-
-  @Test
-  public void inserts_cache() throws IOException {
-    reader.setAnalysisCache("test".getBytes(UTF_8));
-
-    Component root = mock(Component.class);
-    when(root.getUuid()).thenReturn("branch");
-    treeRootHolder.setRoot(root);
-
-    step.execute(mock(ComputationStep.Context.class));
-    assertThat(dbTester.countRowsOfTable("scanner_analysis_cache")).isOne();
-    try (DbInputStream data = client.scannerAnalysisCacheDao().selectData(dbTester.getSession(), "branch")) {
-      assertThat(IOUtils.toString(data, UTF_8)).isEqualTo("test");
-    }
-  }
-
-  @Test
-  public void updates_cache() throws IOException {
-    client.scannerAnalysisCacheDao().insert(dbTester.getSession(), "branch", new ByteArrayInputStream("test".getBytes(UTF_8)));
-    inserts_cache();
-  }
-
-  @Test
-  public void do_nothing_if_no_analysis_cache() {
-    step.execute(mock(ComputationStep.Context.class));
-    assertThat(dbTester.countRowsOfTable("scanner_analysis_cache")).isZero();
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistScannerContextStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistScannerContextStepTest.java
deleted file mode 100644 (file)
index 4143d0d..0000000
+++ /dev/null
@@ -1,99 +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 org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.CeTask;
-import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
-import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
-import org.sonar.ce.task.step.TestComputationStepContext;
-import org.sonar.core.util.CloseableIterator;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptyList;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class PersistScannerContextStepTest {
-  private static final String ANALYSIS_UUID = "UUID";
-
-  @ClassRule
-  public static final DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  @Rule
-  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
-  @Rule
-  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule()
-    .setUuid(ANALYSIS_UUID);
-
-  private DbClient dbClient = dbTester.getDbClient();
-  private CeTask ceTask = mock(CeTask.class);
-  private PersistScannerContextStep underTest = new PersistScannerContextStep(reportReader, dbClient, ceTask);
-
-  @Test
-  public void getDescription() {
-    assertThat(underTest.getDescription()).isEqualTo("Persist scanner context");
-  }
-
-  @Test
-  public void executes_persist_lines_of_reportReader() {
-    String taskUuid = "task uuid";
-    when(ceTask.getUuid()).thenReturn(taskUuid);
-    reportReader.setScannerLogs(asList("log1", "log2"));
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbClient.ceScannerContextDao().selectScannerContext(dbTester.getSession(), taskUuid))
-      .contains("log1" + '\n' + "log2");
-  }
-
-  @Test
-  public void executes_persist_does_not_persist_any_scanner_context_if_iterator_is_empty() {
-    reportReader.setScannerLogs(emptyList());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbClient.ceScannerContextDao().selectScannerContext(dbTester.getSession(), ANALYSIS_UUID))
-      .isEmpty();
-  }
-
-  /**
-   * SONAR-8306
-   */
-  @Test
-  public void execute_does_not_fail_if_scanner_context_has_already_been_persisted() {
-    dbClient.ceScannerContextDao().insert(dbTester.getSession(), ANALYSIS_UUID, CloseableIterator.from(Arrays.asList("a", "b", "c").iterator()));
-    dbTester.commit();
-    reportReader.setScannerLogs(asList("1", "2", "3"));
-    when(ceTask.getUuid()).thenReturn(ANALYSIS_UUID);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbClient.ceScannerContextDao().selectScannerContext(dbTester.getSession(), ANALYSIS_UUID))
-      .contains("1" + '\n' + "2" + '\n' + "3");
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ReportPersistAnalysisStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ReportPersistAnalysisStepTest.java
deleted file mode 100644 (file)
index 188dbb5..0000000
+++ /dev/null
@@ -1,207 +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.List;
-import java.util.Optional;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.DateUtils;
-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.period.Period;
-import org.sonar.ce.task.projectanalysis.period.PeriodHolderRule;
-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.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.component.SnapshotDto;
-import org.sonar.db.component.SnapshotQuery;
-import org.sonar.db.component.SnapshotTesting;
-
-import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class ReportPersistAnalysisStepTest extends BaseStepTest {
-
-  private static final String PROJECT_KEY = "PROJECT_KEY";
-  private static final String ANALYSIS_UUID = "U1";
-  private static final String REVISION_ID = "5f6432a1";
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-  @Rule
-  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
-  @Rule
-  public PeriodHolderRule periodsHolder = new PeriodHolderRule();
-
-  private System2 system2 = mock(System2.class);
-  private DbClient dbClient = dbTester.getDbClient();
-  private long analysisDate;
-  private long now;
-  private PersistAnalysisStep underTest;
-
-  @Before
-  public void setup() {
-    analysisDate = DateUtils.parseDateQuietly("2015-06-01").getTime();
-    analysisMetadataHolder.setUuid(ANALYSIS_UUID);
-    analysisMetadataHolder.setAnalysisDate(analysisDate);
-    analysisMetadataHolder.setScmRevision(REVISION_ID);
-
-    now = DateUtils.parseDateQuietly("2015-06-02").getTime();
-
-    when(system2.now()).thenReturn(now);
-
-    underTest = new PersistAnalysisStep(system2, dbClient, treeRootHolder, analysisMetadataHolder, periodsHolder);
-
-    // initialize PeriodHolder to empty by default
-    periodsHolder.setPeriod(null);
-  }
-
-  @Override
-  protected ComputationStep step() {
-    return underTest;
-  }
-
-  @Test
-  public void persist_analysis() {
-    String projectVersion = randomAlphabetic(10);
-    ComponentDto projectDto = ComponentTesting.newPrivateProjectDto("ABCD").setKey(PROJECT_KEY).setName("Project");
-    dbTester.components().insertComponent(projectDto);
-    ComponentDto directoryDto = ComponentTesting.newDirectory(projectDto, "CDEF", "src/main/java/dir").setKey("PROJECT_KEY:src/main/java/dir");
-    dbTester.components().insertComponent(directoryDto);
-    ComponentDto fileDto = ComponentTesting.newFileDto(projectDto, directoryDto, "DEFG").setKey("PROJECT_KEY:src/main/java/dir/Foo.java");
-    dbTester.components().insertComponent(fileDto);
-    dbTester.getSession().commit();
-
-    Component file = ReportComponent.builder(Component.Type.FILE, 3).setUuid("DEFG").setKey("PROJECT_KEY:src/main/java/dir/Foo.java").build();
-    Component directory = ReportComponent.builder(Component.Type.DIRECTORY, 2).setUuid("CDEF").setKey("PROJECT_KEY:src/main/java/dir").addChildren(file).build();
-    String buildString = Optional.ofNullable(projectVersion).map(v -> randomAlphabetic(7)).orElse(null);
-    Component project = ReportComponent.builder(Component.Type.PROJECT, 1)
-      .setUuid("ABCD")
-      .setKey(PROJECT_KEY)
-      .setProjectVersion(projectVersion)
-      .setBuildString(buildString)
-      .setScmRevisionId(REVISION_ID)
-      .addChildren(directory)
-      .build();
-    treeRootHolder.setRoot(project);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbTester.countRowsOfTable("snapshots")).isOne();
-
-    SnapshotDto projectSnapshot = getUnprocessedSnapshot(projectDto.uuid());
-    assertThat(projectSnapshot.getUuid()).isEqualTo(ANALYSIS_UUID);
-    assertThat(projectSnapshot.getComponentUuid()).isEqualTo(project.getUuid());
-    assertThat(projectSnapshot.getProjectVersion()).isEqualTo(projectVersion);
-    assertThat(projectSnapshot.getBuildString()).isEqualTo(buildString);
-    assertThat(projectSnapshot.getLast()).isFalse();
-    assertThat(projectSnapshot.getStatus()).isEqualTo("U");
-    assertThat(projectSnapshot.getCreatedAt()).isEqualTo(analysisDate);
-    assertThat(projectSnapshot.getBuildDate()).isEqualTo(now);
-    assertThat(projectSnapshot.getRevision()).isEqualTo(REVISION_ID);
-  }
-
-  @Test
-  public void persist_snapshots_with_new_code_period() {
-    ComponentDto projectDto = ComponentTesting.newPrivateProjectDto("ABCD").setKey(PROJECT_KEY).setName("Project");
-    dbTester.components().insertComponent(projectDto);
-    SnapshotDto snapshotDto = SnapshotTesting.newAnalysis(projectDto).setCreatedAt(DateUtils.parseDateQuietly("2015-01-01").getTime());
-    dbClient.snapshotDao().insert(dbTester.getSession(), snapshotDto);
-    dbTester.getSession().commit();
-    periodsHolder.setPeriod(new Period("NUMBER_OF_DAYS", "10", analysisDate));
-
-    Component project = ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").setKey(PROJECT_KEY).build();
-    treeRootHolder.setRoot(project);
-
-    underTest.execute(new TestComputationStepContext());
-
-    SnapshotDto projectSnapshot = getUnprocessedSnapshot(projectDto.uuid());
-    assertThat(projectSnapshot.getPeriodMode()).isEqualTo("NUMBER_OF_DAYS");
-    assertThat(projectSnapshot.getPeriodDate()).isEqualTo(analysisDate);
-    assertThat(projectSnapshot.getPeriodModeParameter()).isNotNull();
-  }
-
-  @Test
-  public void only_persist_snapshots_with_new_code_period_on_project_and_module() {
-    periodsHolder.setPeriod(new Period("PREVIOUS_VERSION", null, analysisDate));
-
-    ComponentDto projectDto = ComponentTesting.newPrivateProjectDto("ABCD").setKey(PROJECT_KEY).setName("Project");
-    dbTester.components().insertComponent(projectDto);
-    SnapshotDto projectSnapshot = SnapshotTesting.newAnalysis(projectDto);
-    dbClient.snapshotDao().insert(dbTester.getSession(), projectSnapshot);
-
-    ComponentDto directoryDto = ComponentTesting.newDirectory(projectDto, "CDEF", "MODULE_KEY:src/main/java/dir").setKey("MODULE_KEY:src/main/java/dir");
-    dbTester.components().insertComponent(directoryDto);
-
-    ComponentDto fileDto = ComponentTesting.newFileDto(projectDto, directoryDto, "DEFG").setKey("MODULE_KEY:src/main/java/dir/Foo.java");
-    dbTester.components().insertComponent(fileDto);
-
-    dbTester.getSession().commit();
-
-    Component file = ReportComponent.builder(Component.Type.FILE, 3).setUuid("DEFG").setKey("MODULE_KEY:src/main/java/dir/Foo.java").build();
-    Component directory = ReportComponent.builder(Component.Type.DIRECTORY, 2).setUuid("CDEF").setKey("MODULE_KEY:src/main/java/dir").addChildren(file).build();
-    Component project = ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").setKey(PROJECT_KEY).addChildren(directory).build();
-    treeRootHolder.setRoot(project);
-
-    underTest.execute(new TestComputationStepContext());
-
-    SnapshotDto newProjectSnapshot = getUnprocessedSnapshot(projectDto.uuid());
-    assertThat(newProjectSnapshot.getPeriodMode()).isEqualTo("PREVIOUS_VERSION");
-  }
-
-  @Test
-  public void set_no_period_on_snapshots_when_no_period() {
-    ComponentDto projectDto = ComponentTesting.newPrivateProjectDto("ABCD").setKey(PROJECT_KEY).setName("Project");
-    dbTester.components().insertComponent(projectDto);
-    SnapshotDto snapshotDto = SnapshotTesting.newAnalysis(projectDto);
-    dbClient.snapshotDao().insert(dbTester.getSession(), snapshotDto);
-    dbTester.getSession().commit();
-
-    Component project = ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").setKey(PROJECT_KEY).build();
-    treeRootHolder.setRoot(project);
-
-    underTest.execute(new TestComputationStepContext());
-
-    SnapshotDto projectSnapshot = getUnprocessedSnapshot(projectDto.uuid());
-    assertThat(projectSnapshot.getPeriodMode()).isNull();
-    assertThat(projectSnapshot.getPeriodDate()).isNull();
-    assertThat(projectSnapshot.getPeriodModeParameter()).isNull();
-  }
-
-  private SnapshotDto getUnprocessedSnapshot(String componentUuid) {
-    List<SnapshotDto> projectSnapshots = dbClient.snapshotDao().selectAnalysesByQuery(dbTester.getSession(),
-      new SnapshotQuery().setComponentUuid(componentUuid).setIsLast(false).setStatus(SnapshotDto.STATUS_UNPROCESSED));
-    assertThat(projectSnapshots).hasSize(1);
-    return projectSnapshots.get(0);
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ReportPersistComponentsStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ReportPersistComponentsStepTest.java
deleted file mode 100644 (file)
index c47def4..0000000
+++ /dev/null
@@ -1,522 +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.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Optional;
-import java.util.function.Consumer;
-import java.util.stream.Stream;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.DateUtils;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
-import org.sonar.ce.task.projectanalysis.analysis.TestBranch;
-import org.sonar.ce.task.projectanalysis.component.BranchPersister;
-import org.sonar.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.component.DefaultBranchImpl;
-import org.sonar.ce.task.projectanalysis.component.FileAttributes;
-import org.sonar.ce.task.projectanalysis.component.MutableDisabledComponentsHolder;
-import org.sonar.ce.task.projectanalysis.component.ProjectPersister;
-import org.sonar.ce.task.projectanalysis.component.ReportComponent;
-import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
-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.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.server.project.Project;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
-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.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME;
-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.ComponentTesting.newDirectory;
-
-public class ReportPersistComponentsStepTest extends BaseStepTest {
-
-  private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
-  private static final String PROJECT_KEY = "PROJECT_KEY";
-
-  @Rule
-  public DbTester db = DbTester.create(System2.INSTANCE);
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-  @Rule
-  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
-
-  private final System2 system2 = mock(System2.class);
-  private final DbClient dbClient = db.getDbClient();
-  private Date now;
-  private final MutableDisabledComponentsHolder disabledComponentsHolder = mock(MutableDisabledComponentsHolder.class, RETURNS_DEEP_STUBS);
-  private PersistComponentsStep underTest;
-
-  @Before
-  public void setup() throws Exception {
-    now = DATE_FORMAT.parse("2015-06-02");
-    when(system2.now()).thenReturn(now.getTime());
-
-    BranchPersister branchPersister = mock(BranchPersister.class);
-    ProjectPersister projectPersister = mock(ProjectPersister.class);
-    underTest = new PersistComponentsStep(dbClient, treeRootHolder, system2, disabledComponentsHolder, analysisMetadataHolder, branchPersister, projectPersister);
-  }
-
-  @Override
-  protected ComputationStep step() {
-    return underTest;
-  }
-
-  @Test
-  public void persist_components() {
-    ComponentDto projectDto = prepareProject();
-    Component file = builder(FILE, 4).setUuid("DEFG").setKey("PROJECT_KEY:src/main/java/dir/Foo.java")
-      .setName("src/main/java/dir/Foo.java")
-      .setShortName("Foo.java")
-      .setFileAttributes(new FileAttributes(false, "java", 1))
-      .build();
-    Component directory = builder(DIRECTORY, 3).setUuid("CDEF").setKey("PROJECT_KEY:src/main/java/dir")
-      .setName("src/main/java/dir")
-      .setShortName("dir")
-      .addChildren(file)
-      .build();
-    Component treeRoot = asTreeRoot(projectDto)
-      .addChildren(directory)
-      .build();
-    treeRootHolder.setRoot(treeRoot);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(db.countRowsOfTable("components")).isEqualTo(3);
-
-    ComponentDto directoryDto = dbClient.componentDao().selectByKey(db.getSession(), "PROJECT_KEY:src/main/java/dir").get();
-    assertThat(directoryDto.name()).isEqualTo("dir");
-    assertThat(directoryDto.longName()).isEqualTo("src/main/java/dir");
-    assertThat(directoryDto.description()).isNull();
-    assertThat(directoryDto.path()).isEqualTo("src/main/java/dir");
-    assertThat(directoryDto.uuid()).isEqualTo("CDEF");
-    assertThat(directoryDto.getUuidPath()).isEqualTo(UUID_PATH_SEPARATOR + projectDto.uuid() + UUID_PATH_SEPARATOR);
-    assertThat(directoryDto.getMainBranchProjectUuid()).isNull();
-    assertThat(directoryDto.branchUuid()).isEqualTo(projectDto.uuid());
-    assertThat(directoryDto.qualifier()).isEqualTo("DIR");
-    assertThat(directoryDto.scope()).isEqualTo("DIR");
-    assertThat(directoryDto.getCreatedAt()).isEqualTo(now);
-
-    ComponentDto fileDto = dbClient.componentDao().selectByKey(db.getSession(), "PROJECT_KEY:src/main/java/dir/Foo.java").get();
-    assertThat(fileDto.name()).isEqualTo("Foo.java");
-    assertThat(fileDto.longName()).isEqualTo("src/main/java/dir/Foo.java");
-    assertThat(fileDto.description()).isNull();
-    assertThat(fileDto.path()).isEqualTo("src/main/java/dir/Foo.java");
-    assertThat(fileDto.language()).isEqualTo("java");
-    assertThat(fileDto.uuid()).isEqualTo("DEFG");
-    assertThat(fileDto.getUuidPath()).isEqualTo(directoryDto.getUuidPath() + directoryDto.uuid() + UUID_PATH_SEPARATOR);
-    assertThat(fileDto.getMainBranchProjectUuid()).isNull();
-    assertThat(fileDto.branchUuid()).isEqualTo(projectDto.uuid());
-    assertThat(fileDto.qualifier()).isEqualTo("FIL");
-    assertThat(fileDto.scope()).isEqualTo("FIL");
-    assertThat(fileDto.getCreatedAt()).isEqualTo(now);
-  }
-
-  @Test
-  public void persist_components_of_existing_branch() {
-    ComponentDto branch = prepareBranch("feature/foo");
-    Component file = builder(FILE, 4).setUuid("DEFG").setKey("PROJECT_KEY:src/main/java/dir/Foo.java")
-      .setName("src/main/java/dir/Foo.java")
-      .setShortName("Foo.java")
-      .setFileAttributes(new FileAttributes(false, "java", 1))
-      .build();
-    Component directory = builder(DIRECTORY, 3).setUuid("CDEF")
-      .setKey("PROJECT_KEY:src/main/java/dir")
-      .setName("src/main/java/dir")
-      .setShortName("dir")
-      .addChildren(file)
-      .build();
-    Component treeRoot = asTreeRoot(branch)
-      .addChildren(directory)
-      .build();
-    treeRootHolder.setRoot(treeRoot);
-
-    underTest.execute(new TestComputationStepContext());
-
-    // 3 in this branch plus the project
-    assertThat(db.countRowsOfTable("components")).isEqualTo(4);
-
-    ComponentDto directoryDto = dbClient.componentDao().selectByKeyAndBranch(db.getSession(), "PROJECT_KEY:src/main/java/dir", "feature/foo").get();
-    assertThat(directoryDto.name()).isEqualTo("dir");
-    assertThat(directoryDto.longName()).isEqualTo("src/main/java/dir");
-    assertThat(directoryDto.description()).isNull();
-    assertThat(directoryDto.path()).isEqualTo("src/main/java/dir");
-    assertThat(directoryDto.uuid()).isEqualTo("CDEF");
-    assertThat(directoryDto.getUuidPath()).isEqualTo(UUID_PATH_SEPARATOR + branch.uuid() + UUID_PATH_SEPARATOR);
-    assertThat(directoryDto.getMainBranchProjectUuid()).isEqualTo(analysisMetadataHolder.getProject().getUuid());
-    assertThat(directoryDto.branchUuid()).isEqualTo(branch.uuid());
-    assertThat(directoryDto.qualifier()).isEqualTo("DIR");
-    assertThat(directoryDto.scope()).isEqualTo("DIR");
-    assertThat(directoryDto.getCreatedAt()).isEqualTo(now);
-
-    ComponentDto fileDto = dbClient.componentDao().selectByKeyAndBranch(db.getSession(), "PROJECT_KEY:src/main/java/dir/Foo.java", "feature/foo").get();
-    assertThat(fileDto.name()).isEqualTo("Foo.java");
-    assertThat(fileDto.longName()).isEqualTo("src/main/java/dir/Foo.java");
-    assertThat(fileDto.description()).isNull();
-    assertThat(fileDto.path()).isEqualTo("src/main/java/dir/Foo.java");
-    assertThat(fileDto.language()).isEqualTo("java");
-    assertThat(fileDto.uuid()).isEqualTo("DEFG");
-    assertThat(fileDto.getUuidPath()).isEqualTo(directoryDto.getUuidPath() + directoryDto.uuid() + UUID_PATH_SEPARATOR);
-    assertThat(fileDto.getMainBranchProjectUuid()).isEqualTo(analysisMetadataHolder.getProject().getUuid());
-    assertThat(fileDto.branchUuid()).isEqualTo(branch.uuid());
-    assertThat(fileDto.qualifier()).isEqualTo("FIL");
-    assertThat(fileDto.scope()).isEqualTo("FIL");
-    assertThat(fileDto.getCreatedAt()).isEqualTo(now);
-  }
-
-  @Test
-  public void persist_file_directly_attached_on_root_directory() {
-    ComponentDto projectDto = prepareProject();
-    treeRootHolder.setRoot(
-      asTreeRoot(projectDto)
-        .addChildren(
-          builder(FILE, 2).setUuid("DEFG").setKey(projectDto.getKey() + ":pom.xml")
-            .setName("pom.xml")
-            .build())
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbClient.componentDao().selectByKey(db.getSession(), projectDto.getKey() + ":/")).isNotPresent();
-
-    ComponentDto file = dbClient.componentDao().selectByKey(db.getSession(), projectDto.getKey() + ":pom.xml").get();
-    assertThat(file.name()).isEqualTo("pom.xml");
-    assertThat(file.path()).isEqualTo("pom.xml");
-  }
-
-  @Test
-  public void persist_unit_test() {
-    ComponentDto projectDto = prepareProject();
-    treeRootHolder.setRoot(
-      asTreeRoot(projectDto)
-        .addChildren(
-          builder(DIRECTORY, 2).setUuid("CDEF").setKey(PROJECT_KEY + ":src/test/java/dir")
-            .setName("src/test/java/dir")
-            .addChildren(
-              builder(FILE, 3).setUuid("DEFG").setKey(PROJECT_KEY + ":src/test/java/dir/FooTest.java")
-                .setName("src/test/java/dir/FooTest.java")
-                .setShortName("FooTest.java")
-                .setFileAttributes(new FileAttributes(true, null, 1))
-                .build())
-            .build())
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    ComponentDto file = dbClient.componentDao().selectByKey(db.getSession(), PROJECT_KEY + ":src/test/java/dir/FooTest.java").get();
-    assertThat(file.name()).isEqualTo("FooTest.java");
-    assertThat(file.longName()).isEqualTo("src/test/java/dir/FooTest.java");
-    assertThat(file.path()).isEqualTo("src/test/java/dir/FooTest.java");
-    assertThat(file.qualifier()).isEqualTo("UTS");
-    assertThat(file.scope()).isEqualTo("FIL");
-  }
-
-  @Test
-  public void update_file_to_directory_change_scope() {
-    ComponentDto project = prepareProject();
-    ComponentDto directory = ComponentTesting.newDirectory(project, "src").setUuid("CDEF").setKey("PROJECT_KEY:src");
-    ComponentDto file = ComponentTesting.newFileDto(project, directory, "DEFG").setPath("src/foo").setName("foo")
-      .setKey("PROJECT_KEY:src/foo");
-    dbClient.componentDao().insert(db.getSession(), directory, file);
-    db.getSession().commit();
-
-    assertThat(dbClient.componentDao().selectByKey(db.getSession(), PROJECT_KEY + ":src/foo").get().scope()).isEqualTo("FIL");
-
-    treeRootHolder.setRoot(
-      asTreeRoot(project)
-        .addChildren(
-          builder(DIRECTORY, 2).setUuid("CDEF").setKey(PROJECT_KEY + ":src")
-            .setName("src")
-            .addChildren(
-              builder(DIRECTORY, 3).setUuid("DEFG").setKey(PROJECT_KEY + ":src/foo")
-                .setName("foo")
-                .addChildren(
-                  builder(FILE, 4).setUuid("HIJK").setKey(PROJECT_KEY + ":src/foo/FooTest.java")
-                    .setName("src/foo/FooTest.java")
-                    .setShortName("FooTest.java")
-                    .setFileAttributes(new FileAttributes(false, null, 1))
-                    .build())
-                .build())
-            .build())
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    // commit the functional transaction
-    dbClient.componentDao().applyBChangesForBranchUuid(db.getSession(), project.uuid());
-    db.commit();
-
-    assertThat(dbClient.componentDao().selectByKey(db.getSession(), PROJECT_KEY + ":src/foo").get().scope()).isEqualTo("DIR");
-  }
-
-  @Test
-  public void persist_only_new_components() {
-    // Project and module already exists
-    ComponentDto project = prepareProject();
-    db.getSession().commit();
-
-    treeRootHolder.setRoot(
-      builder(PROJECT, 1).setUuid(project.uuid()).setKey(project.getKey())
-        .setName("Project")
-        .addChildren(
-          builder(DIRECTORY, 3).setUuid("CDEF").setKey("PROJECT_KEY:src/main/java/dir")
-            .setName("src/main/java/dir")
-            .addChildren(
-              builder(FILE, 4).setUuid("DEFG").setKey("PROJECT_KEY:src/main/java/dir/Foo.java")
-                .setName("src/main/java/dir/Foo.java")
-                .setShortName("Foo.java")
-                .build())
-            .build())
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(db.countRowsOfTable("components")).isEqualTo(3);
-
-    ComponentDto projectReloaded = dbClient.componentDao().selectByKey(db.getSession(), project.getKey()).get();
-    assertThat(projectReloaded.uuid()).isEqualTo(project.uuid());
-    assertThat(projectReloaded.getUuidPath()).isEqualTo(UUID_PATH_OF_ROOT);
-    assertThat(projectReloaded.getMainBranchProjectUuid()).isNull();
-
-    ComponentDto directory = dbClient.componentDao().selectByKey(db.getSession(), "PROJECT_KEY:src/main/java/dir").get();
-    assertThat(directory.getUuidPath()).isEqualTo(directory.getUuidPath());
-    assertThat(directory.branchUuid()).isEqualTo(project.uuid());
-    assertThat(directory.getMainBranchProjectUuid()).isNull();
-
-    ComponentDto file = dbClient.componentDao().selectByKey(db.getSession(), "PROJECT_KEY:src/main/java/dir/Foo.java").get();
-    assertThat(file.getUuidPath()).isEqualTo(file.getUuidPath());
-    assertThat(file.branchUuid()).isEqualTo(project.uuid());
-    assertThat(file.getMainBranchProjectUuid()).isNull();
-  }
-
-  @Test
-  public void nothing_to_persist() {
-    ComponentDto project = prepareProject();
-    ComponentDto directory = ComponentTesting.newDirectory(project, "src/main/java/dir").setUuid("CDEF").setKey("PROJECT_KEY:src/main/java/dir");
-    ComponentDto file = ComponentTesting.newFileDto(project, directory, "DEFG").setPath("src/main/java/dir/Foo.java").setName("Foo.java")
-      .setKey("PROJECT_KEY:src/main/java/dir/Foo.java");
-    dbClient.componentDao().insert(db.getSession(), directory, file);
-    db.getSession().commit();
-
-    treeRootHolder.setRoot(
-      builder(PROJECT, 1).setUuid(project.uuid()).setKey(project.getKey())
-        .setName("Project")
-        .addChildren(
-          builder(DIRECTORY, 3).setUuid("CDEF").setKey("PROJECT_KEY:src/main/java/dir")
-            .setName("src/main/java/dir")
-            .addChildren(
-              builder(FILE, 4).setUuid("DEFG").setKey("PROJECT_KEY:src/main/java/dir/Foo.java")
-                .setName("src/main/java/dir/Foo.java")
-                .build())
-            .build())
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(db.countRowsOfTable("components")).isEqualTo(3);
-    assertThat(dbClient.componentDao().selectByKey(db.getSession(), project.getKey()).get().uuid()).isEqualTo(project.uuid());
-    assertThat(dbClient.componentDao().selectByKey(db.getSession(), "PROJECT_KEY:src/main/java/dir").get().uuid()).isEqualTo(directory.uuid());
-    assertThat(dbClient.componentDao().selectByKey(db.getSession(), "PROJECT_KEY:src/main/java/dir/Foo.java").get().uuid()).isEqualTo(file.uuid());
-
-    ComponentDto projectReloaded = dbClient.componentDao().selectByKey(db.getSession(), project.getKey()).get();
-    assertThat(projectReloaded.uuid()).isEqualTo(project.uuid());
-    assertThat(projectReloaded.branchUuid()).isEqualTo(project.branchUuid());
-
-    ComponentDto directoryReloaded = dbClient.componentDao().selectByKey(db.getSession(), "PROJECT_KEY:src/main/java/dir").get();
-    assertThat(directoryReloaded.uuid()).isEqualTo(directory.uuid());
-    assertThat(directoryReloaded.getUuidPath()).isEqualTo(directory.getUuidPath());
-    assertThat(directoryReloaded.branchUuid()).isEqualTo(directory.branchUuid());
-    assertThat(directoryReloaded.name()).isEqualTo(directory.name());
-    assertThat(directoryReloaded.path()).isEqualTo(directory.path());
-
-    ComponentDto fileReloaded = dbClient.componentDao().selectByKey(db.getSession(), "PROJECT_KEY:src/main/java/dir/Foo.java").get();
-    assertThat(fileReloaded.uuid()).isEqualTo(file.uuid());
-    assertThat(fileReloaded.getUuidPath()).isEqualTo(file.getUuidPath());
-    assertThat(fileReloaded.branchUuid()).isEqualTo(file.branchUuid());
-    assertThat(fileReloaded.name()).isEqualTo(file.name());
-    assertThat(fileReloaded.path()).isEqualTo(file.path());
-  }
-
-  @Test
-  public void do_not_update_created_at_on_existing_component() {
-    Date oldDate = DateUtils.parseDate("2015-01-01");
-    ComponentDto project = prepareProject(p -> p.setCreatedAt(oldDate));
-    db.getSession().commit();
-
-    treeRootHolder.setRoot(
-      builder(PROJECT, 1).setUuid(project.uuid()).setKey(project.getKey())
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    Optional<ComponentDto> projectReloaded = dbClient.componentDao().selectByUuid(db.getSession(), project.uuid());
-    assertThat(projectReloaded.get().getCreatedAt()).isNotEqualTo(now);
-  }
-
-  @Test
-  public void persist_components_that_were_previously_removed() {
-    ComponentDto project = prepareProject();
-    ComponentDto removedDirectory = ComponentTesting.newDirectory(project, "src/main/java/dir")
-      .setLongName("src/main/java/dir")
-      .setName("dir")
-      .setUuid("CDEF")
-      .setKey("PROJECT_KEY:src/main/java/dir")
-      .setEnabled(false);
-    ComponentDto removedFile = ComponentTesting.newFileDto(project, removedDirectory, "DEFG")
-      .setPath("src/main/java/dir/Foo.java")
-      .setLongName("src/main/java/dir/Foo.java")
-      .setName("Foo.java")
-      .setKey("PROJECT_KEY:src/main/java/dir/Foo.java")
-      .setEnabled(false);
-    dbClient.componentDao().insert(db.getSession(), removedDirectory, removedFile);
-    db.getSession().commit();
-
-    treeRootHolder.setRoot(
-      builder(PROJECT, 1).setUuid(project.uuid()).setKey(project.getKey())
-        .setName("Project")
-        .addChildren(
-          builder(DIRECTORY, 3).setUuid("CDEF").setKey("PROJECT_KEY:src/main/java/dir")
-            .setName("src/main/java/dir")
-            .setShortName("dir")
-            .addChildren(
-              builder(FILE, 4).setUuid("DEFG").setKey("PROJECT_KEY:src/main/java/dir/Foo.java")
-                .setName("src/main/java/dir/Foo.java")
-                .setShortName("Foo.java")
-                .build())
-            .build())
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(db.countRowsOfTable("components")).isEqualTo(3);
-    assertThat(dbClient.componentDao().selectByKey(db.getSession(), project.getKey()).get().uuid()).isEqualTo(project.uuid());
-    assertThat(dbClient.componentDao().selectByKey(db.getSession(), "PROJECT_KEY:src/main/java/dir").get().uuid()).isEqualTo(removedDirectory.uuid());
-    assertThat(dbClient.componentDao().selectByKey(db.getSession(), "PROJECT_KEY:src/main/java/dir/Foo.java").get().uuid()).isEqualTo(removedFile.uuid());
-    assertExistButDisabled(removedDirectory.getKey(), removedFile.getKey());
-
-    // commit the functional transaction
-    dbClient.componentDao().applyBChangesForBranchUuid(db.getSession(), project.uuid());
-
-    ComponentDto projectReloaded = dbClient.componentDao().selectByKey(db.getSession(), project.getKey()).get();
-    assertThat(projectReloaded.uuid()).isEqualTo(project.uuid());
-    assertThat(projectReloaded.getUuidPath()).isEqualTo(project.getUuidPath());
-    assertThat(projectReloaded.branchUuid()).isEqualTo(project.branchUuid());
-    assertThat(projectReloaded.isEnabled()).isTrue();
-
-    ComponentDto directoryReloaded = dbClient.componentDao().selectByKey(db.getSession(), "PROJECT_KEY:src/main/java/dir").get();
-    assertThat(directoryReloaded.uuid()).isEqualTo(removedDirectory.uuid());
-    assertThat(directoryReloaded.getUuidPath()).isEqualTo(removedDirectory.getUuidPath());
-    assertThat(directoryReloaded.branchUuid()).isEqualTo(removedDirectory.branchUuid());
-    assertThat(directoryReloaded.name()).isEqualTo(removedDirectory.name());
-    assertThat(directoryReloaded.longName()).isEqualTo(removedDirectory.longName());
-    assertThat(directoryReloaded.path()).isEqualTo(removedDirectory.path());
-    assertThat(directoryReloaded.isEnabled()).isTrue();
-
-    ComponentDto fileReloaded = dbClient.componentDao().selectByKey(db.getSession(), "PROJECT_KEY:src/main/java/dir/Foo.java").get();
-    assertThat(fileReloaded.uuid()).isEqualTo(removedFile.uuid());
-    assertThat(fileReloaded.getUuidPath()).isEqualTo(removedFile.getUuidPath());
-    assertThat(fileReloaded.branchUuid()).isEqualTo(removedFile.branchUuid());
-    assertThat(fileReloaded.name()).isEqualTo(removedFile.name());
-    assertThat(fileReloaded.path()).isEqualTo(removedFile.path());
-    assertThat(fileReloaded.isEnabled()).isTrue();
-  }
-
-  private void assertExistButDisabled(String... keys) {
-    for (String key : keys) {
-      ComponentDto dto = dbClient.componentDao().selectByKey(db.getSession(), key).get();
-      assertThat(dto.isEnabled()).isFalse();
-    }
-  }
-
-  @Test
-  public void persists_existing_components_with_visibility_of_root_in_db_out_of_functional_transaction() {
-    ComponentDto project = prepareProject(p -> p.setPrivate(true));
-    ComponentDto dir = db.components().insertComponent(newDirectory(project, "DEFG", "Directory").setKey("DIR").setPrivate(true));
-    treeRootHolder.setRoot(createSampleProjectComponentTree(project));
-
-    underTest.execute(new TestComputationStepContext());
-
-    Stream.of(project.uuid(), dir.uuid())
-      .forEach(uuid -> assertThat(dbClient.componentDao().selectByUuid(db.getSession(), uuid).get().isPrivate())
-        .describedAs("for uuid " + uuid)
-        .isEqualTo(true));
-  }
-
-  private ReportComponent createSampleProjectComponentTree(ComponentDto project) {
-    return createSampleProjectComponentTree(project.uuid(), project.getKey());
-  }
-
-  private ReportComponent createSampleProjectComponentTree(String projectUuid, String projectKey) {
-    return builder(PROJECT, 1).setUuid(projectUuid).setKey(projectKey)
-      .setName("Project")
-      .addChildren(
-        builder(Component.Type.DIRECTORY, 3).setUuid("DEFG").setKey("DIR")
-          .setName("Directory")
-          .addChildren(
-            builder(FILE, 4).setUuid("CDEF").setKey("FILE")
-              .setName("file")
-              .build())
-          .build())
-      .build();
-  }
-
-  private ReportComponent.Builder asTreeRoot(ComponentDto project) {
-    return builder(PROJECT, 1).setUuid(project.uuid()).setKey(project.getKey()).setName(project.name());
-  }
-
-  private ComponentDto prepareProject() {
-    return prepareProject(defaults());
-  }
-
-  private ComponentDto prepareProject(Consumer<ComponentDto> populators) {
-    ComponentDto dto = db.components().insertPrivateProject(populators);
-    analysisMetadataHolder.setProject(Project.from(dto));
-    analysisMetadataHolder.setBranch(new DefaultBranchImpl(DEFAULT_MAIN_BRANCH_NAME));
-    return dto;
-  }
-
-  private ComponentDto prepareBranch(String branchName) {
-    ComponentDto projectDto = db.components().insertPublicProject();
-    ComponentDto branchDto = db.components().insertProjectBranch(projectDto, b -> b.setKey(branchName));
-    analysisMetadataHolder.setProject(Project.from(projectDto));
-    analysisMetadataHolder.setBranch(new TestBranch(branchName));
-    return branchDto;
-  }
-
-  private static <T> Consumer<T> defaults() {
-    return t -> {
-    };
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/SendIssueNotificationsStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/SendIssueNotificationsStepTest.java
deleted file mode 100644 (file)
index f6a9526..0000000
+++ /dev/null
@@ -1,721 +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.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.Set;
-import java.util.function.Supplier;
-import java.util.stream.IntStream;
-import java.util.stream.Stream;
-import org.assertj.core.groups.Tuple;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.mockito.ArgumentCaptor;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-import org.sonar.api.notifications.Notification;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rules.RuleType;
-import org.sonar.api.utils.Duration;
-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.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.component.DefaultBranchImpl;
-import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
-import org.sonar.ce.task.projectanalysis.issue.ProtoIssueCache;
-import org.sonar.ce.task.projectanalysis.notification.NotificationFactory;
-import org.sonar.ce.task.projectanalysis.util.cache.DiskCache;
-import org.sonar.ce.task.step.ComputationStep;
-import org.sonar.ce.task.step.TestComputationStepContext;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.BranchType;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.db.user.UserDto;
-import org.sonar.server.issue.notification.DistributedMetricStatsInt;
-import org.sonar.server.issue.notification.IssuesChangesNotification;
-import org.sonar.server.issue.notification.MyNewIssuesNotification;
-import org.sonar.server.issue.notification.NewIssuesNotification;
-import org.sonar.server.issue.notification.NewIssuesStatistics;
-import org.sonar.server.notification.NotificationService;
-import org.sonar.server.project.Project;
-
-import static java.util.Arrays.stream;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.shuffle;
-import static java.util.Collections.singleton;
-import static java.util.stream.Collectors.toList;
-import static java.util.stream.Stream.concat;
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.apache.commons.lang.math.RandomUtils.nextInt;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.groups.Tuple.tuple;
-import static org.mockito.ArgumentCaptor.forClass;
-import static org.mockito.ArgumentMatchers.anyCollection;
-import static org.mockito.ArgumentMatchers.anyMap;
-import static org.mockito.ArgumentMatchers.anySet;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoInteractions;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
-import static org.sonar.ce.task.projectanalysis.component.Component.Type;
-import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
-import static org.sonar.ce.task.projectanalysis.step.SendIssueNotificationsStep.NOTIF_TYPES;
-import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME;
-import static org.sonar.db.component.BranchType.BRANCH;
-import static org.sonar.db.component.BranchType.PULL_REQUEST;
-import static org.sonar.db.component.ComponentTesting.newBranchComponent;
-import static org.sonar.db.component.ComponentTesting.newBranchDto;
-import static org.sonar.db.component.ComponentTesting.newFileDto;
-import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
-import static org.sonar.db.issue.IssueTesting.newIssue;
-import static org.sonar.db.rule.RuleTesting.newRule;
-
-public class SendIssueNotificationsStepTest extends BaseStepTest {
-
-  private static final String BRANCH_NAME = "feature";
-  private static final String PULL_REQUEST_ID = "pr-123";
-
-  private static final long ANALYSE_DATE = 123L;
-  private static final int FIVE_MINUTES_IN_MS = 1000 * 60 * 5;
-
-  private static final Duration ISSUE_DURATION = Duration.create(100L);
-
-  private static final Component FILE = builder(Type.FILE, 11).build();
-  private static final Component PROJECT = builder(Type.PROJECT, 1)
-    .setProjectVersion(randomAlphanumeric(10))
-    .addChildren(FILE).build();
-
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule()
-    .setRoot(PROJECT);
-  @Rule
-  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule()
-    .setBranch(new DefaultBranchImpl(DEFAULT_MAIN_BRANCH_NAME))
-    .setAnalysisDate(new Date(ANALYSE_DATE));
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-  @Rule
-  public DbTester db = DbTester.create(System2.INSTANCE);
-
-  private final Random random = new Random();
-  private final RuleType[] RULE_TYPES_EXCEPT_HOTSPOTS = Stream.of(RuleType.values()).filter(r -> r != SECURITY_HOTSPOT).toArray(RuleType[]::new);
-  private final RuleType randomRuleType = RULE_TYPES_EXCEPT_HOTSPOTS[random.nextInt(RULE_TYPES_EXCEPT_HOTSPOTS.length)];
-  @SuppressWarnings("unchecked")
-  private Class<Map<String, UserDto>> assigneeCacheType = (Class<Map<String, UserDto>>) (Object) Map.class;
-  @SuppressWarnings("unchecked")
-  private Class<Set<DefaultIssue>> setType = (Class<Set<DefaultIssue>>) (Class<?>) Set.class;
-  @SuppressWarnings("unchecked")
-  private Class<Map<String, UserDto>> mapType = (Class<Map<String, UserDto>>) (Class<?>) Map.class;
-  private ArgumentCaptor<Map<String, UserDto>> assigneeCacheCaptor = ArgumentCaptor.forClass(assigneeCacheType);
-  private ArgumentCaptor<Set<DefaultIssue>> issuesSetCaptor = forClass(setType);
-  private ArgumentCaptor<Map<String, UserDto>> assigneeByUuidCaptor = forClass(mapType);
-  private NotificationService notificationService = mock(NotificationService.class);
-  private NotificationFactory notificationFactory = mock(NotificationFactory.class);
-  private NewIssuesNotification newIssuesNotificationMock = createNewIssuesNotificationMock();
-  private MyNewIssuesNotification myNewIssuesNotificationMock = createMyNewIssuesNotificationMock();
-
-  private ProtoIssueCache protoIssueCache;
-  private SendIssueNotificationsStep underTest;
-
-  @Before
-  public void setUp() throws Exception {
-    protoIssueCache = new ProtoIssueCache(temp.newFile(), System2.INSTANCE);
-    underTest = new SendIssueNotificationsStep(protoIssueCache, treeRootHolder, notificationService, analysisMetadataHolder,
-      notificationFactory, db.getDbClient());
-    when(notificationFactory.newNewIssuesNotification(any(assigneeCacheType))).thenReturn(newIssuesNotificationMock);
-    when(notificationFactory.newMyNewIssuesNotification(any(assigneeCacheType))).thenReturn(myNewIssuesNotificationMock);
-  }
-
-  @Test
-  public void do_not_send_notifications_if_no_subscribers() {
-    analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
-    when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(false);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    verify(notificationService, never()).deliver(any(Notification.class));
-    verify(notificationService, never()).deliverEmails(anyCollection());
-    verifyStatistics(context, 0, 0, 0);
-  }
-
-  @Test
-  public void send_global_new_issues_notification() {
-    analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
-    protoIssueCache.newAppender().append(
-      createIssue().setType(randomRuleType).setEffort(ISSUE_DURATION)
-        .setCreationDate(new Date(ANALYSE_DATE)))
-      .close();
-    when(notificationService.hasProjectSubscribersForTypes(eq(PROJECT.getUuid()), any())).thenReturn(true);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    verify(notificationService).deliver(newIssuesNotificationMock);
-    verify(newIssuesNotificationMock).setProject(PROJECT.getKey(), PROJECT.getName(), null, null);
-    verify(newIssuesNotificationMock).setAnalysisDate(new Date(ANALYSE_DATE));
-    verify(newIssuesNotificationMock).setStatistics(eq(PROJECT.getName()), any());
-    verify(newIssuesNotificationMock).setDebt(ISSUE_DURATION);
-    verifyStatistics(context, 1, 0, 0);
-  }
-
-  @Test
-  public void send_global_new_issues_notification_only_for_non_backdated_issues() {
-    Random random = new Random();
-    Integer[] efforts = IntStream.range(0, 1 + random.nextInt(10)).mapToObj(i -> 10_000 * i).toArray(Integer[]::new);
-    Integer[] backDatedEfforts = IntStream.range(0, 1 + random.nextInt(10)).mapToObj(i -> 10 + random.nextInt(100)).toArray(Integer[]::new);
-    Duration expectedEffort = Duration.create(stream(efforts).mapToInt(i -> i).sum());
-    List<DefaultIssue> issues = concat(stream(efforts)
-        .map(effort -> createIssue().setType(randomRuleType).setEffort(Duration.create(effort))
-          .setCreationDate(new Date(ANALYSE_DATE))),
-      stream(backDatedEfforts)
-        .map(effort -> createIssue().setType(randomRuleType).setEffort(Duration.create(effort))
-          .setCreationDate(new Date(ANALYSE_DATE - FIVE_MINUTES_IN_MS))))
-      .collect(toList());
-    shuffle(issues);
-    DiskCache.CacheAppender issueCache = this.protoIssueCache.newAppender();
-    issues.forEach(issueCache::append);
-    issueCache.close();
-    analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
-    when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    verify(notificationService).deliver(newIssuesNotificationMock);
-    ArgumentCaptor<NewIssuesStatistics.Stats> statsCaptor = forClass(NewIssuesStatistics.Stats.class);
-    verify(newIssuesNotificationMock).setStatistics(eq(PROJECT.getName()), statsCaptor.capture());
-    verify(newIssuesNotificationMock).setDebt(expectedEffort);
-    NewIssuesStatistics.Stats stats = statsCaptor.getValue();
-    assertThat(stats.hasIssues()).isTrue();
-    // just checking all issues have been added to the stats
-    DistributedMetricStatsInt severity = stats.getDistributedMetricStats(NewIssuesStatistics.Metric.RULE_TYPE);
-    assertThat(severity.getOnCurrentAnalysis()).isEqualTo(efforts.length);
-    assertThat(severity.getTotal()).isEqualTo(backDatedEfforts.length + efforts.length);
-    verifyStatistics(context, 1, 0, 0);
-  }
-
-  @Test
-  public void do_not_send_global_new_issues_notification_if_issue_has_been_backdated() {
-    analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
-    protoIssueCache.newAppender().append(
-      createIssue().setType(randomRuleType).setEffort(ISSUE_DURATION)
-        .setCreationDate(new Date(ANALYSE_DATE - FIVE_MINUTES_IN_MS)))
-      .close();
-    when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    verify(notificationService, never()).deliver(any(Notification.class));
-    verify(notificationService, never()).deliverEmails(anyCollection());
-    verifyStatistics(context, 0, 0, 0);
-  }
-
-  @Test
-  public void send_global_new_issues_notification_on_branch() {
-    ComponentDto project = newPrivateProjectDto();
-    ComponentDto branch = setUpBranch(project, BRANCH);
-    protoIssueCache.newAppender().append(
-      createIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setCreationDate(new Date(ANALYSE_DATE))).close();
-    when(notificationService.hasProjectSubscribersForTypes(branch.uuid(), NOTIF_TYPES)).thenReturn(true);
-    analysisMetadataHolder.setProject(Project.from(project));
-    analysisMetadataHolder.setBranch(newBranch(BranchType.BRANCH));
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    verify(notificationService).deliver(newIssuesNotificationMock);
-    verify(newIssuesNotificationMock).setProject(branch.getKey(), branch.longName(), BRANCH_NAME, null);
-    verify(newIssuesNotificationMock).setAnalysisDate(new Date(ANALYSE_DATE));
-    verify(newIssuesNotificationMock).setStatistics(eq(branch.longName()), any(NewIssuesStatistics.Stats.class));
-    verify(newIssuesNotificationMock).setDebt(ISSUE_DURATION);
-    verifyStatistics(context, 1, 0, 0);
-  }
-
-  @Test
-  public void do_not_send_global_new_issues_notification_on_pull_request() {
-    ComponentDto project = newPrivateProjectDto();
-    ComponentDto branch = setUpBranch(project, PULL_REQUEST);
-    protoIssueCache.newAppender().append(
-      createIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setCreationDate(new Date(ANALYSE_DATE))).close();
-    when(notificationService.hasProjectSubscribersForTypes(project.uuid(), NOTIF_TYPES)).thenReturn(true);
-    analysisMetadataHolder.setProject(Project.from(project));
-    analysisMetadataHolder.setBranch(newPullRequest());
-    analysisMetadataHolder.setPullRequestKey(PULL_REQUEST_ID);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    verifyNoInteractions(notificationService, newIssuesNotificationMock);
-  }
-
-  private DefaultIssue createIssue() {
-    return new DefaultIssue().setKey("k").setProjectKey("p").setStatus("OPEN").setProjectUuid("uuid").setComponentKey("c").setRuleKey(RuleKey.of("r", "r"));
-  }
-
-  @Test
-  public void do_not_send_global_new_issues_notification_on_branch_if_issue_has_been_backdated() {
-    ComponentDto project = newPrivateProjectDto();
-    ComponentDto branch = setUpBranch(project, BRANCH);
-    protoIssueCache.newAppender().append(
-      createIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setCreationDate(new Date(ANALYSE_DATE - FIVE_MINUTES_IN_MS))).close();
-    when(notificationService.hasProjectSubscribersForTypes(branch.uuid(), NOTIF_TYPES)).thenReturn(true);
-    analysisMetadataHolder.setProject(Project.from(project));
-    analysisMetadataHolder.setBranch(newBranch(BranchType.BRANCH));
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    verify(notificationService, never()).deliver(any(Notification.class));
-    verify(notificationService, never()).deliverEmails(anyCollection());
-    verifyStatistics(context, 0, 0, 0);
-  }
-
-  @Test
-  public void send_new_issues_notification_to_user() {
-    UserDto user = db.users().insertUser();
-    analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
-
-    protoIssueCache.newAppender().append(
-      createIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setAssigneeUuid(user.getUuid()).setCreationDate(new Date(ANALYSE_DATE)))
-      .close();
-    when(notificationService.hasProjectSubscribersForTypes(eq(PROJECT.getUuid()), any())).thenReturn(true);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    verify(notificationService).deliverEmails(ImmutableSet.of(newIssuesNotificationMock));
-    verify(notificationService).deliverEmails(ImmutableSet.of(myNewIssuesNotificationMock));
-    // old API compatibility call
-    verify(notificationService).deliver(newIssuesNotificationMock);
-    verify(notificationService).deliver(myNewIssuesNotificationMock);
-    verify(myNewIssuesNotificationMock).setAssignee(any(UserDto.class));
-    verify(myNewIssuesNotificationMock).setProject(PROJECT.getKey(), PROJECT.getName(), null, null);
-    verify(myNewIssuesNotificationMock).setAnalysisDate(new Date(ANALYSE_DATE));
-    verify(myNewIssuesNotificationMock).setStatistics(eq(PROJECT.getName()), any(NewIssuesStatistics.Stats.class));
-    verify(myNewIssuesNotificationMock).setDebt(ISSUE_DURATION);
-    verifyStatistics(context, 1, 1, 0);
-  }
-
-  @Test
-  public void send_new_issues_notification_to_user_only_for_those_assigned_to_her() throws IOException {
-    UserDto perceval = db.users().insertUser(u -> u.setLogin("perceval"));
-    Integer[] assigned = IntStream.range(0, 5).mapToObj(i -> 10_000 * i).toArray(Integer[]::new);
-    Duration expectedEffort = Duration.create(stream(assigned).mapToInt(i -> i).sum());
-
-    UserDto arthur = db.users().insertUser(u -> u.setLogin("arthur"));
-    Integer[] assignedToOther = IntStream.range(0, 3).mapToObj(i -> 10).toArray(Integer[]::new);
-
-    List<DefaultIssue> issues = concat(stream(assigned)
-        .map(effort -> createIssue().setType(randomRuleType).setEffort(Duration.create(effort))
-          .setAssigneeUuid(perceval.getUuid())
-          .setNew(true)
-          .setCreationDate(new Date(ANALYSE_DATE))),
-      stream(assignedToOther)
-        .map(effort -> createIssue().setType(randomRuleType).setEffort(Duration.create(effort))
-          .setAssigneeUuid(arthur.getUuid())
-          .setNew(true)
-          .setCreationDate(new Date(ANALYSE_DATE))))
-      .collect(toList());
-    shuffle(issues);
-    ProtoIssueCache protoIssueCache = new ProtoIssueCache(temp.newFile(), System2.INSTANCE);
-    DiskCache.CacheAppender newIssueCache = protoIssueCache.newAppender();
-    issues.forEach(newIssueCache::append);
-    newIssueCache.close();
-    analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
-    when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
-
-    NotificationFactory notificationFactory = mock(NotificationFactory.class);
-    NewIssuesNotification newIssuesNotificationMock = createNewIssuesNotificationMock();
-    when(notificationFactory.newNewIssuesNotification(assigneeCacheCaptor.capture()))
-      .thenReturn(newIssuesNotificationMock);
-
-    MyNewIssuesNotification myNewIssuesNotificationMock1 = createMyNewIssuesNotificationMock();
-    MyNewIssuesNotification myNewIssuesNotificationMock2 = createMyNewIssuesNotificationMock();
-    doReturn(myNewIssuesNotificationMock1).doReturn(myNewIssuesNotificationMock2).when(notificationFactory).newMyNewIssuesNotification(any(assigneeCacheType));
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    new SendIssueNotificationsStep(protoIssueCache, treeRootHolder, notificationService, analysisMetadataHolder, notificationFactory, db.getDbClient())
-      .execute(context);
-
-    verify(notificationService).deliverEmails(ImmutableSet.of(myNewIssuesNotificationMock1, myNewIssuesNotificationMock2));
-    // old API compatibility
-    verify(notificationService).deliver(myNewIssuesNotificationMock1);
-    verify(notificationService).deliver(myNewIssuesNotificationMock2);
-
-    verify(notificationFactory).newNewIssuesNotification(assigneeCacheCaptor.capture());
-    verify(notificationFactory, times(2)).newMyNewIssuesNotification(assigneeCacheCaptor.capture());
-    verifyNoMoreInteractions(notificationFactory);
-    verifyAssigneeCache(assigneeCacheCaptor, perceval, arthur);
-
-    Map<String, MyNewIssuesNotification> myNewIssuesNotificationMocksByUsersName = new HashMap<>();
-    ArgumentCaptor<UserDto> userCaptor1 = forClass(UserDto.class);
-    verify(myNewIssuesNotificationMock1).setAssignee(userCaptor1.capture());
-    myNewIssuesNotificationMocksByUsersName.put(userCaptor1.getValue().getLogin(), myNewIssuesNotificationMock1);
-
-    ArgumentCaptor<UserDto> userCaptor2 = forClass(UserDto.class);
-    verify(myNewIssuesNotificationMock2).setAssignee(userCaptor2.capture());
-    myNewIssuesNotificationMocksByUsersName.put(userCaptor2.getValue().getLogin(), myNewIssuesNotificationMock2);
-
-    MyNewIssuesNotification myNewIssuesNotificationMock = myNewIssuesNotificationMocksByUsersName.get("perceval");
-    ArgumentCaptor<NewIssuesStatistics.Stats> statsCaptor = forClass(NewIssuesStatistics.Stats.class);
-    verify(myNewIssuesNotificationMock).setStatistics(eq(PROJECT.getName()), statsCaptor.capture());
-    verify(myNewIssuesNotificationMock).setDebt(expectedEffort);
-
-    NewIssuesStatistics.Stats stats = statsCaptor.getValue();
-    assertThat(stats.hasIssues()).isTrue();
-    // just checking all issues have been added to the stats
-    DistributedMetricStatsInt severity = stats.getDistributedMetricStats(NewIssuesStatistics.Metric.RULE_TYPE);
-    assertThat(severity.getOnCurrentAnalysis()).isEqualTo(assigned.length);
-    assertThat(severity.getTotal()).isEqualTo(assigned.length);
-
-    verifyStatistics(context, 1, 2, 0);
-  }
-
-  @Test
-  public void send_new_issues_notification_to_user_only_for_non_backdated_issues() {
-    UserDto user = db.users().insertUser();
-    Random random = new Random();
-    Integer[] efforts = IntStream.range(0, 1 + random.nextInt(10)).mapToObj(i -> 10_000 * i).toArray(Integer[]::new);
-    Integer[] backDatedEfforts = IntStream.range(0, 1 + random.nextInt(10)).mapToObj(i -> 10 + random.nextInt(100)).toArray(Integer[]::new);
-    Duration expectedEffort = Duration.create(stream(efforts).mapToInt(i -> i).sum());
-    List<DefaultIssue> issues = concat(stream(efforts)
-        .map(effort -> createIssue().setType(randomRuleType).setEffort(Duration.create(effort))
-          .setAssigneeUuid(user.getUuid())
-          .setCreationDate(new Date(ANALYSE_DATE))),
-      stream(backDatedEfforts)
-        .map(effort -> createIssue().setType(randomRuleType).setEffort(Duration.create(effort))
-          .setAssigneeUuid(user.getUuid())
-          .setCreationDate(new Date(ANALYSE_DATE - FIVE_MINUTES_IN_MS))))
-      .collect(toList());
-    shuffle(issues);
-    DiskCache.CacheAppender issueCache = this.protoIssueCache.newAppender();
-    issues.forEach(issueCache::append);
-    issueCache.close();
-    analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
-    when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    verify(notificationService).deliver(newIssuesNotificationMock);
-    verify(notificationService).deliverEmails(ImmutableSet.of(myNewIssuesNotificationMock));
-    // old API compatibility
-    verify(notificationService).deliver(myNewIssuesNotificationMock);
-
-    verify(notificationFactory).newNewIssuesNotification(assigneeCacheCaptor.capture());
-    verify(notificationFactory).newMyNewIssuesNotification(assigneeCacheCaptor.capture());
-    verifyNoMoreInteractions(notificationFactory);
-    verifyAssigneeCache(assigneeCacheCaptor, user);
-
-    verify(myNewIssuesNotificationMock).setAssignee(any(UserDto.class));
-    ArgumentCaptor<NewIssuesStatistics.Stats> statsCaptor = forClass(NewIssuesStatistics.Stats.class);
-    verify(myNewIssuesNotificationMock).setStatistics(eq(PROJECT.getName()), statsCaptor.capture());
-    verify(myNewIssuesNotificationMock).setDebt(expectedEffort);
-    NewIssuesStatistics.Stats stats = statsCaptor.getValue();
-    assertThat(stats.hasIssues()).isTrue();
-    // just checking all issues have been added to the stats
-    DistributedMetricStatsInt severity = stats.getDistributedMetricStats(NewIssuesStatistics.Metric.RULE_TYPE);
-    assertThat(severity.getOnCurrentAnalysis()).isEqualTo(efforts.length);
-    assertThat(severity.getTotal()).isEqualTo(backDatedEfforts.length + efforts.length);
-
-    verifyStatistics(context, 1, 1, 0);
-  }
-
-  private static void verifyAssigneeCache(ArgumentCaptor<Map<String, UserDto>> assigneeCacheCaptor, UserDto... users) {
-    Map<String, UserDto> cache = assigneeCacheCaptor.getAllValues().iterator().next();
-    assertThat(assigneeCacheCaptor.getAllValues())
-      .filteredOn(t -> t != cache)
-      .isEmpty();
-    Tuple[] expected = stream(users).map(user -> tuple(user.getUuid(), user.getUuid(), user.getUuid(), user.getLogin())).toArray(Tuple[]::new);
-    assertThat(cache.entrySet())
-      .extracting(Map.Entry::getKey, t -> t.getValue().getUuid(), t -> t.getValue().getUuid(), t -> t.getValue().getLogin())
-      .containsOnly(expected);
-  }
-
-  @Test
-  public void do_not_send_new_issues_notification_to_user_if_issue_is_backdated() {
-    analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
-    UserDto user = db.users().insertUser();
-    protoIssueCache.newAppender().append(
-      createIssue().setType(randomRuleType).setEffort(ISSUE_DURATION).setAssigneeUuid(user.getUuid())
-        .setCreationDate(new Date(ANALYSE_DATE - FIVE_MINUTES_IN_MS)))
-      .close();
-    when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    verify(notificationService, never()).deliver(any(Notification.class));
-    verify(notificationService, never()).deliverEmails(anyCollection());
-    verifyStatistics(context, 0, 0, 0);
-  }
-
-  @Test
-  public void send_issues_change_notification() {
-    sendIssueChangeNotification(ANALYSE_DATE);
-  }
-
-  @Test
-  public void do_not_send_new_issues_notifications_for_hotspot() {
-    UserDto user = db.users().insertUser();
-    ComponentDto project = newPrivateProjectDto().setKey(PROJECT.getKey()).setLongName(PROJECT.getName());
-    ComponentDto file = newFileDto(project).setKey(FILE.getKey()).setLongName(FILE.getName());
-    RuleDto ruleDefinitionDto = newRule();
-    prepareIssue(ANALYSE_DATE, user, project, file, ruleDefinitionDto, RuleType.SECURITY_HOTSPOT);
-    analysisMetadataHolder.setProject(new Project(PROJECT.getUuid(), PROJECT.getKey(), PROJECT.getName(), null, emptyList()));
-    when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
-
-    TestComputationStepContext context = new TestComputationStepContext();
-    underTest.execute(context);
-
-    verify(notificationService, never()).deliver(any(Notification.class));
-    verify(notificationService, never()).deliverEmails(anyCollection());
-    verifyStatistics(context, 0, 0, 0);
-  }
-
-  @Test
-  public void send_issues_change_notification_even_if_issue_is_backdated() {
-    sendIssueChangeNotification(ANALYSE_DATE - FIVE_MINUTES_IN_MS);
-  }
-
-  private void sendIssueChangeNotification(long issueCreatedAt) {
-    UserDto user = db.users().insertUser();
-    ComponentDto project = newPrivateProjectDto().setKey(PROJECT.getKey()).setLongName(PROJECT.getName());
-    analysisMetadataHolder.setProject(Project.from(project));
-    ComponentDto file = newFileDto(project).setKey(FILE.getKey()).setLongName(FILE.getName());
-    treeRootHolder.setRoot(builder(Type.PROJECT, 2).setKey(project.getKey()).setName(project.longName()).setUuid(project.uuid())
-      .addChildren(
-        builder(Type.FILE, 11).setKey(file.getKey()).setName(file.longName()).build())
-      .build());
-    RuleDto ruleDefinitionDto = newRule();
-    RuleType randomTypeExceptHotspot = RuleType.values()[nextInt(RuleType.values().length - 1)];
-    DefaultIssue issue = prepareIssue(issueCreatedAt, user, project, file, ruleDefinitionDto, randomTypeExceptHotspot);
-    IssuesChangesNotification issuesChangesNotification = mock(IssuesChangesNotification.class);
-    when(notificationService.hasProjectSubscribersForTypes(project.uuid(), NOTIF_TYPES)).thenReturn(true);
-    when(notificationFactory.newIssuesChangesNotification(anySet(), anyMap())).thenReturn(issuesChangesNotification);
-
-    underTest.execute(new TestComputationStepContext());
-
-    verify(notificationFactory).newIssuesChangesNotification(issuesSetCaptor.capture(), assigneeByUuidCaptor.capture());
-    assertThat(issuesSetCaptor.getValue()).hasSize(1);
-    assertThat(issuesSetCaptor.getValue().iterator().next()).isEqualTo(issue);
-    assertThat(assigneeByUuidCaptor.getValue()).hasSize(1);
-    assertThat(assigneeByUuidCaptor.getValue().get(user.getUuid())).isNotNull();
-    verify(notificationService).hasProjectSubscribersForTypes(project.uuid(), NOTIF_TYPES);
-    verify(notificationService).deliverEmails(singleton(issuesChangesNotification));
-    verify(notificationService).deliver(issuesChangesNotification);
-    verifyNoMoreInteractions(notificationService);
-  }
-
-  private DefaultIssue prepareIssue(long issueCreatedAt, UserDto user, ComponentDto project, ComponentDto file, RuleDto ruleDefinitionDto, RuleType type) {
-    DefaultIssue issue = newIssue(ruleDefinitionDto, project, file).setType(type).toDefaultIssue()
-      .setNew(false).setChanged(true).setSendNotifications(true).setCreationDate(new Date(issueCreatedAt)).setAssigneeUuid(user.getUuid());
-    protoIssueCache.newAppender().append(issue).close();
-    when(notificationService.hasProjectSubscribersForTypes(project.branchUuid(), NOTIF_TYPES)).thenReturn(true);
-    return issue;
-  }
-
-  @Test
-  public void send_issues_change_notification_on_branch() {
-    sendIssueChangeNotificationOnBranch(ANALYSE_DATE);
-  }
-
-  @Test
-  public void send_issues_change_notification_on_branch_even_if_issue_is_backdated() {
-    sendIssueChangeNotificationOnBranch(ANALYSE_DATE - FIVE_MINUTES_IN_MS);
-  }
-
-  private void sendIssueChangeNotificationOnBranch(long issueCreatedAt) {
-    ComponentDto project = newPrivateProjectDto();
-    ComponentDto branch = newBranchComponent(project, newBranchDto(project).setKey(BRANCH_NAME));
-    ComponentDto file = newFileDto(branch);
-    treeRootHolder.setRoot(builder(Type.PROJECT, 2).setKey(branch.getKey()).setName(branch.longName()).setUuid(branch.uuid()).addChildren(
-      builder(Type.FILE, 11).setKey(file.getKey()).setName(file.longName()).build()).build());
-    analysisMetadataHolder.setProject(Project.from(project));
-    RuleDto ruleDefinitionDto = newRule();
-    RuleType randomTypeExceptHotspot = RuleType.values()[nextInt(RuleType.values().length - 1)];
-    DefaultIssue issue = newIssue(ruleDefinitionDto, branch, file).setType(randomTypeExceptHotspot).toDefaultIssue()
-      .setNew(false)
-      .setChanged(true)
-      .setSendNotifications(true)
-      .setCreationDate(new Date(issueCreatedAt));
-    protoIssueCache.newAppender().append(issue).close();
-    when(notificationService.hasProjectSubscribersForTypes(project.uuid(), NOTIF_TYPES)).thenReturn(true);
-    IssuesChangesNotification issuesChangesNotification = mock(IssuesChangesNotification.class);
-    when(notificationFactory.newIssuesChangesNotification(anySet(), anyMap())).thenReturn(issuesChangesNotification);
-    analysisMetadataHolder.setBranch(newBranch(BranchType.BRANCH));
-
-    underTest.execute(new TestComputationStepContext());
-
-    verify(notificationFactory).newIssuesChangesNotification(issuesSetCaptor.capture(), assigneeByUuidCaptor.capture());
-    assertThat(issuesSetCaptor.getValue()).hasSize(1);
-    assertThat(issuesSetCaptor.getValue().iterator().next()).isEqualTo(issue);
-    assertThat(assigneeByUuidCaptor.getValue()).isEmpty();
-    verify(notificationService).hasProjectSubscribersForTypes(project.uuid(), NOTIF_TYPES);
-    verify(notificationService).deliverEmails(singleton(issuesChangesNotification));
-    verify(notificationService).deliver(issuesChangesNotification);
-    verifyNoMoreInteractions(notificationService);
-  }
-
-  @Test
-  public void sends_one_issue_change_notification_every_1000_issues() {
-    UserDto user = db.users().insertUser();
-    ComponentDto project = newPrivateProjectDto().setKey(PROJECT.getKey()).setLongName(PROJECT.getName());
-    ComponentDto file = newFileDto(project).setKey(FILE.getKey()).setLongName(FILE.getName());
-    RuleDto ruleDefinitionDto = newRule();
-    RuleType randomTypeExceptHotspot = RuleType.values()[nextInt(RuleType.values().length - 1)];
-    List<DefaultIssue> issues = IntStream.range(0, 2001 + new Random().nextInt(10))
-      .mapToObj(i -> newIssue(ruleDefinitionDto, project, file).setKee("uuid_" + i).setType(randomTypeExceptHotspot).toDefaultIssue()
-        .setNew(false).setChanged(true).setSendNotifications(true).setAssigneeUuid(user.getUuid()))
-      .toList();
-    DiskCache.CacheAppender cacheAppender = protoIssueCache.newAppender();
-    issues.forEach(cacheAppender::append);
-    cacheAppender.close();
-    analysisMetadataHolder.setProject(Project.from(project));
-    NewIssuesFactoryCaptor newIssuesFactoryCaptor = new NewIssuesFactoryCaptor(() -> mock(IssuesChangesNotification.class));
-    when(notificationFactory.newIssuesChangesNotification(anySet(), anyMap())).thenAnswer(newIssuesFactoryCaptor);
-    when(notificationService.hasProjectSubscribersForTypes(PROJECT.getUuid(), NOTIF_TYPES)).thenReturn(true);
-    when(notificationService.hasProjectSubscribersForTypes(project.uuid(), NOTIF_TYPES)).thenReturn(true);
-
-    underTest.execute(new TestComputationStepContext());
-
-    verify(notificationFactory, times(3)).newIssuesChangesNotification(anySet(), anyMap());
-    assertThat(newIssuesFactoryCaptor.issuesSetCaptor).hasSize(3);
-    assertThat(newIssuesFactoryCaptor.issuesSetCaptor.get(0)).hasSize(1000);
-    assertThat(newIssuesFactoryCaptor.issuesSetCaptor.get(1)).hasSize(1000);
-    assertThat(newIssuesFactoryCaptor.issuesSetCaptor.get(2)).hasSize(issues.size() - 2000);
-    assertThat(newIssuesFactoryCaptor.assigneeCacheCaptor)
-      .hasSize(3)
-      .containsOnly(newIssuesFactoryCaptor.assigneeCacheCaptor.iterator().next());
-    ArgumentCaptor<Collection> collectionCaptor = forClass(Collection.class);
-    verify(notificationService, times(3)).deliverEmails(collectionCaptor.capture());
-    assertThat(collectionCaptor.getAllValues()).hasSize(3);
-    assertThat(collectionCaptor.getAllValues().get(0)).hasSize(1);
-    assertThat(collectionCaptor.getAllValues().get(1)).hasSize(1);
-    assertThat(collectionCaptor.getAllValues().get(2)).hasSize(1);
-    verify(notificationService, times(3)).deliver(any(IssuesChangesNotification.class));
-  }
-
-  /**
-   * Since the very same Set object is passed to {@link NotificationFactory#newIssuesChangesNotification(Set, Map)} and
-   * reset between each call. We must make a copy of each argument to capture what's been passed to the factory.
-   * This is of course not supported by Mockito's {@link ArgumentCaptor} and we implement this ourselves with a
-   * {@link Answer}.
-   */
-  private static class NewIssuesFactoryCaptor implements Answer<Object> {
-    private final Supplier<IssuesChangesNotification> delegate;
-    private final List<Set<DefaultIssue>> issuesSetCaptor = new ArrayList<>();
-    private final List<Map<String, UserDto>> assigneeCacheCaptor = new ArrayList<>();
-
-    private NewIssuesFactoryCaptor(Supplier<IssuesChangesNotification> delegate) {
-      this.delegate = delegate;
-    }
-
-    @Override
-    public Object answer(InvocationOnMock t) {
-      Set<DefaultIssue> issuesSet = t.getArgument(0);
-      Map<String, UserDto> assigneeCatch = t.getArgument(1);
-      issuesSetCaptor.add(ImmutableSet.copyOf(issuesSet));
-      assigneeCacheCaptor.add(ImmutableMap.copyOf(assigneeCatch));
-      return delegate.get();
-    }
-  }
-
-  private NewIssuesNotification createNewIssuesNotificationMock() {
-    NewIssuesNotification notification = mock(NewIssuesNotification.class);
-    when(notification.setProject(any(), any(), any(), any())).thenReturn(notification);
-    when(notification.setProjectVersion(any())).thenReturn(notification);
-    when(notification.setAnalysisDate(any())).thenReturn(notification);
-    when(notification.setStatistics(any(), any())).thenReturn(notification);
-    when(notification.setDebt(any())).thenReturn(notification);
-    return notification;
-  }
-
-  private MyNewIssuesNotification createMyNewIssuesNotificationMock() {
-    MyNewIssuesNotification notification = mock(MyNewIssuesNotification.class);
-    when(notification.setAssignee(any(UserDto.class))).thenReturn(notification);
-    when(notification.setProject(any(), any(), any(), any())).thenReturn(notification);
-    when(notification.setProjectVersion(any())).thenReturn(notification);
-    when(notification.setAnalysisDate(any())).thenReturn(notification);
-    when(notification.setStatistics(any(), any())).thenReturn(notification);
-    when(notification.setDebt(any())).thenReturn(notification);
-    return notification;
-  }
-
-  private static Branch newBranch(BranchType type) {
-    Branch branch = mock(Branch.class);
-    when(branch.isMain()).thenReturn(false);
-    when(branch.getName()).thenReturn(BRANCH_NAME);
-    when(branch.getType()).thenReturn(type);
-    return branch;
-  }
-
-  private static Branch newPullRequest() {
-    Branch branch = mock(Branch.class);
-    when(branch.isMain()).thenReturn(false);
-    when(branch.getType()).thenReturn(PULL_REQUEST);
-    when(branch.getName()).thenReturn(BRANCH_NAME);
-    when(branch.getPullRequestKey()).thenReturn(PULL_REQUEST_ID);
-    return branch;
-  }
-
-  private ComponentDto setUpBranch(ComponentDto project, BranchType branchType) {
-    ComponentDto branch = newBranchComponent(project, newBranchDto(project, branchType).setKey(BRANCH_NAME));
-    ComponentDto file = newFileDto(branch);
-    treeRootHolder.setRoot(builder(Type.PROJECT, 2).setKey(branch.getKey()).setName(branch.longName()).setUuid(branch.uuid()).addChildren(
-      builder(Type.FILE, 11).setKey(file.getKey()).setName(file.longName()).build()).build());
-    return branch;
-  }
-
-  private static void verifyStatistics(TestComputationStepContext context, int expectedNewIssuesNotifications, int expectedMyNewIssuesNotifications,
-    int expectedIssueChangesNotifications) {
-    context.getStatistics().assertValue("newIssuesNotifs", expectedNewIssuesNotifications);
-    context.getStatistics().assertValue("myNewIssuesNotifs", expectedMyNewIssuesNotifications);
-    context.getStatistics().assertValue("changesNotifs", expectedIssueChangesNotifications);
-  }
-
-  @Override
-  protected ComputationStep step() {
-    return underTest;
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/UpdateNeedIssueSyncStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/UpdateNeedIssueSyncStepTest.java
deleted file mode 100644 (file)
index e21637e..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.ce.task.projectanalysis.step;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-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.step.TestComputationStepContext;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.BranchDto;
-import org.sonar.db.component.ComponentDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class UpdateNeedIssueSyncStepTest {
-  private static final Component PROJECT = ReportComponent.DUMB_PROJECT;
-
-  @Rule
-  public DbTester db = DbTester.create(System2.INSTANCE);
-
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule().setRoot(PROJECT);
-
-  private DbClient dbClient = db.getDbClient();
-
-  UpdateNeedIssueSyncStep underTest = new UpdateNeedIssueSyncStep(dbClient, treeRootHolder);
-
-  @Test
-  public void analysis_step_updates_need_issue_sync_flag() {
-    ComponentDto project = db.components()
-      .insertPrivateProject(c -> c.setUuid(PROJECT.getUuid()).setKey(PROJECT.getKey()));
-    dbClient.branchDao().updateNeedIssueSync(db.getSession(), PROJECT.getUuid(), true);
-    db.getSession().commit();
-
-    assertThat(dbClient.branchDao().selectByUuid(db.getSession(), project.uuid()))
-      .isNotEmpty()
-      .map(BranchDto::isNeedIssueSync)
-      .hasValue(true);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbClient.branchDao().selectByUuid(db.getSession(), project.uuid()))
-      .isNotEmpty()
-      .map(BranchDto::isNeedIssueSync)
-      .hasValue(false);
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/UpdateQualityProfilesLastUsedDateStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/UpdateQualityProfilesLastUsedDateStepTest.java
deleted file mode 100644 (file)
index 7746f34..0000000
+++ /dev/null
@@ -1,169 +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.Date;
-import java.util.stream.Collectors;
-import javax.annotation.CheckForNull;
-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.measure.Measure;
-import org.sonar.ce.task.projectanalysis.measure.MeasureRepositoryRule;
-import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule;
-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.RowNotFoundException;
-import org.sonar.db.qualityprofile.QProfileDto;
-import org.sonar.db.qualityprofile.QualityProfileDbTester;
-import org.sonar.server.qualityprofile.QPMeasureData;
-import org.sonar.server.qualityprofile.QualityProfile;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.sonar.api.measures.CoreMetrics.QUALITY_PROFILES;
-import static org.sonar.api.measures.CoreMetrics.QUALITY_PROFILES_KEY;
-import static org.sonar.db.qualityprofile.QualityProfileTesting.newQualityProfileDto;
-
-public class UpdateQualityProfilesLastUsedDateStepTest {
-  private static final long ANALYSIS_DATE = 1_123_456_789L;
-  private static final Component PROJECT = ReportComponent.DUMB_PROJECT;
-  private QProfileDto sonarWayJava = newProfile("sonar-way-java");
-  private QProfileDto sonarWayPhp = newProfile("sonar-way-php");
-  private QProfileDto myQualityProfile = newProfile("my-qp");
-
-
-  @Rule
-  public DbTester db = DbTester.create(System2.INSTANCE);
-
-  @Rule
-  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule().setAnalysisDate(ANALYSIS_DATE);
-
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule().setRoot(PROJECT);
-
-  @Rule
-  public MetricRepositoryRule metricRepository = new MetricRepositoryRule().add(QUALITY_PROFILES);
-
-  @Rule
-  public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
-
-  private DbClient dbClient = db.getDbClient();
-  private DbSession dbSession = db.getSession();
-  private QualityProfileDbTester qualityProfileDb = new QualityProfileDbTester(db);
-
-  UpdateQualityProfilesLastUsedDateStep underTest = new UpdateQualityProfilesLastUsedDateStep(dbClient, analysisMetadataHolder, treeRootHolder, metricRepository,
-    measureRepository);
-
-  @Test
-  public void doest_not_update_profiles_when_no_measure() {
-    qualityProfileDb.insert(sonarWayJava, sonarWayPhp, myQualityProfile);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertQualityProfileIsTheSame(sonarWayJava);
-    assertQualityProfileIsTheSame(sonarWayPhp);
-    assertQualityProfileIsTheSame(myQualityProfile);
-  }
-
-  @Test
-  public void update_profiles_defined_in_quality_profiles_measure() {
-    qualityProfileDb.insert(sonarWayJava, sonarWayPhp, myQualityProfile);
-
-    measureRepository.addRawMeasure(1, QUALITY_PROFILES_KEY, Measure.newMeasureBuilder().create(
-      toJson(sonarWayJava.getKee(), myQualityProfile.getKee())));
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertQualityProfileIsTheSame(sonarWayPhp);
-    assertQualityProfileIsUpdated(sonarWayJava);
-    assertQualityProfileIsUpdated(myQualityProfile);
-  }
-
-  @Test
-  public void ancestor_profiles_are_updated() {
-    // Parent profiles should be updated
-    QProfileDto rootProfile = newProfile("root");
-    QProfileDto parentProfile = newProfile("parent").setParentKee(rootProfile.getKee());
-    // Current profile => should be updated
-    QProfileDto currentProfile = newProfile("current").setParentKee(parentProfile.getKee());
-    // Child of current profile => should not be updated
-    QProfileDto childProfile = newProfile("child").setParentKee(currentProfile.getKee());
-    qualityProfileDb.insert(rootProfile, parentProfile, currentProfile, childProfile);
-
-    measureRepository.addRawMeasure(1, QUALITY_PROFILES_KEY, Measure.newMeasureBuilder().create(toJson(currentProfile.getKee())));
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertQualityProfileIsUpdated(rootProfile);
-    assertQualityProfileIsUpdated(parentProfile);
-    assertQualityProfileIsUpdated(currentProfile);
-    assertQualityProfileIsTheSame(childProfile);
-  }
-
-  @Test
-  public void fail_when_profile_is_linked_to_unknown_parent() {
-    QProfileDto currentProfile = newProfile("current").setParentKee("unknown");
-    qualityProfileDb.insert(currentProfile);
-
-    measureRepository.addRawMeasure(1, QUALITY_PROFILES_KEY, Measure.newMeasureBuilder().create(toJson(currentProfile.getKee())));
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(RowNotFoundException.class);
-  }
-
-  @Test
-  public void test_description() {
-    assertThat(underTest.getDescription()).isEqualTo("Update last usage date of quality profiles");
-  }
-
-  private static QProfileDto newProfile(String key) {
-    return newQualityProfileDto().setKee(key)
-      // profile has been used before the analysis
-      .setLastUsed(ANALYSIS_DATE - 10_000);
-  }
-
-  private void assertQualityProfileIsUpdated(QProfileDto qp) {
-    assertThat(selectLastUser(qp.getKee())).withFailMessage("Quality profile '%s' hasn't been updated. Value: %d", qp.getKee(), qp.getLastUsed()).isEqualTo(ANALYSIS_DATE);
-  }
-
-  private void assertQualityProfileIsTheSame(QProfileDto qp) {
-    assertThat(selectLastUser(qp.getKee())).isEqualTo(qp.getLastUsed());
-  }
-
-  @CheckForNull
-  private Long selectLastUser(String qualityProfileKey) {
-    return dbClient.qualityProfileDao().selectByUuid(dbSession, qualityProfileKey).getLastUsed();
-  }
-
-  private static String toJson(String... keys) {
-    return QPMeasureData.toJson(new QPMeasureData(
-      Arrays.stream(keys)
-        .map(key -> new QualityProfile(key, key, key, new Date()))
-        .collect(Collectors.toList())));
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ValidateProjectStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ValidateProjectStepTest.java
deleted file mode 100644 (file)
index c1e5539..0000000
+++ /dev/null
@@ -1,121 +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.Date;
-import javax.annotation.Nullable;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.DateUtils;
-import org.sonar.api.utils.MessageException;
-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.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.component.DefaultBranchImpl;
-import org.sonar.ce.task.projectanalysis.component.ReportComponent;
-import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
-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.ComponentTesting;
-import org.sonar.db.component.SnapshotTesting;
-
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME;
-
-public class ValidateProjectStepTest {
-  static long PAST_ANALYSIS_TIME = 1_420_088_400_000L; // 2015-01-01
-  static long DEFAULT_ANALYSIS_TIME = 1_433_131_200_000L; // 2015-06-01
-
-  static final String PROJECT_KEY = "PROJECT_KEY";
-  static final Branch DEFAULT_BRANCH = new DefaultBranchImpl(DEFAULT_MAIN_BRANCH_NAME);
-
-  @Rule
-  public DbTester db = DbTester.create(System2.INSTANCE);
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-  @Rule
-  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule()
-    .setAnalysisDate(new Date(DEFAULT_ANALYSIS_TIME))
-    .setBranch(DEFAULT_BRANCH);
-
-  private final DbClient dbClient = db.getDbClient();
-
-  private final ValidateProjectStep underTest = new ValidateProjectStep(dbClient, treeRootHolder, analysisMetadataHolder);
-
-  @Test
-  public void not_fail_if_analysis_date_is_after_last_analysis() {
-    ComponentDto project = db.components().insertPrivateProject("ABCD", c -> c.setKey(PROJECT_KEY));
-    dbClient.snapshotDao().insert(db.getSession(), SnapshotTesting.newAnalysis(project).setCreatedAt(PAST_ANALYSIS_TIME));
-    db.getSession().commit();
-
-    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").setKey(PROJECT_KEY).build());
-
-    underTest.execute(new TestComputationStepContext());
-  }
-
-  @Test
-  public void fail_if_analysis_date_is_before_last_analysis() {
-    analysisMetadataHolder.setAnalysisDate(DateUtils.parseDate("2015-01-01"));
-
-    ComponentDto project = db.components().insertPrivateProject("ABCD", c -> c.setKey(PROJECT_KEY));
-    dbClient.snapshotDao().insert(db.getSession(), SnapshotTesting.newAnalysis(project).setCreatedAt(1433131200000L)); // 2015-06-01
-    db.getSession().commit();
-
-    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1).setUuid("ABCD").setKey(PROJECT_KEY).build());
-
-    var stepContext = new TestComputationStepContext();
-    assertThatThrownBy(() -> underTest.execute(stepContext))
-      .isInstanceOf(MessageException.class)
-      .hasMessageContainingAll("Validation of project failed:",
-        "Date of analysis cannot be older than the date of the last known analysis on this project. Value: ",
-        "Latest analysis: ");
-  }
-
-  @Test
-  public void fail_when_project_key_is_invalid() {
-    ComponentDto project = db.components().insertPrivateProject(p -> p.setKey("inv$lid!"));
-    db.components().insertSnapshot(project, a -> a.setCreatedAt(PAST_ANALYSIS_TIME));
-    treeRootHolder.setRoot(ReportComponent.builder(Component.Type.PROJECT, 1)
-      .setUuid(project.uuid())
-      .setKey(project.getKey())
-      .build());
-
-    var stepContext = new TestComputationStepContext();
-    assertThatThrownBy(() -> underTest.execute(stepContext))
-      .isInstanceOf(MessageException.class)
-      .hasMessageContainingAll("Validation of project failed:",
-        "The project key ‘inv$lid!’ contains invalid characters.",
-        "Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.",
-        "You should update the project key with the expected format.");
-  }
-
-  private void setBranch(BranchType type, @Nullable String mergeBranchUuid) {
-    Branch branch = mock(Branch.class);
-    when(branch.getType()).thenReturn(type);
-    when(branch.getReferenceBranchUuid()).thenReturn(mergeBranchUuid);
-    analysisMetadataHolder.setBranch(branch);
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ViewsPersistAnalysisStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ViewsPersistAnalysisStepTest.java
deleted file mode 100644 (file)
index 272c4cf..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.ce.task.projectanalysis.step;
-
-import java.util.List;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.DateUtils;
-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.TreeRootHolderRule;
-import org.sonar.ce.task.projectanalysis.component.ViewsComponent;
-import org.sonar.ce.task.projectanalysis.period.Period;
-import org.sonar.ce.task.projectanalysis.period.PeriodHolderRule;
-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.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.component.SnapshotDto;
-import org.sonar.db.component.SnapshotQuery;
-
-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.PROJECT_VIEW;
-import static org.sonar.ce.task.projectanalysis.component.Component.Type.SUBVIEW;
-import static org.sonar.ce.task.projectanalysis.component.Component.Type.VIEW;
-import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
-
-public class ViewsPersistAnalysisStepTest extends BaseStepTest {
-
-  private static final String ANALYSIS_UUID = "U1";
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-  @Rule
-  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
-  @Rule
-  public PeriodHolderRule periodsHolder = new PeriodHolderRule();
-
-  private System2 system2 = mock(System2.class);
-  private DbClient dbClient = dbTester.getDbClient();
-  private long analysisDate;
-  private long now;
-  private PersistAnalysisStep underTest;
-
-  @Before
-  public void setup() {
-    analysisDate = DateUtils.parseDateQuietly("2015-06-01").getTime();
-    analysisMetadataHolder.setUuid(ANALYSIS_UUID);
-    analysisMetadataHolder.setAnalysisDate(analysisDate);
-
-    now = DateUtils.parseDateQuietly("2015-06-02").getTime();
-
-    when(system2.now()).thenReturn(now);
-
-    underTest = new PersistAnalysisStep(system2, dbClient, treeRootHolder, analysisMetadataHolder, periodsHolder);
-
-    // initialize PeriodHolder to empty by default
-    periodsHolder.setPeriod(null);
-  }
-
-  @Override
-  protected ComputationStep step() {
-    return underTest;
-  }
-
-  @Test
-  public void persist_analysis() {
-    ComponentDto viewDto = save(ComponentTesting.newPortfolio("UUID_VIEW").setKey("KEY_VIEW"));
-    save(ComponentTesting.newSubPortfolio(viewDto, "UUID_SUBVIEW", "KEY_SUBVIEW"));
-    save(newPrivateProjectDto("proj"));
-    dbTester.getSession().commit();
-
-    Component projectView = ViewsComponent.builder(PROJECT_VIEW, "KEY_PROJECT_COPY").setUuid("UUID_PROJECT_COPY").build();
-    Component subView = ViewsComponent.builder(SUBVIEW, "KEY_SUBVIEW").setUuid("UUID_SUBVIEW").addChildren(projectView).build();
-    Component view = ViewsComponent.builder(VIEW, "KEY_VIEW").setUuid("UUID_VIEW").addChildren(subView).build();
-    treeRootHolder.setRoot(view);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dbTester.countRowsOfTable("snapshots")).isOne();
-
-    SnapshotDto viewSnapshot = getUnprocessedSnapshot(viewDto.uuid());
-    assertThat(viewSnapshot.getUuid()).isEqualTo(ANALYSIS_UUID);
-    assertThat(viewSnapshot.getComponentUuid()).isEqualTo(view.getUuid());
-    assertThat(viewSnapshot.getProjectVersion()).isNull();
-    assertThat(viewSnapshot.getLast()).isFalse();
-    assertThat(viewSnapshot.getStatus()).isEqualTo("U");
-    assertThat(viewSnapshot.getCreatedAt()).isEqualTo(analysisDate);
-    assertThat(viewSnapshot.getBuildDate()).isEqualTo(now);
-  }
-
-  @Test
-  public void persist_snapshots_with_new_code_period() {
-    ComponentDto viewDto = save(ComponentTesting.newPortfolio("UUID_VIEW").setKey("KEY_VIEW"));
-    ComponentDto subViewDto = save(ComponentTesting.newSubPortfolio(viewDto, "UUID_SUBVIEW", "KEY_SUBVIEW"));
-    dbTester.getSession().commit();
-
-    Component subView = ViewsComponent.builder(SUBVIEW, "KEY_SUBVIEW").setUuid("UUID_SUBVIEW").build();
-    Component view = ViewsComponent.builder(VIEW, "KEY_VIEW").setUuid("UUID_VIEW").addChildren(subView).build();
-    treeRootHolder.setRoot(view);
-
-    periodsHolder.setPeriod(new Period("NUMBER_OF_DAYS", "30", analysisDate));
-
-    underTest.execute(new TestComputationStepContext());
-
-    SnapshotDto viewSnapshot = getUnprocessedSnapshot(viewDto.uuid());
-    assertThat(viewSnapshot.getPeriodMode()).isEqualTo("NUMBER_OF_DAYS");
-    assertThat(viewSnapshot.getPeriodDate()).isEqualTo(analysisDate);
-    assertThat(viewSnapshot.getPeriodModeParameter()).isNotNull();
-  }
-
-  private ComponentDto save(ComponentDto componentDto) {
-    return dbTester.components().insertComponent(componentDto);
-  }
-
-  private SnapshotDto getUnprocessedSnapshot(String componentUuid) {
-    List<SnapshotDto> projectSnapshots = dbClient.snapshotDao().selectAnalysesByQuery(dbTester.getSession(),
-      new SnapshotQuery().setComponentUuid(componentUuid).setIsLast(false).setStatus(SnapshotDto.STATUS_UNPROCESSED));
-    assertThat(projectSnapshots).hasSize(1);
-    return projectSnapshots.get(0);
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ViewsPersistComponentsStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ViewsPersistComponentsStepTest.java
deleted file mode 100644 (file)
index 5a8adb1..0000000
+++ /dev/null
@@ -1,554 +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.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Random;
-import java.util.stream.Stream;
-import javax.annotation.Nullable;
-import org.junit.Before;
-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.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
-import org.sonar.ce.task.projectanalysis.component.BranchPersister;
-import org.sonar.ce.task.projectanalysis.component.Component;
-import org.sonar.ce.task.projectanalysis.component.DefaultBranchImpl;
-import org.sonar.ce.task.projectanalysis.component.MutableDisabledComponentsHolder;
-import org.sonar.ce.task.projectanalysis.component.ProjectPersister;
-import org.sonar.ce.task.projectanalysis.component.ProjectViewAttributes;
-import org.sonar.ce.task.projectanalysis.component.SubViewAttributes;
-import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
-import org.sonar.ce.task.projectanalysis.component.ViewAttributes;
-import org.sonar.ce.task.projectanalysis.component.ViewsComponent;
-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.db.component.ComponentDbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.ce.task.projectanalysis.component.ViewAttributes.Type.APPLICATION;
-import static org.sonar.ce.task.projectanalysis.component.ViewAttributes.Type.PORTFOLIO;
-import static org.sonar.ce.task.projectanalysis.component.ViewsComponent.builder;
-import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME;
-import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
-import static org.sonar.db.component.ComponentTesting.newProjectCopy;
-import static org.sonar.db.component.ComponentTesting.newSubPortfolio;
-
-public class ViewsPersistComponentsStepTest extends BaseStepTest {
-
-  private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
-
-  private static final String VIEW_KEY = "VIEW_KEY";
-  private static final String VIEW_NAME = "VIEW_NAME";
-  private static final String VIEW_DESCRIPTION = "view description";
-  private static final String VIEW_UUID = "VIEW_UUID";
-  private static final String SUBVIEW_1_KEY = "SUBVIEW_1_KEY";
-  private static final String SUBVIEW_1_NAME = "SUBVIEW_1_NAME";
-  private static final String SUBVIEW_1_DESCRIPTION = "subview 1 description";
-  private static final String SUBVIEW_1_UUID = "SUBVIEW_1_UUID";
-  private static final String PROJECT_VIEW_1_KEY = "PV1_KEY";
-  private static final String PROJECT_VIEW_1_NAME = "PV1_NAME";
-  private static final String PROJECT_VIEW_1_UUID = "PV1_UUID";
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-  @Rule
-  public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
-
-  private final System2 system2 = mock(System2.class);
-  private final DbClient dbClient = dbTester.getDbClient();
-  private Date now;
-  private final ComponentDbTester componentDbTester = new ComponentDbTester(dbTester);
-  private final MutableDisabledComponentsHolder disabledComponentsHolder = mock(MutableDisabledComponentsHolder.class, RETURNS_DEEP_STUBS);
-  private PersistComponentsStep underTest;
-
-  @Before
-  public void setup() throws Exception {
-    now = DATE_FORMAT.parse("2015-06-02");
-    when(system2.now()).thenReturn(now.getTime());
-
-    analysisMetadataHolder.setBranch(new DefaultBranchImpl(DEFAULT_MAIN_BRANCH_NAME));
-    BranchPersister branchPersister = mock(BranchPersister.class);
-    ProjectPersister projectPersister = mock(ProjectPersister.class);
-    underTest = new PersistComponentsStep(dbClient, treeRootHolder, system2, disabledComponentsHolder, analysisMetadataHolder, branchPersister, projectPersister);
-  }
-
-  @Override
-  protected ComputationStep step() {
-    return underTest;
-  }
-
-  @Test
-  public void persist_empty_view() {
-    treeRootHolder.setRoot(createViewBuilder(PORTFOLIO).build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertRowsCountInTableProjects(1);
-
-    ComponentDto projectDto = getComponentFromDb(VIEW_KEY);
-    assertDtoIsView(projectDto);
-  }
-
-  @Test
-  public void persist_existing_empty_view() {
-    // most of the time view already exists since its supposed to be created when config is uploaded
-    persistComponents(newViewDto());
-
-    treeRootHolder.setRoot(createViewBuilder(PORTFOLIO).build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertRowsCountInTableProjects(1);
-
-    assertDtoNotUpdated(VIEW_KEY);
-  }
-
-  @Test
-  public void persist_view_with_projectView() {
-    ComponentDto project = ComponentTesting.newPrivateProjectDto();
-    persistComponents(project);
-
-    treeRootHolder.setRoot(
-      createViewBuilder(PORTFOLIO)
-        .addChildren(createProjectView1Builder(project, null).build())
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertRowsCountInTableProjects(3);
-
-    ComponentDto viewDto = getComponentFromDb(VIEW_KEY);
-    assertDtoIsView(viewDto);
-
-    ComponentDto pv1Dto = getComponentFromDb(PROJECT_VIEW_1_KEY);
-    assertDtoIsProjectView1(pv1Dto, viewDto, viewDto, project);
-  }
-
-  @Test
-  public void persist_application_with_projectView() {
-    ComponentDto project = ComponentTesting.newPrivateProjectDto();
-    persistComponents(project);
-
-    treeRootHolder.setRoot(
-      createViewBuilder(APPLICATION)
-        .addChildren(createProjectView1Builder(project, null).build())
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertRowsCountInTableProjects(3);
-
-    ComponentDto applicationDto = getComponentFromDb(VIEW_KEY);
-    assertDtoIsApplication(applicationDto);
-
-    ComponentDto pv1Dto = getComponentFromDb(PROJECT_VIEW_1_KEY);
-    assertDtoIsProjectView1(pv1Dto, applicationDto, applicationDto, project);
-  }
-
-  @Test
-  public void persist_empty_subview() {
-    treeRootHolder.setRoot(
-      createViewBuilder(PORTFOLIO)
-        .addChildren(
-          createSubView1Builder(null).build())
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertRowsCountInTableProjects(2);
-
-    ComponentDto viewDto = getComponentFromDb(VIEW_KEY);
-    assertDtoIsView(viewDto);
-
-    ComponentDto sv1Dto = getComponentFromDb(SUBVIEW_1_KEY);
-    assertDtoIsSubView1(viewDto, sv1Dto);
-  }
-
-  @Test
-  public void persist_empty_subview_having_original_view_uuid() {
-    treeRootHolder.setRoot(
-      createViewBuilder(PORTFOLIO)
-        .addChildren(
-          createSubView1Builder("ORIGINAL_UUID").build())
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertRowsCountInTableProjects(2);
-
-    ComponentDto subView = getComponentFromDb(SUBVIEW_1_KEY);
-    assertThat(subView.getCopyComponentUuid()).isEqualTo("ORIGINAL_UUID");
-  }
-
-  @Test
-  public void persist_existing_empty_subview_under_existing_view() {
-    ComponentDto viewDto = newViewDto();
-    persistComponents(viewDto);
-    persistComponents(ComponentTesting.newSubPortfolio(viewDto, SUBVIEW_1_UUID, SUBVIEW_1_KEY).setName(SUBVIEW_1_NAME));
-
-    treeRootHolder.setRoot(
-      createViewBuilder(PORTFOLIO)
-        .addChildren(
-          createSubView1Builder(null).build())
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertRowsCountInTableProjects(2);
-
-    assertDtoNotUpdated(VIEW_KEY);
-    assertDtoNotUpdated(SUBVIEW_1_KEY);
-  }
-
-  @Test
-  public void persist_empty_subview_under_existing_view() {
-    persistComponents(newViewDto());
-
-    treeRootHolder.setRoot(
-      createViewBuilder(PORTFOLIO)
-        .addChildren(
-          createSubView1Builder(null).build())
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertRowsCountInTableProjects(2);
-
-    assertDtoNotUpdated(VIEW_KEY);
-    assertDtoIsSubView1(getComponentFromDb(VIEW_KEY), getComponentFromDb(SUBVIEW_1_KEY));
-  }
-
-  @Test
-  public void persist_project_view_under_subview() {
-    ComponentDto project = ComponentTesting.newPrivateProjectDto();
-    persistComponents(project);
-
-    treeRootHolder.setRoot(
-      createViewBuilder(PORTFOLIO)
-        .addChildren(
-          createSubView1Builder(null)
-            .addChildren(
-              createProjectView1Builder(project, null).build())
-            .build())
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertRowsCountInTableProjects(4);
-
-    ComponentDto viewDto = getComponentFromDb(VIEW_KEY);
-    assertDtoIsView(viewDto);
-    ComponentDto subView1Dto = getComponentFromDb(SUBVIEW_1_KEY);
-    assertDtoIsSubView1(viewDto, subView1Dto);
-    ComponentDto pv1Dto = getComponentFromDb(PROJECT_VIEW_1_KEY);
-    assertDtoIsProjectView1(pv1Dto, viewDto, subView1Dto, project);
-  }
-
-  @Test
-  public void update_view_name_and_longName() {
-    ComponentDto viewDto = newViewDto().setLongName("another long name").setCreatedAt(now);
-    persistComponents(viewDto);
-
-    treeRootHolder.setRoot(createViewBuilder(PORTFOLIO).build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    // commit functional transaction -> copies B-fields to A-fields
-    dbClient.componentDao().applyBChangesForBranchUuid(dbTester.getSession(), viewDto.uuid());
-    dbTester.commit();
-
-    assertRowsCountInTableProjects(1);
-    ComponentDto newViewDto = getComponentFromDb(VIEW_KEY);
-    assertDtoIsView(newViewDto);
-  }
-
-  @Test
-  public void update_project_view() {
-    ComponentDto view = newViewDto();
-    ComponentDto project = ComponentTesting.newPrivateProjectDto();
-    persistComponents(view, project);
-    ComponentDto projectView = ComponentTesting.newProjectCopy(PROJECT_VIEW_1_UUID, project, view)
-      .setKey(PROJECT_VIEW_1_KEY)
-      .setName("Old name")
-      .setCreatedAt(now);
-    persistComponents(projectView);
-
-    treeRootHolder.setRoot(
-      createViewBuilder(PORTFOLIO)
-        .addChildren(createProjectView1Builder(project, null).build())
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    // commit functional transaction -> copies B-fields to A-fields
-    dbClient.componentDao().applyBChangesForBranchUuid(dbTester.getSession(), view.uuid());
-    dbTester.commit();
-
-    assertRowsCountInTableProjects(3);
-    ComponentDto pv1Dto = getComponentFromDb(PROJECT_VIEW_1_KEY);
-    assertDtoIsProjectView1(pv1Dto, view, view, project);
-  }
-
-  @Test
-  public void update_copy_component_uuid_of_project_view() {
-    ComponentDto view = newViewDto();
-    ComponentDto project1 = newPrivateProjectDto("P1");
-    ComponentDto project2 = newPrivateProjectDto("P2");
-    persistComponents(view, project1, project2);
-
-    // Project view in DB is associated to project1
-    ComponentDto projectView = ComponentTesting.newProjectCopy(PROJECT_VIEW_1_UUID, project1, view)
-      .setKey(PROJECT_VIEW_1_KEY)
-      .setCreatedAt(now);
-    persistComponents(projectView);
-
-    treeRootHolder.setRoot(
-      createViewBuilder(PORTFOLIO)
-        // Project view in the View is linked to the first project2
-        .addChildren(createProjectView1Builder(project2, null).build())
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    // commit functional transaction -> copies B-fields to A-fields
-    dbClient.componentDao().applyBChangesForBranchUuid(dbTester.getSession(), view.uuid());
-    dbTester.commit();
-
-    ComponentDto pv1Dto = getComponentFromDb(PROJECT_VIEW_1_KEY);
-    // Project view should now be linked to project2
-    assertDtoIsProjectView1(pv1Dto, view, view, project2);
-  }
-
-  @Test
-  public void update_copy_component_uuid_of_sub_view() {
-    ComponentDto view = newViewDto();
-    ComponentDto subView = newSubViewDto(view).setCopyComponentUuid("OLD_COPY");
-    persistComponents(view, subView);
-
-    treeRootHolder.setRoot(
-      createViewBuilder(PORTFOLIO)
-        .addChildren(
-          createSubView1Builder("NEW_COPY").build())
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    // commit functional transaction -> copies B-fields to A-fields
-    dbClient.componentDao().applyBChangesForBranchUuid(dbTester.getSession(), view.uuid());
-    dbTester.commit();
-
-    ComponentDto subViewReloaded = getComponentFromDb(SUBVIEW_1_KEY);
-    assertThat(subViewReloaded.getCopyComponentUuid()).isEqualTo("NEW_COPY");
-  }
-
-  @Test
-  public void persists_new_components_as_public_if_root_does_not_exist_yet_out_of_functional_transaction() {
-    ComponentDto project = dbTester.components().insertComponent(ComponentTesting.newPrivateProjectDto());
-    treeRootHolder.setRoot(
-      createViewBuilder(PORTFOLIO)
-        .addChildren(
-          createSubView1Builder(null)
-            .addChildren(
-              createProjectView1Builder(project, null).build())
-            .build())
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    Stream.of(VIEW_UUID, SUBVIEW_1_UUID, PROJECT_VIEW_1_UUID)
-      .forEach(uuid -> assertThat(dbClient.componentDao().selectByUuid(dbTester.getSession(), uuid).get().isPrivate()).isFalse());
-  }
-
-  @Test
-  public void persists_new_components_with_visibility_of_root_in_db_out_of_functional_transaction() {
-    boolean isRootPrivate = new Random().nextBoolean();
-    ComponentDto project = dbTester.components().insertComponent(ComponentTesting.newPrivateProjectDto());
-    ComponentDto view = newViewDto().setUuid(VIEW_UUID).setKey(VIEW_KEY).setName("View").setPrivate(isRootPrivate);
-    dbTester.components().insertComponent(view);
-    treeRootHolder.setRoot(
-      createViewBuilder(PORTFOLIO)
-        .addChildren(
-          createSubView1Builder(null)
-            .addChildren(
-              createProjectView1Builder(project, null).build())
-            .build())
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    Stream.of(VIEW_UUID, SUBVIEW_1_UUID, PROJECT_VIEW_1_UUID)
-      .forEach(uuid -> assertThat(dbClient.componentDao().selectByUuid(dbTester.getSession(), uuid).get().isPrivate())
-        .describedAs("for uuid " + uuid)
-        .isEqualTo(isRootPrivate));
-  }
-
-  @Test
-  public void persists_existing_components_with_visibility_of_root_in_db_out_of_functional_transaction() {
-    boolean isRootPrivate = new Random().nextBoolean();
-    ComponentDto project = dbTester.components().insertComponent(ComponentTesting.newPrivateProjectDto());
-    ComponentDto view = newViewDto().setUuid(VIEW_UUID).setKey(VIEW_KEY).setName("View").setPrivate(isRootPrivate);
-    dbTester.components().insertComponent(view);
-    ComponentDto subView = newSubPortfolio(view).setUuid("BCDE").setKey("MODULE").setPrivate(!isRootPrivate);
-    dbTester.components().insertComponent(subView);
-    dbTester.components().insertComponent(newProjectCopy("DEFG", project, view).setKey("DIR").setPrivate(isRootPrivate));
-    treeRootHolder.setRoot(
-      createViewBuilder(PORTFOLIO)
-        .addChildren(
-          createSubView1Builder(null)
-            .addChildren(
-              createProjectView1Builder(project, null).build())
-            .build())
-        .build());
-
-    underTest.execute(new TestComputationStepContext());
-
-    Stream.of(VIEW_UUID, SUBVIEW_1_UUID, PROJECT_VIEW_1_UUID, subView.uuid(), "DEFG")
-      .forEach(uuid -> assertThat(dbClient.componentDao().selectByUuid(dbTester.getSession(), uuid).get().isPrivate())
-        .describedAs("for uuid " + uuid)
-        .isEqualTo(isRootPrivate));
-  }
-
-  private static ViewsComponent.Builder createViewBuilder(ViewAttributes.Type viewType) {
-    return builder(Component.Type.VIEW, VIEW_KEY)
-      .setUuid(VIEW_UUID)
-      .setName(VIEW_NAME)
-      .setDescription(VIEW_DESCRIPTION)
-      .setViewAttributes(new ViewAttributes(viewType));
-  }
-
-  private ViewsComponent.Builder createSubView1Builder(@Nullable String originalViewUuid) {
-    return builder(Component.Type.SUBVIEW, SUBVIEW_1_KEY)
-      .setUuid(SUBVIEW_1_UUID)
-      .setName(SUBVIEW_1_NAME)
-      .setDescription(SUBVIEW_1_DESCRIPTION)
-      .setSubViewAttributes(new SubViewAttributes(originalViewUuid));
-  }
-
-  private static ViewsComponent.Builder createProjectView1Builder(ComponentDto project, Long analysisDate) {
-    return builder(Component.Type.PROJECT_VIEW, PROJECT_VIEW_1_KEY)
-      .setUuid(PROJECT_VIEW_1_UUID)
-      .setName(PROJECT_VIEW_1_NAME)
-      .setDescription("project view description is not persisted")
-      .setProjectViewAttributes(new ProjectViewAttributes(project.uuid(), project.getKey(), analysisDate, null));
-  }
-
-  private void persistComponents(ComponentDto... componentDtos) {
-    componentDbTester.insertComponents(componentDtos);
-  }
-
-  private ComponentDto getComponentFromDb(String componentKey) {
-    return dbClient.componentDao().selectByKey(dbTester.getSession(), componentKey).get();
-  }
-
-  private void assertRowsCountInTableProjects(int rowCount) {
-    assertThat(dbTester.countRowsOfTable("components")).isEqualTo(rowCount);
-  }
-
-  private void assertDtoNotUpdated(String componentKey) {
-    assertThat(getComponentFromDb(componentKey).getCreatedAt()).isNotEqualTo(now);
-  }
-
-  private ComponentDto newViewDto() {
-    return ComponentTesting.newPortfolio(VIEW_UUID)
-      .setKey(VIEW_KEY)
-      .setName(VIEW_NAME);
-  }
-
-  private ComponentDto newSubViewDto(ComponentDto rootView) {
-    return ComponentTesting.newSubPortfolio(rootView, SUBVIEW_1_UUID, SUBVIEW_1_KEY)
-      .setName(SUBVIEW_1_NAME);
-  }
-
-  /**
-   * Assertions to verify the DTO created from {@link #createViewBuilder(ViewAttributes.Type)} ()}
-   */
-  private void assertDtoIsView(ComponentDto dto) {
-    assertThat(dto.name()).isEqualTo(VIEW_NAME);
-    assertThat(dto.longName()).isEqualTo(VIEW_NAME);
-    assertThat(dto.description()).isEqualTo(VIEW_DESCRIPTION);
-    assertThat(dto.path()).isNull();
-    assertThat(dto.uuid()).isEqualTo(VIEW_UUID);
-    assertThat(dto.branchUuid()).isEqualTo(VIEW_UUID);
-    assertThat(dto.qualifier()).isEqualTo(Qualifiers.VIEW);
-    assertThat(dto.scope()).isEqualTo(Scopes.PROJECT);
-    assertThat(dto.getCopyComponentUuid()).isNull();
-    assertThat(dto.getCreatedAt()).isEqualTo(now);
-  }
-
-  /**
-   * Assertions to verify the DTO created from {@link #createViewBuilder(ViewAttributes.Type)} ()}
-   */
-  private void assertDtoIsApplication(ComponentDto dto) {
-    assertThat(dto.name()).isEqualTo(VIEW_NAME);
-    assertThat(dto.longName()).isEqualTo(VIEW_NAME);
-    assertThat(dto.description()).isEqualTo(VIEW_DESCRIPTION);
-    assertThat(dto.path()).isNull();
-    assertThat(dto.uuid()).isEqualTo(VIEW_UUID);
-    assertThat(dto.branchUuid()).isEqualTo(VIEW_UUID);
-    assertThat(dto.qualifier()).isEqualTo(Qualifiers.APP);
-    assertThat(dto.scope()).isEqualTo(Scopes.PROJECT);
-    assertThat(dto.getCopyComponentUuid()).isNull();
-    assertThat(dto.getCreatedAt()).isEqualTo(now);
-  }
-
-  /**
-   * Assertions to verify the DTO created from {@link #createProjectView1Builder(ComponentDto, Long)}
-   */
-  private void assertDtoIsSubView1(ComponentDto viewDto, ComponentDto sv1Dto) {
-    assertThat(sv1Dto.name()).isEqualTo(SUBVIEW_1_NAME);
-    assertThat(sv1Dto.longName()).isEqualTo(SUBVIEW_1_NAME);
-    assertThat(sv1Dto.description()).isEqualTo(SUBVIEW_1_DESCRIPTION);
-    assertThat(sv1Dto.path()).isNull();
-    assertThat(sv1Dto.uuid()).isEqualTo(SUBVIEW_1_UUID);
-    assertThat(sv1Dto.branchUuid()).isEqualTo(viewDto.uuid());
-    assertThat(sv1Dto.qualifier()).isEqualTo(Qualifiers.SUBVIEW);
-    assertThat(sv1Dto.scope()).isEqualTo(Scopes.PROJECT);
-    assertThat(sv1Dto.getCopyComponentUuid()).isNull();
-    assertThat(sv1Dto.getCreatedAt()).isEqualTo(now);
-  }
-
-  private void assertDtoIsProjectView1(ComponentDto pv1Dto, ComponentDto viewDto, ComponentDto parentViewDto, ComponentDto project) {
-    assertThat(pv1Dto.name()).isEqualTo(PROJECT_VIEW_1_NAME);
-    assertThat(pv1Dto.longName()).isEqualTo(PROJECT_VIEW_1_NAME);
-    assertThat(pv1Dto.description()).isNull();
-    assertThat(pv1Dto.path()).isNull();
-    assertThat(pv1Dto.uuid()).isEqualTo(PROJECT_VIEW_1_UUID);
-    assertThat(pv1Dto.branchUuid()).isEqualTo(viewDto.uuid());
-    assertThat(pv1Dto.qualifier()).isEqualTo(Qualifiers.PROJECT);
-    assertThat(pv1Dto.scope()).isEqualTo(Scopes.FILE);
-    assertThat(pv1Dto.getCopyComponentUuid()).isEqualTo(project.uuid());
-    assertThat(pv1Dto.getCreatedAt()).isEqualTo(now);
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/taskprocessor/AuditHousekeepingFrequencyHelperTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/taskprocessor/AuditHousekeepingFrequencyHelperTest.java
deleted file mode 100644 (file)
index 45f1f7f..0000000
+++ /dev/null
@@ -1,104 +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 com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.sonar.api.impl.utils.TestSystem2;
-import org.sonar.api.utils.System2;
-import org.sonar.core.config.Frequency;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.property.PropertiesDao;
-import org.sonar.db.property.PropertyDto;
-
-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.core.config.PurgeConstants.AUDIT_HOUSEKEEPING_FREQUENCY;
-import static org.sonar.core.config.PurgeProperties.DEFAULT_FREQUENCY;
-
-@RunWith(DataProviderRunner.class)
-public class AuditHousekeepingFrequencyHelperTest {
-  private static final long NOW = 10_000_000_000L;
-
-  private final DbClient dbClient = mock(DbClient.class);
-  private final DbSession dbSession = mock(DbSession.class);
-  private final PropertiesDao propertiesDao = mock(PropertiesDao.class);
-  private final System2 system2 = new TestSystem2().setNow(NOW);
-  private final AuditHousekeepingFrequencyHelper underTest = new AuditHousekeepingFrequencyHelper(system2);
-
-  @Test
-  @UseDataProvider("frequencyOptions")
-  public void getThresholdDate(Frequency frequency) {
-    long result = underTest.getThresholdDate(frequency.getDescription());
-
-
-    long expected = Instant.ofEpochMilli(system2.now())
-      .minus(frequency.getDays(), ChronoUnit.DAYS)
-      .toEpochMilli();
-
-    assertThat(result).isEqualTo(expected);
-  }
-
-  @Test
-  public void getThresholdDateForUnknownFrequencyFails() {
-    assertThatThrownBy(() -> underTest.getThresholdDate("Lalala"))
-      .isInstanceOf(IllegalArgumentException.class)
-      .hasMessage("Unsupported frequency: Lalala");
-  }
-
-  @Test
-  public void getHouseKeepingFrequency() {
-    String value = "Weekly";
-    PropertyDto propertyDto = new PropertyDto().setKey(AUDIT_HOUSEKEEPING_FREQUENCY).setValue(value);
-    when(dbClient.propertiesDao()).thenReturn(propertiesDao);
-    when(propertiesDao
-      .selectGlobalProperty(dbSession, AUDIT_HOUSEKEEPING_FREQUENCY))
-      .thenReturn(propertyDto);
-    assertThat(underTest.getHouseKeepingFrequency(dbClient, dbSession).getValue()).isEqualTo(value);
-  }
-
-  @Test
-  public void getDefaultHouseKeepingFrequencyWhenNotSet() {
-    when(dbClient.propertiesDao()).thenReturn(propertiesDao);
-    when(propertiesDao
-      .selectGlobalProperty(dbSession, AUDIT_HOUSEKEEPING_FREQUENCY))
-      .thenReturn(null);
-    assertThat(underTest.getHouseKeepingFrequency(dbClient, dbSession).getValue())
-      .isEqualTo(DEFAULT_FREQUENCY);
-  }
-
-  @DataProvider
-  public static Object[][] frequencyOptions() {
-    return new Object[][] {
-      {Frequency.WEEKLY},
-      {Frequency.MONTHLY},
-      {Frequency.TRIMESTRIAL},
-      {Frequency.YEARLY}
-    };
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/taskprocessor/AuditPurgeStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/taskprocessor/AuditPurgeStepTest.java
deleted file mode 100644 (file)
index 5775125..0000000
+++ /dev/null
@@ -1,99 +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.time.Instant;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
-import org.junit.Before;
-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.audit.AuditDto;
-import org.sonar.db.audit.AuditTesting;
-import org.sonar.db.property.PropertyDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.core.config.Frequency.MONTHLY;
-import static org.sonar.core.config.PurgeConstants.AUDIT_HOUSEKEEPING_FREQUENCY;
-
-public class AuditPurgeStepTest {
-  private final static long NOW = 1_400_000_000_000L;
-  private final static long BEFORE = 1_300_000_000_000L;
-  private final static long LATER = 1_500_000_000_000L;
-  private final static ZonedDateTime thresholdDate = Instant.ofEpochMilli(NOW)
-    .atZone(ZoneId.systemDefault());
-  private final static PropertyDto FREQUENCY_PROPERTY = new PropertyDto()
-    .setKey(AUDIT_HOUSEKEEPING_FREQUENCY)
-    .setValue(MONTHLY.name());
-
-  @Rule
-  public final DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private final DbClient dbClient = dbTester.getDbClient();
-
-  private final System2 system2 = new System2();
-
-  @Rule
-  public final DbTester db = DbTester.create(system2);
-
-  private final AuditHousekeepingFrequencyHelper auditHousekeepingFrequencyHelper = mock(AuditHousekeepingFrequencyHelper.class);
-
-  private final AuditPurgeStep underTest = new AuditPurgeStep(auditHousekeepingFrequencyHelper, dbClient);
-
-  @Before
-  public void setUp() {
-    when(auditHousekeepingFrequencyHelper.getHouseKeepingFrequency(any(), any())).thenReturn(FREQUENCY_PROPERTY);
-    when(auditHousekeepingFrequencyHelper.getThresholdDate(anyString())).thenReturn(NOW);
-  }
-
-  @Test
-  public void executeDeletesOlderAudits() {
-    prepareRowsWithDeterministicCreatedAt();
-    assertThat(dbClient.auditDao().selectOlderThan(db.getSession(), LATER + 1)).hasSize(3);
-
-    underTest.execute(() -> null);
-
-    assertThat(dbClient.auditDao().selectOlderThan(db.getSession(), LATER + 1)).hasSize(2);
-  }
-
-  @Test
-  public void getDescription() {
-    assertThat(underTest.getDescription()).isEqualTo("Purge Audit Logs");
-  }
-
-  private void prepareRowsWithDeterministicCreatedAt() {
-    insertAudit(BEFORE);
-    insertAudit(NOW);
-    insertAudit(LATER);
-    db.getSession().commit();
-  }
-
-  private void insertAudit(long timestamp) {
-    AuditDto auditDto = AuditTesting.newAuditDto(timestamp);
-    dbClient.auditDao().insert(db.getSession(), auditDto);
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/taskprocessor/IndexIssuesStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/taskprocessor/IndexIssuesStepTest.java
deleted file mode 100644 (file)
index beb8123..0000000
+++ /dev/null
@@ -1,117 +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 org.sonar.server.es.EsTester;
-import org.sonar.server.issue.index.IssueIndexer;
-
-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.times;
-import static org.mockito.Mockito.verify;
-import static org.sonar.db.component.BranchType.BRANCH;
-
-public class IndexIssuesStepTest {
-
-  private String BRANCH_UUID = "branch_uuid";
-
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private DbClient dbClient = dbTester.getDbClient();
-
-  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();
-
-  @Rule
-  public EsTester es = EsTester.create();
-  private System2 system2 = new System2();
-  @Rule
-  public DbTester db = DbTester.create(system2);
-
-  private IssueIndexer issueIndexer = mock(IssueIndexer.class);
-
-  private IndexIssuesStep underTest = new IndexIssuesStep(ceTask, dbClient, issueIndexer);
-
-  @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);
-
-    verify(issueIndexer, times(1)).indexOnAnalysis(BRANCH_UUID);
-    Optional<BranchDto> branch = dbClient.branchDao().selectByUuid(dbTester.getSession(), BRANCH_UUID);
-    assertThat(branch.get().isNeedIssueSync()).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);
-
-    verify(issueIndexer, times(0)).indexOnAnalysis(BRANCH_UUID);
-  }
-
-  @Test
-  public void fail_if_missing_component_in_task() {
-    CeTask ceTask = new CeTask.Builder()
-      .setType("type")
-      .setUuid("uuid")
-      .setComponent(null)
-      .setMainComponent(null)
-      .build();
-    IndexIssuesStep underTest = new IndexIssuesStep(ceTask, dbClient, issueIndexer);
-
-    assertThatThrownBy(() -> underTest.execute(() -> null))
-      .isInstanceOf(UnsupportedOperationException.class)
-      .hasMessage("component not found in task");
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/analysis/ExportAnalysesStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/analysis/ExportAnalysesStepTest.java
deleted file mode 100644 (file)
index 2c345e7..0000000
+++ /dev/null
@@ -1,212 +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.analysis;
-
-import com.sonarsource.governance.projectdump.protobuf.ProjectDump;
-import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import java.util.List;
-import javax.annotation.Nullable;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-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.component.ComponentRepositoryImpl;
-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 org.sonar.db.component.SnapshotDto;
-
-import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
-import static org.apache.commons.lang.StringUtils.defaultString;
-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.db.component.ComponentDto.UUID_PATH_OF_ROOT;
-import static org.sonar.db.component.ComponentDto.UUID_PATH_SEPARATOR;
-
-@RunWith(DataProviderRunner.class)
-public class ExportAnalysesStepTest {
-
-  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)
-    .setBranchUuid(PROJECT_UUID);
-
-  private static final String DIR_UUID = "DIR_UUID";
-  private static final String UUID_PATH = UUID_PATH_OF_ROOT + UUID_PATH_SEPARATOR + DIR_UUID;
-  private static final ComponentDto DIR = new ComponentDto()
-    // no id yet
-    .setScope(Scopes.PROJECT)
-    .setQualifier(Qualifiers.DIRECTORY)
-    .setKey("the_dir")
-    .setName("The Dir")
-    .setDescription("description of dir")
-    .setEnabled(true)
-    .setUuid(DIR_UUID)
-    .setUuidPath(UUID_PATH)
-    .setBranchUuid(PROJECT_UUID);
-
-  private static final String FILE_UUID = "FILE_UUID";
-  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(UUID_PATH + UUID_PATH_SEPARATOR + FILE_UUID)
-    .setEnabled(true)
-    .setBranchUuid(PROJECT_UUID);
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-  @Rule
-  public LogTester logTester = new LogTester();
-
-  private final ComponentRepositoryImpl componentRepository = new ComponentRepositoryImpl();
-  private final FakeDumpWriter dumpWriter = new FakeDumpWriter();
-  private final ProjectHolder projectHolder = mock(ProjectHolder.class);
-  private final ExportAnalysesStep underTest = new ExportAnalysesStep(dbTester.getDbClient(), projectHolder, componentRepository, dumpWriter);
-
-  @Before
-  public void setUp() {
-    ComponentDto projectDto = dbTester.components().insertPublicProject(PROJECT);
-    componentRepository.register(1, projectDto.uuid(), false);
-    dbTester.getDbClient().componentDao().insert(dbTester.getSession(), DIR, FILE);
-    dbTester.commit();
-    when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(projectDto));
-  }
-
-  @Test
-  public void getDescription_is_defined() {
-    assertThat(underTest.getDescription()).isEqualTo("Export analyses");
-  }
-
-  @Test
-  @UseDataProvider("versionAndBuildStringCombinations")
-  public void export_analyses(@Nullable String version, @Nullable String buildString) {
-    SnapshotDto firstAnalysis = newAnalysis("U_1", 1_450_000_000_000L, PROJECT.uuid(), "1.0", false, "1.0.2.3", 1_450_000_000_000L);
-    SnapshotDto secondAnalysis = newAnalysis("U_4", 1_460_000_000_000L, PROJECT.uuid(), "1.1", true, "1.1.3.4", 1_460_000_000_000L);
-    SnapshotDto thirdAnalysis = newAnalysis("U_7", 1_460_000_000_000L, PROJECT.uuid(), version, true, buildString, 1_470_000_000_000L);
-    dbTester.getDbClient().snapshotDao().insert(dbTester.getSession(), firstAnalysis, secondAnalysis, thirdAnalysis);
-    dbTester.commit();
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("3 analyses exported");
-    List<ProjectDump.Analysis> analyses = dumpWriter.getWrittenMessagesOf(DumpElement.ANALYSES);
-    assertThat(analyses).hasSize(3);
-    assertAnalysis(analyses.get(0), PROJECT, firstAnalysis);
-    assertAnalysis(analyses.get(1), PROJECT, secondAnalysis);
-    assertAnalysis(analyses.get(2), PROJECT, thirdAnalysis);
-  }
-
-  @DataProvider
-  public static Object[][] versionAndBuildStringCombinations() {
-    String version = randomAlphabetic(7);
-    String buildString = randomAlphabetic(12);
-    return new Object[][] {
-      {null, null},
-      {version, null},
-      {null, buildString},
-      {version, buildString},
-      {"", ""},
-      {version, ""},
-      {"", buildString},
-    };
-  }
-
-  @Test
-  public void export_analyses_by_ordering_by_technical_creation_date() {
-    SnapshotDto firstAnalysis = newAnalysis("U_1", 1_450_000_000_000L, PROJECT.uuid(), "1.0", false, "1.0.2.3", 3_000_000_000_000L);
-    SnapshotDto secondAnalysis = newAnalysis("U_4", 1_460_000_000_000L, PROJECT.uuid(), "1.1", true, "1.1.3.4", 1_000_000_000_000L);
-    SnapshotDto thirdAnalysis = newAnalysis("U_7", 1_460_500_000_000L, PROJECT.uuid(), null, true, null, 2_000_000_000_000L);
-    dbTester.getDbClient().snapshotDao().insert(dbTester.getSession(), firstAnalysis, secondAnalysis, thirdAnalysis);
-    dbTester.commit();
-
-    underTest.execute(new TestComputationStepContext());
-
-    List<ProjectDump.Analysis> analyses = dumpWriter.getWrittenMessagesOf(DumpElement.ANALYSES);
-    assertAnalysis(analyses.get(0), PROJECT, secondAnalysis);
-    assertAnalysis(analyses.get(1), PROJECT, thirdAnalysis);
-    assertAnalysis(analyses.get(2), PROJECT, firstAnalysis);
-  }
-
-  @Test
-  public void export_provisioned_projects_without_any_analyses() {
-    underTest.execute(new TestComputationStepContext());
-
-    List<ProjectDump.Analysis> analyses = dumpWriter.getWrittenMessagesOf(DumpElement.ANALYSES);
-    assertThat(analyses).isEmpty();
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("0 analyses exported");
-  }
-
-  @Test
-  public void throws_ISE_if_error() {
-    SnapshotDto firstAnalysis = newAnalysis("U_1", 1_450_000_000_000L, PROJECT.uuid(), "1.0", false, "1.0.2.3", 1);
-    SnapshotDto secondAnalysis = newAnalysis("U_4", 1_460_000_000_000L, PROJECT.uuid(), "1.1", true, "1.1.3.4", 2);
-    dbTester.getDbClient().snapshotDao().insert(dbTester.getSession(), firstAnalysis, secondAnalysis);
-    dbTester.commit();
-    dumpWriter.failIfMoreThan(1, DumpElement.ANALYSES);
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Analysis Export failed after processing 1 analyses successfully");
-  }
-
-  private static SnapshotDto newAnalysis(String uuid, long date, String componentUuid, @Nullable String version, boolean isLast, @Nullable String buildString, long buildDate) {
-    return new SnapshotDto()
-      .setUuid(uuid)
-      .setCreatedAt(date)
-      .setComponentUuid(componentUuid)
-      .setProjectVersion(version)
-      .setBuildString(buildString)
-      .setLast(isLast)
-      .setStatus(SnapshotDto.STATUS_PROCESSED)
-      .setBuildDate(buildDate);
-  }
-
-  private static void assertAnalysis(ProjectDump.Analysis analysis, ComponentDto component, SnapshotDto dto) {
-    assertThat(analysis.getUuid()).isEqualTo(dto.getUuid());
-    assertThat(analysis.getComponentRef()).isOne();
-    assertThat(analysis.getDate()).isEqualTo(dto.getCreatedAt());
-    assertThat(analysis.getProjectVersion()).isEqualTo(defaultString(dto.getProjectVersion()));
-    assertThat(analysis.getBuildString()).isEqualTo(defaultString(dto.getBuildString()));
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/branches/ExportBranchesStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/branches/ExportBranchesStepTest.java
deleted file mode 100644 (file)
index 9c8ae31..0000000
+++ /dev/null
@@ -1,156 +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.branches;
-
-import com.google.common.collect.ImmutableList;
-import com.sonarsource.governance.projectdump.protobuf.ProjectDump;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-import org.apache.commons.lang.time.DateUtils;
-import org.junit.Before;
-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.BranchDto;
-import org.sonar.db.component.BranchType;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.project.ProjectExportMapper;
-
-import static java.util.stream.Collectors.toMap;
-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.db.component.ComponentDto.UUID_PATH_OF_ROOT;
-
-public class ExportBranchesStepTest {
-  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)
-    .setBranchUuid(PROJECT_UUID);
-
-  @Rule
-  public DbTester dbTester = DbTester.createWithExtensionMappers(System2.INSTANCE, ProjectExportMapper.class);
-  @Rule
-  public LogTester logTester = new LogTester();
-
-  private final ProjectHolder projectHolder = mock(ProjectHolder.class);
-  private final FakeDumpWriter dumpWriter = new FakeDumpWriter();
-  private final ExportBranchesStep underTest = new ExportBranchesStep(dumpWriter, dbTester.getDbClient(), projectHolder);
-  private final List<BranchDto> branches = ImmutableList.of(
-    new BranchDto()
-      .setBranchType(BranchType.BRANCH)
-      .setProjectUuid(PROJECT_UUID)
-      .setKey("branch-1")
-      .setUuid("branch-1-uuid")
-      .setMergeBranchUuid("master")
-      .setExcludeFromPurge(true),
-    new BranchDto()
-      .setBranchType(BranchType.PULL_REQUEST)
-      .setProjectUuid(PROJECT_UUID)
-      .setKey("branch-3")
-      .setUuid("branch-3-uuid")
-      .setMergeBranchUuid("master"),
-    new BranchDto()
-      .setBranchType(BranchType.BRANCH)
-      .setProjectUuid(PROJECT_UUID)
-      .setKey("branch-4")
-      .setUuid("branch-4-uuid")
-      .setMergeBranchUuid("branch-1-uuid"),
-    new BranchDto()
-      .setBranchType(BranchType.BRANCH)
-      .setProjectUuid(PROJECT_UUID)
-      .setKey("branch-5")
-      .setUuid("branch-5-uuid")
-      .setMergeBranchUuid("master")
-      .setExcludeFromPurge(true));
-
-  @Before
-  public void setUp() {
-    Date createdAt = new Date();
-    ComponentDto projectDto = dbTester.components().insertPublicProject(PROJECT).setCreatedAt(createdAt);
-    for (BranchDto branch : branches) {
-      createdAt = DateUtils.addMinutes(createdAt, 10);
-      dbTester.components().insertProjectBranch(PROJECT, branch).setCreatedAt(createdAt);
-    }
-    dbTester.commit();
-    when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(projectDto));
-  }
-
-  @Test
-  public void export_branches() {
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("3 branches exported");
-    Map<String, ProjectDump.Branch> branches = dumpWriter.getWrittenMessagesOf(DumpElement.BRANCHES)
-      .stream()
-      .collect(toMap(ProjectDump.Branch::getUuid, Function.identity()));
-    assertThat(branches).hasSize(3);
-    ProjectDump.Branch mainBranch = branches.get(PROJECT_UUID);
-    assertThat(mainBranch).isNotNull();
-    assertThat(mainBranch.getKee()).isEqualTo(BranchDto.DEFAULT_MAIN_BRANCH_NAME);
-    assertThat(mainBranch.getProjectUuid()).isEqualTo(PROJECT_UUID);
-    assertThat(mainBranch.getMergeBranchUuid()).isEmpty();
-    assertThat(mainBranch.getBranchType()).isEqualTo("BRANCH");
-    ProjectDump.Branch branch1 = branches.get("branch-1-uuid");
-    assertThat(branch1.getKee()).isEqualTo("branch-1");
-    assertThat(branch1.getProjectUuid()).isEqualTo(PROJECT_UUID);
-    assertThat(branch1.getMergeBranchUuid()).isEqualTo("master");
-    assertThat(branch1.getBranchType()).isEqualTo("BRANCH");
-    ProjectDump.Branch branch5 = branches.get("branch-5-uuid");
-    assertThat(branch5.getKee()).isEqualTo("branch-5");
-    assertThat(branch5.getProjectUuid()).isEqualTo(PROJECT_UUID);
-    assertThat(branch5.getMergeBranchUuid()).isEqualTo("master");
-    assertThat(branch5.getBranchType()).isEqualTo("BRANCH");
-  }
-
-  @Test
-  public void throws_ISE_if_error() {
-    dumpWriter.failIfMoreThan(1, DumpElement.BRANCHES);
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Branch export failed after processing 1 branch(es) successfully");
-  }
-
-  @Test
-  public void getDescription_is_defined() {
-    assertThat(underTest.getDescription()).isEqualTo("Export branches");
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/file/ExportLineHashesStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/file/ExportLineHashesStepTest.java
deleted file mode 100644 (file)
index fb24c71..0000000
+++ /dev/null
@@ -1,192 +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.file;
-
-import com.sonarsource.governance.projectdump.protobuf.ProjectDump;
-import java.util.List;
-import org.junit.After;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-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.projectexport.component.MutableComponentRepository;
-import org.sonar.ce.task.projectexport.steps.DumpElement;
-import org.sonar.ce.task.projectexport.steps.FakeDumpWriter;
-import org.sonar.ce.task.step.TestComputationStepContext;
-import org.sonar.core.util.Uuids;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.MyBatis;
-import org.sonar.db.source.FileSourceDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doCallRealMethod;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verifyNoInteractions;
-import static org.mockito.Mockito.when;
-
-public class ExportLineHashesStepTest {
-  private static final String PROJECT_MASTER_UUID = "project uuid";
-  private static final String PROJECT_BRANCH_UUID = "branch-uuid";
-  private static final String FILE_UUID = "file uuid";
-  private static final String FILE_UUID_2 = "file-2-uuid";
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-  @Rule
-  public LogTester logTester = new LogTester();
-
-  private DbClient dbClient = dbTester.getDbClient();
-  private DbSession dbSession = dbClient.openSession(false);
-  private FakeDumpWriter dumpWriter = new FakeDumpWriter();
-  private MutableComponentRepository componentRepository = new ComponentRepositoryImpl();
-
-  private ExportLineHashesStep underTest = new ExportLineHashesStep(dbClient, dumpWriter, componentRepository);
-
-  @After
-  public void tearDown() {
-    dbSession.close();
-  }
-
-  @Test
-  public void getDescription_is_set() {
-    assertThat(underTest.getDescription()).isEqualTo("Export line hashes");
-  }
-
-  @Test
-  public void execute_does_not_create_a_session_when_there_is_no_file_in_ComponentRepository() {
-    DbClient spy = spy(dbClient);
-    new ExportLineHashesStep(spy, dumpWriter, componentRepository)
-      .execute(new TestComputationStepContext());
-
-    verifyNoInteractions(spy);
-
-    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.LINES_HASHES)).isEmpty();
-  }
-
-  @Test
-  public void execute_relies_only_on_file_uuid_and_does_not_check_project_uuid() {
-    componentRepository.register(1, FILE_UUID, true);
-
-    insertFileSource(createDto(FILE_UUID, "blabla", "A"));
-    insertFileSource(createDto(FILE_UUID_2, "blabla", "B"));
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.LINES_HASHES)).hasSize(1);
-  }
-
-  @Test
-  public void execute_maps_ref_of_component_and_hashes_from_fileSources() {
-    int fileRef = 984615;
-    componentRepository.register(fileRef, FILE_UUID, true);
-    FileSourceDto dto = createDto(FILE_UUID, PROJECT_MASTER_UUID, "B");
-    insertFileSource(dto);
-
-    underTest.execute(new TestComputationStepContext());
-
-    List<ProjectDump.LineHashes> messages = dumpWriter.getWrittenMessagesOf(DumpElement.LINES_HASHES);
-    assertThat(messages).hasSize(1);
-    ProjectDump.LineHashes lineHashes = messages.iterator().next();
-    assertThat(lineHashes.getHashes()).isEqualTo(dto.getRawLineHashes());
-    assertThat(lineHashes.getComponentRef()).isEqualTo(fileRef);
-  }
-
-  @Test
-  public void execute_does_one_SQL_request_by_1000_items_per_IN_clause() {
-    for (int i = 0; i < 2500; i++) {
-      componentRepository.register(i, "uuid_" + i, true);
-    }
-
-    DbClient spyDbClient = spy(dbClient);
-    MyBatis spyMyBatis = spy(dbClient.getMyBatis());
-    when(spyDbClient.getMyBatis()).thenReturn(spyMyBatis);
-    ArgumentCaptor<String> stringCaptor = ArgumentCaptor.forClass(String.class);
-    doCallRealMethod().when(spyMyBatis).newScrollingSelectStatement(any(DbSession.class), stringCaptor.capture());
-
-    new ExportLineHashesStep(spyDbClient, dumpWriter, componentRepository)
-      .execute(new TestComputationStepContext());
-
-    List<String> statements = stringCaptor.getAllValues();
-    assertThat(statements).hasSize(3);
-
-    assertThat(statements.get(0).split("\\?")).hasSize(1001);
-    assertThat(statements.get(1).split("\\?")).hasSize(1001);
-    assertThat(statements.get(2).split("\\?")).hasSize(501);
-  }
-
-  @Test
-  public void execute_exports_lines_hashes_of_file_sources() {
-    componentRepository.register(1, FILE_UUID, true);
-    insertFileSource(FILE_UUID, "A");
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.LINES_HASHES))
-      .extracting(ProjectDump.LineHashes::getHashes)
-      .containsOnly("A");
-  }
-
-  @Test
-  public void execute_logs_number_of_filesource_exported_and_export_by_order_of_id() {
-    componentRepository.register(1, FILE_UUID, true);
-    componentRepository.register(2, "file-2", true);
-    componentRepository.register(3, "file-3", true);
-    componentRepository.register(4, "file-4", true);
-
-    insertFileSource(createDto(FILE_UUID, PROJECT_MASTER_UUID, "A"));
-    insertFileSource(createDto("file-2", PROJECT_MASTER_UUID, "C"));
-    insertFileSource(createDto("file-3", PROJECT_MASTER_UUID, "D"));
-    insertFileSource(createDto("file-4", PROJECT_BRANCH_UUID, "E"));
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.LINES_HASHES))
-      .extracting(ProjectDump.LineHashes::getHashes)
-      .containsExactly("A", "C", "D", "E");
-
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).containsExactly("Lines hashes of 4 files exported");
-  }
-
-  private FileSourceDto insertFileSource(String fileUuid, String hashes) {
-    FileSourceDto dto = createDto(fileUuid, PROJECT_MASTER_UUID, hashes);
-    return insertFileSource(dto);
-  }
-
-  private FileSourceDto insertFileSource(FileSourceDto dto) {
-    dbClient.fileSourceDao().insert(dbSession, dto);
-    dbSession.commit();
-    return dto;
-  }
-
-  private FileSourceDto createDto(String fileUuid, String componentUuid, String hashes) {
-    FileSourceDto fileSourceDto = new FileSourceDto()
-      .setUuid(Uuids.createFast())
-      .setFileUuid(fileUuid)
-      .setProjectUuid(componentUuid);
-    fileSourceDto.setRawLineHashes(hashes);
-    return fileSourceDto;
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/issue/ExportIssuesChangelogStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/issue/ExportIssuesChangelogStepTest.java
deleted file mode 100644 (file)
index 09eef46..0000000
+++ /dev/null
@@ -1,277 +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.issue;
-
-import com.sonarsource.governance.projectdump.protobuf.ProjectDump;
-import java.util.List;
-import javax.annotation.Nullable;
-import org.junit.After;
-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.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.core.util.Uuids;
-import org.sonar.db.DbClient;
-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.issue.IssueChangeDto;
-import org.sonar.db.issue.IssueDto;
-
-import static com.google.common.collect.Lists.newArrayList;
-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.api.issue.Issue.STATUS_CLOSED;
-import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
-import static org.sonar.api.issue.Issue.STATUS_OPEN;
-import static org.sonar.api.issue.Issue.STATUS_REOPENED;
-import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
-
-public class ExportIssuesChangelogStepTest {
-  private static final String PROJECT_UUID = "project uuid";
-  private static final String ISSUE_OPEN_UUID = "issue 1 uuid";
-  private static final String ISSUE_CONFIRMED_UUID = "issue 2 uuid";
-  private static final String ISSUE_REOPENED_UUID = "issue 3 uuid";
-  private static final String ISSUE_RESOLVED_UUID = "issue 4 uuid";
-  private static final String ISSUE_CLOSED_UUID = "issue closed uuid";
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-  @Rule
-  public LogTester logTester = new LogTester();
-
-  private final DbClient dbClient = dbTester.getDbClient();
-  private final DbSession dbSession = dbClient.openSession(false);
-  private final ProjectHolder projectHolder = mock(ProjectHolder.class);
-  private final FakeDumpWriter dumpWriter = new FakeDumpWriter();
-  private final ExportIssuesChangelogStep underTest = new ExportIssuesChangelogStep(dbClient, projectHolder, dumpWriter);
-
-  private int issueChangeUuidGenerator = 0;
-
-  @Before
-  public void setUp() {
-    ComponentDto projectDto = dbTester.components().insertPublicProject(p -> p.setUuid(PROJECT_UUID));
-    when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(projectDto));
-    when(projectHolder.branches()).thenReturn(newArrayList(
-      new BranchDto().setBranchType(BranchType.BRANCH).setKey("master").setProjectUuid(PROJECT_UUID).setUuid(PROJECT_UUID)));
-
-    insertIssue(PROJECT_UUID, ISSUE_OPEN_UUID, STATUS_OPEN);
-    insertIssue(PROJECT_UUID, ISSUE_CONFIRMED_UUID, STATUS_CONFIRMED);
-    insertIssue(PROJECT_UUID, ISSUE_REOPENED_UUID, STATUS_REOPENED);
-    insertIssue(PROJECT_UUID, ISSUE_RESOLVED_UUID, STATUS_RESOLVED);
-    insertIssue(PROJECT_UUID, ISSUE_CLOSED_UUID, STATUS_CLOSED);
-  }
-
-  @After
-  public void tearDown() {
-    dbSession.close();
-  }
-
-  @Test
-  public void getDescription_is_set() {
-    assertThat(underTest.getDescription()).isEqualTo("Export issues changelog");
-  }
-
-  @Test
-  public void execute_writes_now_RuleChange_is_db_is_empty() {
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES_CHANGELOG)).isEmpty();
-  }
-
-  @Test
-  public void execute_writes_entries_of_issues_of_any_type_but_CLOSED() {
-    long createdAt = 1_999_993L;
-    String[] expectedKeys = new String[] {
-      insertIssueChange(ISSUE_OPEN_UUID, createdAt).getKey(),
-      insertIssueChange(ISSUE_CONFIRMED_UUID, createdAt + 1).getKey(),
-      insertIssueChange(ISSUE_REOPENED_UUID, createdAt + 2).getKey(),
-      insertIssueChange(ISSUE_RESOLVED_UUID, createdAt + 3).getKey()
-    };
-    insertIssueChange(ISSUE_CLOSED_UUID, createdAt + 4);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES_CHANGELOG))
-      .extracting(ProjectDump.IssueChange::getKey).containsExactly(expectedKeys);
-  }
-
-  @Test
-  public void execute_writes_only_entries_of_current_project() {
-    String issueUuid = "issue uuid";
-    insertIssue("other project uuid", issueUuid, STATUS_OPEN);
-    insertIssueChange(issueUuid);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES_CHANGELOG)).isEmpty();
-  }
-
-  @Test
-  public void execute_maps_all_fields_to_protobuf() {
-    IssueChangeDto issueChangeDto = new IssueChangeDto()
-      .setUuid("uuid")
-      .setKey("key")
-      .setIssueKey(ISSUE_OPEN_UUID)
-      .setChangeData("change data")
-      .setChangeType("change type")
-      .setUserUuid("user_uuid")
-      .setIssueChangeCreationDate(454135L)
-      .setProjectUuid(PROJECT_UUID);
-    insertIssueChange(issueChangeDto);
-
-    underTest.execute(new TestComputationStepContext());
-
-    ProjectDump.IssueChange issueChange = getSingleMessage();
-    assertThat(issueChange.getKey()).isEqualTo(issueChangeDto.getKey());
-    assertThat(issueChange.getIssueUuid()).isEqualTo(issueChangeDto.getIssueKey());
-    assertThat(issueChange.getChangeData()).isEqualTo(issueChangeDto.getChangeData());
-    assertThat(issueChange.getChangeType()).isEqualTo(issueChangeDto.getChangeType());
-    assertThat(issueChange.getUserUuid()).isEqualTo(issueChangeDto.getUserUuid());
-    assertThat(issueChange.getCreatedAt()).isEqualTo(issueChangeDto.getIssueChangeCreationDate());
-    assertThat(issueChange.getProjectUuid()).isEqualTo(issueChangeDto.getProjectUuid());
-  }
-
-  @Test
-  public void execute_exports_issues_by_oldest_create_date_first() {
-    long createdAt = 1_999_993L;
-    long now = createdAt + 1_000_000_000L;
-    String key1 = insertIssueChange(ISSUE_OPEN_UUID, createdAt, now).getKey();
-    String key2 = insertIssueChange(ISSUE_OPEN_UUID, createdAt - 500, now + 100).getKey();
-    String key3 = insertIssueChange(ISSUE_OPEN_UUID, createdAt - 1000, now + 200).getKey();
-    String key4 = insertIssueChange(ISSUE_OPEN_UUID, createdAt + 200, now + 300).getKey();
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES_CHANGELOG))
-      .extracting(ProjectDump.IssueChange::getKey)
-      .containsExactly(key3, key2, key1, key4);
-  }
-
-  @Test
-  public void execute_sets_missing_fields_to_default_values() {
-    long createdAt = 1_999_888L;
-    insertIssueChange(new IssueChangeDto().setUuid(Uuids.createFast()).setIssueKey(ISSUE_REOPENED_UUID).setCreatedAt(createdAt).setProjectUuid("project_uuid"));
-
-    underTest.execute(new TestComputationStepContext());
-
-    ProjectDump.IssueChange issueChange = getSingleMessage();
-    assertThat(issueChange.getKey()).isEmpty();
-    assertThat(issueChange.getChangeType()).isEmpty();
-    assertThat(issueChange.getChangeData()).isEmpty();
-    assertThat(issueChange.getUserUuid()).isEmpty();
-    assertThat(issueChange.getCreatedAt()).isEqualTo(createdAt);
-  }
-
-  @Test
-  public void execute_sets_createAt_to_zero_if_both_createdAt_and_issueChangeDate_are_null() {
-    insertIssueChange(new IssueChangeDto().setUuid(Uuids.createFast()).setIssueKey(ISSUE_REOPENED_UUID).setProjectUuid(PROJECT_UUID));
-
-    underTest.execute(new TestComputationStepContext());
-
-    ProjectDump.IssueChange issueChange = getSingleMessage();
-    assertThat(issueChange.getCreatedAt()).isZero();
-  }
-
-  @Test
-  public void execute_writes_entries_of_closed_issue() {
-    insertIssueChange(ISSUE_CLOSED_UUID);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES_CHANGELOG)).isEmpty();
-  }
-
-  @Test
-  public void execute_logs_number_total_exported_issue_changes_count_when_successful() {
-    insertIssueChange(ISSUE_OPEN_UUID);
-    insertIssueChange(ISSUE_CONFIRMED_UUID);
-    insertIssueChange(ISSUE_REOPENED_UUID);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).containsExactly("3 issue changes exported");
-  }
-
-  @Test
-  public void execute_throws_ISE_when_exception_occurs_and_message_contains_number_of_successfully_processed_files_in_() {
-    insertIssueChange(ISSUE_OPEN_UUID);
-    insertIssueChange(ISSUE_CONFIRMED_UUID);
-    insertIssueChange(ISSUE_REOPENED_UUID);
-    dumpWriter.failIfMoreThan(2, DumpElement.ISSUES_CHANGELOG);
-    TestComputationStepContext context = new TestComputationStepContext();
-
-    assertThatThrownBy(() -> underTest.execute(context))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Issues changelog export failed after processing 2 issue changes successfully");
-  }
-
-  private void insertIssueChange(String issueUuid) {
-    insertIssueChange(issueUuid, System2.INSTANCE.now(), null);
-  }
-
-  private IssueChangeDto insertIssueChange(String issueUuid, long creationDate) {
-    return insertIssueChange(issueUuid, creationDate, null);
-  }
-
-  private IssueChangeDto insertIssueChange(String issueUuid, long creationDate, @Nullable Long issueChangeDate) {
-    IssueChangeDto dto = new IssueChangeDto()
-      .setKey("uuid_" + issueChangeUuidGenerator++)
-      .setUuid(Uuids.createFast())
-      .setCreatedAt(creationDate)
-      .setIssueKey(issueUuid)
-      .setProjectUuid("project_uuid");
-    if (issueChangeDate != null) {
-      dto.setIssueChangeCreationDate(issueChangeDate);
-    }
-    return insertIssueChange(dto);
-  }
-
-  private IssueChangeDto insertIssueChange(IssueChangeDto dto) {
-    dbClient.issueChangeDao().insert(dbSession, dto);
-    dbSession.commit();
-    return dto;
-  }
-
-  private void insertIssue(String projectUuid, String uuid, String status) {
-    IssueDto dto = new IssueDto()
-      .setKee(uuid)
-      .setProjectUuid(projectUuid)
-      .setStatus(status);
-    dbClient.issueDao().insert(dbSession, dto);
-    dbSession.commit();
-  }
-
-  private ProjectDump.IssueChange getSingleMessage() {
-    List<ProjectDump.IssueChange> messages = dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES_CHANGELOG);
-    assertThat(messages).hasSize(1);
-    return messages.get(0);
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/issue/ExportIssuesStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/issue/ExportIssuesStepTest.java
deleted file mode 100644 (file)
index 7d9544b..0000000
+++ /dev/null
@@ -1,404 +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.issue;
-
-import com.google.protobuf.InvalidProtocolBufferException;
-import com.sonarsource.governance.projectdump.protobuf.ProjectDump;
-import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import java.io.File;
-import java.io.IOException;
-import java.net.URISyntaxException;
-import java.util.Random;
-import org.apache.commons.io.FileUtils;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rule.RuleStatus;
-import org.sonar.api.rules.RuleType;
-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.projectexport.component.MutableComponentRepository;
-import org.sonar.ce.task.projectexport.rule.RuleRepository;
-import org.sonar.ce.task.projectexport.rule.RuleRepositoryImpl;
-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.DbClient;
-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.issue.IssueDto;
-import org.sonar.db.project.ProjectDto;
-import org.sonar.db.protobuf.DbIssues;
-import org.sonar.db.protobuf.DbIssues.Locations;
-import org.sonar.db.protobuf.DbIssues.MessageFormattingType;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.db.rule.RuleDto.Scope;
-
-import static com.google.common.collect.Lists.newArrayList;
-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.api.issue.Issue.STATUS_CONFIRMED;
-import static org.sonar.api.issue.Issue.STATUS_OPEN;
-import static org.sonar.api.issue.Issue.STATUS_REOPENED;
-import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
-
-@RunWith(DataProviderRunner.class)
-public class ExportIssuesStepTest {
-  private static final String SOME_PROJECT_UUID = "project uuid";
-  private static final String PROJECT_KEY = "projectkey";
-  private static final String SOME_REPO = "rule repo";
-  private static final String READY_RULE_KEY = "rule key 1";
-  public static final DbIssues.MessageFormatting MESSAGE_FORMATTING = DbIssues.MessageFormatting.newBuilder().setStart(0).setEnd(4).setType(MessageFormattingType.CODE).build();
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-  @Rule
-  public LogTester logTester = new LogTester();
-
-  private final DbClient dbClient = dbTester.getDbClient();
-  private final DbSession dbSession = dbClient.openSession(false);
-  private final ProjectHolder projectHolder = mock(ProjectHolder.class);
-  private final FakeDumpWriter dumpWriter = new FakeDumpWriter();
-  private final RuleRepository ruleRepository = new RuleRepositoryImpl();
-  private final MutableComponentRepository componentRepository = new ComponentRepositoryImpl();
-
-  private final ExportIssuesStep underTest = new ExportIssuesStep(dbClient, projectHolder, dumpWriter, ruleRepository, componentRepository);
-
-  private RuleDto readyRuleDto;
-
-  @Before
-  public void setUp() {
-    ProjectDto project = createProject();
-    when(projectHolder.projectDto()).thenReturn(project);
-    when(projectHolder.branches()).thenReturn(newArrayList(
-      new BranchDto().setBranchType(BranchType.BRANCH).setKey("master").setProjectUuid(SOME_PROJECT_UUID).setUuid(SOME_PROJECT_UUID)));
-
-    // adds a random number of Rules to db and repository so that READY_RULE_KEY does always get id=ref=1
-    for (int i = 0; i < new Random().nextInt(150); i++) {
-      RuleKey ruleKey = RuleKey.of("repo_" + i, "key_" + i);
-      RuleDto ruleDto = insertRule(ruleKey.toString());
-      ruleRepository.register(ruleDto.getUuid(), ruleKey);
-    }
-    this.readyRuleDto = insertRule(READY_RULE_KEY);
-    componentRepository.register(12, SOME_PROJECT_UUID, false);
-  }
-
-  @After
-  public void tearDown() {
-    dbSession.close();
-  }
-
-  @Test
-  public void getDescription_is_set() {
-    assertThat(underTest.getDescription()).isEqualTo("Export issues");
-  }
-
-  @Test
-  public void execute_written_writes_no_issues_when_project_has_no_issues() {
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES)).isEmpty();
-  }
-
-  @Test
-  public void execute_written_writes_no_issues_when_project_has_only_CLOSED_issues() {
-    insertIssue(readyRuleDto, SOME_PROJECT_UUID, Issue.STATUS_CLOSED);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES)).isEmpty();
-  }
-
-  @Test
-  public void execute_fails_with_ISE_if_componentUuid_is_not_set() {
-    insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID).setComponentUuid(null));
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Issue export failed after processing 0 issues successfully")
-      .hasRootCauseInstanceOf(NullPointerException.class)
-      .hasRootCauseMessage("uuid can not be null");
-  }
-
-  @DataProvider
-  public static Object[][] allStatusesButCLOSED() {
-    return new Object[][] {
-      {STATUS_OPEN},
-      {STATUS_CONFIRMED},
-      {STATUS_REOPENED},
-      {STATUS_RESOLVED}
-    };
-  }
-
-  @Test
-  @UseDataProvider("allStatusesButCLOSED")
-  public void execute_writes_issues_with_any_status_but_CLOSED(String status) {
-    String uuid = insertIssue(readyRuleDto, SOME_PROJECT_UUID, status).getKey();
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES))
-      .extracting(ProjectDump.Issue::getUuid)
-      .containsOnly(uuid);
-  }
-
-  @Test
-  public void execute_writes_issues_from_any_component_in_project_are_written() {
-    componentRepository.register(13, "module uuid", false);
-    componentRepository.register(14, "dir uuid", false);
-    componentRepository.register(15, "file uuid", false);
-    String projectIssueUuid = insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID)).getKey();
-    String moduleIssueUuid = insertIssue(createBaseIssueDto(readyRuleDto, "module uuid")).getKey();
-    String dirIssueUuid = insertIssue(createBaseIssueDto(readyRuleDto, "dir uuid")).getKey();
-    String fileIssueUuid = insertIssue(createBaseIssueDto(readyRuleDto, "file uuid")).getKey();
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES))
-      .extracting(ProjectDump.Issue::getUuid)
-      .containsOnly(projectIssueUuid, moduleIssueUuid, dirIssueUuid, fileIssueUuid);
-  }
-
-  @Test
-  public void execute_ignores_issues_of_other_projects() {
-    insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID).setProjectUuid("other project"));
-    String projectIssueUuid = insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID)).getKey();
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES))
-      .extracting(ProjectDump.Issue::getUuid)
-      .containsOnly(projectIssueUuid);
-  }
-
-  @Test
-  public void verify_field_by_field_mapping() throws InvalidProtocolBufferException {
-    String componentUuid = "component uuid";
-    long componentRef = 5454;
-    componentRepository.register(componentRef, componentUuid, false);
-    DbIssues.MessageFormattings messageFormattings = DbIssues.MessageFormattings.newBuilder().addMessageFormatting(MESSAGE_FORMATTING).build();
-    IssueDto issueDto = new IssueDto()
-      .setKee("issue uuid")
-      .setComponentUuid(componentUuid)
-      .setType(988)
-      .setMessage("msg")
-      .setMessageFormattings(messageFormattings)
-      .setLine(10)
-      .setChecksum("checksum")
-      .setResolution("resolution")
-      .setSeverity("severity")
-      .setManualSeverity(true)
-      .setGap(13.13d)
-      .setEffort(99L)
-      .setAssigneeUuid("assignee-uuid")
-      .setAuthorLogin("author")
-      .setTagsString("tags")
-      .setRuleDescriptionContextKey("test_rule_description_context_key")
-      .setIssueCreationTime(963L)
-      .setIssueUpdateTime(852L)
-      .setIssueCloseTime(741L);
-
-    // fields tested separately and/or required to match SQL request
-    issueDto
-      .setType(RuleType.CODE_SMELL)
-      .setLocations(Locations.newBuilder().addFlow(DbIssues.Flow.newBuilder()).build())
-      .setRuleUuid(readyRuleDto.getUuid())
-      .setStatus(STATUS_OPEN).setProjectUuid(SOME_PROJECT_UUID);
-
-    insertIssue(issueDto);
-
-    underTest.execute(new TestComputationStepContext());
-
-    ProjectDump.Issue issue = getWrittenIssue();
-
-    assertThat(issue.getUuid()).isEqualTo(issueDto.getKey());
-    assertThat(issue.getComponentRef()).isEqualTo(componentRef);
-    assertThat(issue.getType()).isEqualTo(issueDto.getType());
-    assertThat(issue.getMessage()).isEqualTo(issueDto.getMessage());
-    assertThat(issue.getLine()).isEqualTo(issueDto.getLine());
-    assertThat(issue.getChecksum()).isEqualTo(issueDto.getChecksum());
-    assertThat(issue.getStatus()).isEqualTo(issueDto.getStatus());
-    assertThat(issue.getResolution()).isEqualTo(issueDto.getResolution());
-    assertThat(issue.getSeverity()).isEqualTo(issueDto.getSeverity());
-    assertThat(issue.getManualSeverity()).isEqualTo(issueDto.isManualSeverity());
-    assertThat(issue.getGap()).isEqualTo(issueDto.getGap());
-    assertThat(issue.getEffort()).isEqualTo(issueDto.getEffort());
-    assertThat(issue.getAssignee()).isEqualTo(issueDto.getAssigneeUuid());
-    assertThat(issue.getAuthor()).isEqualTo(issueDto.getAuthorLogin());
-    assertThat(issue.getTags()).isEqualTo(issueDto.getTagsString());
-    assertThat(issue.getRuleDescriptionContextKey()).isEqualTo(issue.getRuleDescriptionContextKey());
-    assertThat(issue.getIssueCreatedAt()).isEqualTo(issueDto.getIssueCreationTime());
-    assertThat(issue.getIssueUpdatedAt()).isEqualTo(issueDto.getIssueUpdateTime());
-    assertThat(issue.getIssueClosedAt()).isEqualTo(issueDto.getIssueCloseTime());
-    assertThat(issue.getLocations()).isNotEmpty();
-    assertThat(issue.getMessageFormattingsList())
-      .isEqualTo(ExportIssuesStep.dbToDumpMessageFormatting(messageFormattings.getMessageFormattingList()));
-  }
-
-  @Test
-  public void verify_mapping_of_nullable_numerical_fields_to_defaultValue() {
-    insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
-
-    underTest.execute(new TestComputationStepContext());
-
-    ProjectDump.Issue issue = getWrittenIssue();
-
-    assertThat(issue.getLine()).isEqualTo(DumpElement.ISSUES.NO_LINE);
-    assertThat(issue.getGap()).isEqualTo(DumpElement.ISSUES.NO_GAP);
-    assertThat(issue.getEffort()).isEqualTo(DumpElement.ISSUES.NO_EFFORT);
-    assertThat(issue.getIssueCreatedAt()).isEqualTo(DumpElement.NO_DATETIME);
-    assertThat(issue.getIssueUpdatedAt()).isEqualTo(DumpElement.NO_DATETIME);
-    assertThat(issue.getIssueClosedAt()).isEqualTo(DumpElement.NO_DATETIME);
-    assertThat(issue.hasRuleDescriptionContextKey()).isFalse();
-  }
-
-  @Test
-  public void ruleRef_is_ref_provided_by_RuleRepository() {
-
-    IssueDto issueDto = insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
-
-    underTest.execute(new TestComputationStepContext());
-
-    ProjectDump.Issue issue = getWrittenIssue();
-    assertThat(issue.getRuleRef())
-      .isEqualTo(ruleRepository.register(issueDto.getRuleUuid(), readyRuleDto.getKey()).ref());
-  }
-
-  @Test
-  public void locations_is_not_set_in_protobuf_if_null_in_DB() {
-    insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(getWrittenIssue().getLocations()).isEmpty();
-  }
-
-  @Test
-  public void message_formattings_is_empty_in_protobuf_if_null_in_DB() {
-    insertIssue(readyRuleDto, SOME_PROJECT_UUID, STATUS_OPEN);
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(getWrittenIssue().getMessageFormattingsList()).isEmpty();
-  }
-
-  @Test
-  public void execute_fails_with_ISE_if_locations_cannot_be_parsed_to_protobuf() throws URISyntaxException, IOException {
-    byte[] rubbishBytes = getRubbishBytes();
-    String uuid = insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID).setLocations(rubbishBytes)).getKey();
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Issue export failed after processing 0 issues successfully");
-  }
-
-  @Test
-  public void execute_logs_number_total_exported_issue_count_when_successful() {
-    insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
-    insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
-    insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).containsExactly("3 issues exported");
-  }
-
-  @Test
-  public void execute_throws_ISE_with_number_of_successful_exports_before_failure() throws URISyntaxException, IOException {
-    insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
-    insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID));
-    insertIssue(createBaseIssueDto(readyRuleDto, SOME_PROJECT_UUID).setLocations(getRubbishBytes())).getKey();
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Issue export failed after processing 2 issues successfully");
-  }
-
-  private byte[] getRubbishBytes() throws IOException, URISyntaxException {
-    return FileUtils.readFileToByteArray(new File(getClass().getResource("rubbish_data.txt").toURI()));
-  }
-
-  private ProjectDump.Issue getWrittenIssue() {
-    return dumpWriter.getWrittenMessagesOf(DumpElement.ISSUES).get(0);
-  }
-
-  // private void expectExportFailure() {
-  // expectExportFailure(0);
-  // }
-
-  // private void expectExportFailure(int i) {
-  // expectedException.expect(IllegalStateException.class);
-  // expectedException.expectMessage("Issue export failed after processing " + i + " issues successfully");
-  // }
-
-  private int issueUuidGenerator = 1;
-
-  private IssueDto insertIssue(RuleDto ruleDto, String componentUuid, String status) {
-    IssueDto dto = createBaseIssueDto(ruleDto, componentUuid, status);
-    return insertIssue(dto);
-  }
-
-  private IssueDto insertIssue(IssueDto dto) {
-    dbClient.issueDao().insert(dbSession, dto);
-    dbSession.commit();
-    return dto;
-  }
-
-  private ProjectDto createProject() {
-    ComponentDto projectDto = dbTester.components().insertPrivateProject(c -> c.setKey(PROJECT_KEY).setUuid(SOME_PROJECT_UUID));
-    dbTester.commit();
-    return dbTester.components().getProjectDto(projectDto);
-  }
-
-  private IssueDto createBaseIssueDto(RuleDto ruleDto, String componentUuid) {
-    return createBaseIssueDto(ruleDto, componentUuid, STATUS_OPEN);
-  }
-
-  private IssueDto createBaseIssueDto(RuleDto ruleDto, String componentUuid, String status) {
-    return new IssueDto()
-      .setKee("issue_uuid_" + issueUuidGenerator++)
-      .setComponentUuid(componentUuid)
-      .setProjectUuid(SOME_PROJECT_UUID)
-      .setRuleUuid(ruleDto.getUuid())
-      .setCreatedAt(System2.INSTANCE.now())
-      .setStatus(status);
-  }
-
-  private RuleDto insertRule(String ruleKey1) {
-    RuleDto dto = new RuleDto().setRepositoryKey(SOME_REPO).setScope(Scope.MAIN).setRuleKey(ruleKey1).setStatus(RuleStatus.READY);
-    dbTester.rules().insert(dto);
-    dbSession.commit();
-    return dto;
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/rule/ExportAdHocRulesStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/rule/ExportAdHocRulesStepTest.java
deleted file mode 100644 (file)
index 04d1f1f..0000000
+++ /dev/null
@@ -1,264 +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.rule;
-
-import com.google.common.collect.ImmutableList;
-import com.sonarsource.governance.projectdump.protobuf.ProjectDump;
-import java.util.Date;
-import java.util.List;
-import org.apache.commons.lang.RandomStringUtils;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.api.resources.Scopes;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rule.RuleStatus;
-import org.sonar.api.rule.Severity;
-import org.sonar.api.rules.RuleType;
-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.BranchDto;
-import org.sonar.db.component.BranchType;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.issue.IssueDto;
-import org.sonar.db.project.ProjectDto;
-import org.sonar.db.rule.RuleDto;
-
-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.db.component.ComponentDto.UUID_PATH_OF_ROOT;
-
-public class ExportAdHocRulesStepTest {
-  private static final String PROJECT_UUID = "some-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)
-    .setBranchUuid(PROJECT_UUID);
-
-  private static final List<BranchDto> BRANCHES = ImmutableList.of(
-    new BranchDto().setBranchType(BranchType.PULL_REQUEST).setProjectUuid(PROJECT_UUID).setKey("pr-1").setUuid("pr-1-uuid").setMergeBranchUuid("master"),
-    new BranchDto().setBranchType(BranchType.BRANCH).setProjectUuid(PROJECT_UUID).setKey("branch-2").setUuid("branch-2-uuid").setMergeBranchUuid("master")
-      .setExcludeFromPurge(true),
-    new BranchDto().setBranchType(BranchType.BRANCH).setProjectUuid(PROJECT_UUID).setKey("branch-3").setUuid("branch-3-uuid").setMergeBranchUuid("master")
-      .setExcludeFromPurge(false));
-
-  @Rule
-  public LogTester logTester = new LogTester();
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private int issueUuidGenerator = 1;
-  private FakeDumpWriter dumpWriter = new FakeDumpWriter();
-  private ProjectHolder projectHolder = mock(ProjectHolder.class);
-  private ExportAdHocRulesStep underTest = new ExportAdHocRulesStep(dbTester.getDbClient(), projectHolder, dumpWriter);
-
-  @Before
-  public void setup() {
-    ProjectDto project = createProject();
-    when(projectHolder.projectDto()).thenReturn(project);
-  }
-
-  @Test
-  public void export_zero_ad_hoc_rules() {
-    underTest.execute(new TestComputationStepContext());
-
-    List<ProjectDump.AdHocRule> exportedRules = dumpWriter.getWrittenMessagesOf(DumpElement.AD_HOC_RULES);
-    assertThat(exportedRules).isEmpty();
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("0 ad-hoc rules exported");
-  }
-
-  @Test
-  public void execute_only_exports_ad_hoc_rules_that_reference_project_issue() {
-    String differentProject = "diff-proj-uuid";
-    RuleDto rule1 = insertAddHocRule( "rule-1");
-    RuleDto rule2 = insertAddHocRule( "rule-2");
-    insertAddHocRule( "rule-3");
-    insertIssue(rule1, differentProject, differentProject);
-    insertIssue(rule2, PROJECT_UUID, PROJECT_UUID);
-
-    underTest.execute(new TestComputationStepContext());
-
-    List<ProjectDump.AdHocRule> exportedRules = dumpWriter.getWrittenMessagesOf(DumpElement.AD_HOC_RULES);
-    assertThat(exportedRules).hasSize(1);
-    assertProtobufAdHocRuleIsCorrectlyBuilt(exportedRules.iterator().next(), rule2);
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("1 ad-hoc rules exported");
-  }
-
-  @Test
-  public void execute_only_exports_rules_that_are_ad_hoc() {
-    RuleDto rule1 = insertStandardRule("rule-1");
-    RuleDto rule2 = insertExternalRule("rule-2");
-    RuleDto rule3 = insertAddHocRule("rule-3");
-    insertIssue(rule1, PROJECT_UUID, PROJECT_UUID);
-    insertIssue(rule2, PROJECT_UUID, PROJECT_UUID);
-    insertIssue(rule3, PROJECT_UUID, PROJECT_UUID);
-
-    underTest.execute(new TestComputationStepContext());
-
-    List<ProjectDump.AdHocRule> exportedRules = dumpWriter.getWrittenMessagesOf(DumpElement.AD_HOC_RULES);
-    assertThat(exportedRules).hasSize(1);
-    assertProtobufAdHocRuleIsCorrectlyBuilt(exportedRules.iterator().next(), rule3);
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("1 ad-hoc rules exported");
-  }
-
-  @Test
-  public void execute_exports_ad_hoc_rules_that_are_referenced_by_issues_on_branches_excluded_from_purge() {
-    when(projectHolder.branches()).thenReturn(BRANCHES);
-    RuleDto rule1 = insertAddHocRule("rule-1");
-    RuleDto rule2 = insertAddHocRule("rule-2");
-    RuleDto rule3 = insertAddHocRule("rule-3");
-    insertIssue(rule1, "branch-1-uuid", "branch-1-uuid");
-    insertIssue(rule2, "branch-2-uuid", "branch-2-uuid");
-    insertIssue(rule3, "branch-3-uuid", "branch-3-uuid");
-
-    underTest.execute(new TestComputationStepContext());
-
-    List<ProjectDump.AdHocRule> exportedRules = dumpWriter.getWrittenMessagesOf(DumpElement.AD_HOC_RULES);
-    assertThat(exportedRules).hasSize(1);
-    assertProtobufAdHocRuleIsCorrectlyBuilt(exportedRules.iterator().next(), rule2);
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("1 ad-hoc rules exported");
-  }
-
-  @Test
-  public void execute_throws_ISE_with_number_of_successful_exports_before_failure() {
-    RuleDto rule1 = insertAddHocRule("rule-1");
-    RuleDto rule2 = insertAddHocRule("rule-2");
-    RuleDto rule3 = insertAddHocRule("rule-3");
-    insertIssue(rule1, PROJECT_UUID, PROJECT_UUID);
-    insertIssue(rule2, PROJECT_UUID, PROJECT_UUID);
-    insertIssue(rule3, PROJECT_UUID, PROJECT_UUID);
-    dumpWriter.failIfMoreThan(2, DumpElement.AD_HOC_RULES);
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Ad-hoc rules export failed after processing 2 rules successfully");
-  }
-
-  @Test
-  public void getDescription() {
-    assertThat(underTest.getDescription()).isEqualTo("Export ad-hoc rules");
-  }
-
-  private ProjectDto createProject() {
-    Date createdAt = new Date();
-    ComponentDto projectDto = dbTester.components().insertPublicProject(PROJECT);
-    BRANCHES.forEach(branch -> dbTester.components().insertProjectBranch(projectDto, branch).setCreatedAt(createdAt));
-    dbTester.commit();
-    return dbTester.components().getProjectDto(projectDto);
-  }
-
-  private void insertIssue(RuleDto ruleDto, String projectUuid, String componentUuid) {
-    IssueDto dto = createBaseIssueDto(ruleDto, projectUuid, componentUuid);
-    insertIssue(dto);
-  }
-
-  private void insertIssue(IssueDto dto) {
-    dbTester.getDbClient().issueDao().insert(dbTester.getSession(), dto);
-    dbTester.commit();
-  }
-
-  private IssueDto createBaseIssueDto(RuleDto ruleDto, String projectUuid, String componentUuid) {
-    return new IssueDto()
-      .setKee("issue_uuid_" + issueUuidGenerator++)
-      .setComponentUuid(componentUuid)
-      .setProjectUuid(projectUuid)
-      .setRuleUuid(ruleDto.getUuid())
-      .setStatus("OPEN");
-  }
-
-  private RuleDto insertExternalRule(String ruleName) {
-    RuleDto ruleDto = new RuleDto()
-      .setIsExternal(true)
-      .setIsAdHoc(false);
-    return insertRule(ruleName, ruleDto);
-  }
-
-  private RuleDto insertAddHocRule(String ruleName) {
-    RuleDto ruleDto = new RuleDto()
-      .setIsExternal(false)
-      .setIsAdHoc(true)
-      .setAdHocName("ad_hoc_rule" + RandomStringUtils.randomAlphabetic(10))
-      .setAdHocType(RuleType.VULNERABILITY)
-      .setAdHocSeverity(Severity.CRITICAL)
-      .setAdHocDescription("ad hoc description: " + RandomStringUtils.randomAlphanumeric(100));
-    return insertRule(ruleName, ruleDto);
-  }
-
-  private RuleDto insertStandardRule(String ruleName) {
-    RuleDto ruleDto = new RuleDto()
-      .setIsExternal(false)
-      .setIsAdHoc(false);
-    return insertRule(ruleName, ruleDto);
-  }
-
-  private RuleDto insertRule(String ruleName, RuleDto partiallyInitRuleDto) {
-    RuleKey ruleKey = RuleKey.of("plugin1", ruleName);
-    partiallyInitRuleDto
-      .setName("ruleName" + RandomStringUtils.randomAlphanumeric(10))
-      .setRuleKey(ruleKey)
-      .setPluginKey("pluginKey" + RandomStringUtils.randomAlphanumeric(10))
-      .setStatus(RuleStatus.READY)
-      .setScope(RuleDto.Scope.ALL);
-
-    dbTester.rules().insert(partiallyInitRuleDto);
-    dbTester.commit();
-    return dbTester.getDbClient().ruleDao().selectByKey(dbTester.getSession(), ruleKey)
-      .orElseThrow(() -> new RuntimeException("insertAdHocRule failed"));
-  }
-
-  private static void assertProtobufAdHocRuleIsCorrectlyBuilt(ProjectDump.AdHocRule protobufAdHocRule, RuleDto source) {
-    assertThat(protobufAdHocRule.getName()).isEqualTo(source.getName());
-    assertThat(protobufAdHocRule.getRef()).isEqualTo(source.getUuid());
-    assertThat(protobufAdHocRule.getPluginKey()).isEqualTo(source.getPluginKey());
-    assertThat(protobufAdHocRule.getPluginRuleKey()).isEqualTo(source.getRuleKey());
-    assertThat(protobufAdHocRule.getPluginName()).isEqualTo(source.getRepositoryKey());
-    assertThat(protobufAdHocRule.getName()).isEqualTo(source.getName());
-    assertThat(protobufAdHocRule.getStatus()).isEqualTo(source.getStatus().name());
-    assertThat(protobufAdHocRule.getType()).isEqualTo(source.getType());
-    assertThat(protobufAdHocRule.getScope()).isEqualTo(source.getScope().name());
-    assertProtobufAdHocRuleIsCorrectlyBuilt(protobufAdHocRule.getMetadata(), source);
-  }
-
-  private static void assertProtobufAdHocRuleIsCorrectlyBuilt(ProjectDump.AdHocRule.RuleMetadata metadata, RuleDto expected) {
-    assertThat(metadata.getAdHocName()).isEqualTo(expected.getAdHocName());
-    assertThat(metadata.getAdHocDescription()).isEqualTo(expected.getAdHocDescription());
-    assertThat(metadata.getAdHocSeverity()).isEqualTo(expected.getAdHocSeverity());
-    assertThat(metadata.getAdHocType()).isEqualTo(expected.getAdHocType());
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/ExportEventsStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/ExportEventsStepTest.java
deleted file mode 100644 (file)
index 7dc28f6..0000000
+++ /dev/null
@@ -1,167 +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.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.component.ComponentRepositoryImpl;
-import org.sonar.ce.task.step.TestComputationStepContext;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.SnapshotDto;
-import org.sonar.db.event.EventDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.sonar.db.component.ComponentDto.UUID_PATH_OF_ROOT;
-import static org.sonar.db.component.SnapshotDto.STATUS_PROCESSED;
-
-public class ExportEventsStepTest {
-
-  private static final long NOW = 1_450_000_000_000L;
-  private static final long IN_THE_PAST = 1_440_000_000_000L;
-
-  private static final String PROJECT_UUID = "project_uuid";
-  private static final ComponentDto PROJECT = new ComponentDto()
-    .setUuid(PROJECT_UUID)
-    .setUuidPath(UUID_PATH_OF_ROOT)
-    .setBranchUuid(PROJECT_UUID)
-    .setScope(Scopes.PROJECT)
-    .setQualifier(Qualifiers.PROJECT)
-    .setKey("the_project")
-    .setEnabled(true);
-
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  @Rule
-  public LogTester logTester = new LogTester();
-
-  private FakeDumpWriter dumpWriter = new FakeDumpWriter();
-  private MutableProjectHolder projectHolder = new MutableProjectHolderImpl();
-  private ComponentRepositoryImpl componentRepository = new ComponentRepositoryImpl();
-  private ExportEventsStep underTest = new ExportEventsStep(dbTester.getDbClient(), projectHolder, componentRepository, dumpWriter);
-
-  @Before
-  public void setUp() {
-    ComponentDto projectDto = dbTester.components().insertPublicProject(PROJECT);
-    componentRepository.register(1, projectDto.uuid(), false);
-    projectHolder.setProjectDto(dbTester.components().getProjectDto(projectDto));
-  }
-
-  @Test
-  public void export_zero_events() {
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("0 events exported");
-    List<ProjectDump.Event> events = dumpWriter.getWrittenMessagesOf(DumpElement.EVENTS);
-    assertThat(events).isEmpty();
-  }
-
-  @Test
-  public void export_events() {
-    SnapshotDto snapshot = insertSnapshot();
-    insertEvent(snapshot, "E1", "one");
-    insertEvent(snapshot, "E2", "two");
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("2 events exported");
-    List<ProjectDump.Event> events = dumpWriter.getWrittenMessagesOf(DumpElement.EVENTS);
-    assertThat(events).hasSize(2);
-    assertThat(events).extracting(ProjectDump.Event::getUuid).containsOnly("E1", "E2");
-  }
-
-  @Test
-  public void throws_ISE_if_error() {
-    SnapshotDto snapshot = insertSnapshot();
-    insertEvent(snapshot, "E1", "one");
-    insertEvent(snapshot, "E2", "two");
-    dumpWriter.failIfMoreThan(1, DumpElement.EVENTS);
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Event Export failed after processing 1 events successfully");
-  }
-
-  @Test
-  public void export_all_fields() {
-    SnapshotDto snapshot = insertSnapshot();
-    dbTester.getDbClient().eventDao().insert(dbTester.getSession(), new EventDto()
-      .setUuid("E1")
-      .setAnalysisUuid(snapshot.getUuid())
-      .setComponentUuid(snapshot.getComponentUuid())
-      .setDate(IN_THE_PAST)
-      .setCreatedAt(NOW)
-      .setData("data")
-      .setName("name")
-      .setCategory("categ")
-      .setDescription("desc"));
-    dbTester.commit();
-
-    underTest.execute(new TestComputationStepContext());
-
-    ProjectDump.Event event = dumpWriter.getWrittenMessagesOf(DumpElement.EVENTS).get(0);
-    assertThat(event.getUuid()).isEqualTo("E1");
-    assertThat(event.getName()).isEqualTo("name");
-    assertThat(event.getData()).isEqualTo("data");
-    assertThat(event.getCategory()).isEqualTo("categ");
-    assertThat(event.getDescription()).isEqualTo("desc");
-    assertThat(event.getDate()).isEqualTo(IN_THE_PAST);
-    assertThat(event.getAnalysisUuid()).isEqualTo(snapshot.getUuid());
-    assertThat(event.getComponentRef()).isOne();
-  }
-
-  @Test
-  public void getDescription_is_defined() {
-    assertThat(underTest.getDescription()).isEqualTo("Export events");
-  }
-
-  private void insertEvent(SnapshotDto snapshot, String uuid, String name) {
-    dbTester.getDbClient().eventDao().insert(dbTester.getSession(), new EventDto()
-      .setUuid(uuid)
-      .setAnalysisUuid(snapshot.getUuid())
-      .setComponentUuid(snapshot.getComponentUuid())
-      .setDate(IN_THE_PAST)
-      .setCreatedAt(NOW)
-      .setName(name));
-    dbTester.commit();
-  }
-
-  private SnapshotDto insertSnapshot() {
-    SnapshotDto snapshot = new SnapshotDto()
-      .setUuid("U1")
-      .setComponentUuid(PROJECT.uuid())
-      .setStatus(STATUS_PROCESSED)
-      .setLast(false);
-    dbTester.getDbClient().snapshotDao().insert(dbTester.getSession(), snapshot);
-    dbTester.commit();
-    return snapshot;
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/ExportLinksStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/ExportLinksStepTest.java
deleted file mode 100644 (file)
index 4c0ddf0..0000000
+++ /dev/null
@@ -1,135 +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.Link;
-import org.junit.Before;
-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.component.ComponentRepository;
-import org.sonar.ce.task.step.TestComputationStepContext;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ProjectLinkDto;
-import org.sonar.db.project.ProjectExportMapper;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.assertj.core.groups.Tuple.tuple;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.db.component.ComponentDto.UUID_PATH_OF_ROOT;
-
-public class ExportLinksStepTest {
-
-  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)
-    .setBranchUuid(PROJECT_UUID);
-
-  @Rule
-  public DbTester db = DbTester.createWithExtensionMappers(System2.INSTANCE, ProjectExportMapper.class);
-
-
-  @Rule
-  public LogTester logTester = new LogTester();
-
-  private final FakeDumpWriter dumpWriter = new FakeDumpWriter();
-  private final ComponentRepository componentRepository = mock(ComponentRepository.class);
-  private final ProjectHolder projectHolder = mock(ProjectHolder.class);
-  private final ExportLinksStep underTest = new ExportLinksStep(db.getDbClient(), componentRepository, projectHolder, dumpWriter);
-
-  @Before
-  public void setUp() {
-    ComponentDto project = db.components().insertPublicProject(PROJECT);
-    when(projectHolder.projectDto()).thenReturn(db.components().getProjectDto(project));
-    when(componentRepository.getRef(PROJECT_UUID)).thenReturn(1L);
-  }
-
-  @Test
-  public void export_zero_links() {
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("0 links exported");
-    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.LINKS)).isEmpty();
-  }
-
-  @Test
-  public void export_links() {
-    ProjectLinkDto link1 = db.componentLinks().insertCustomLink(PROJECT);
-    ProjectLinkDto link2 = db.componentLinks().insertProvidedLink(PROJECT);
-    db.componentLinks().insertCustomLink(db.components().insertPrivateProject());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("2 links exported");
-    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.LINKS))
-      .extracting(Link::getUuid, Link::getName, Link::getType, Link::getHref)
-      .containsExactlyInAnyOrder(
-        tuple(link1.getUuid(), link1.getName(), link1.getType(), link1.getHref()),
-        tuple(link2.getUuid(), "", link2.getType(), link2.getHref()));
-  }
-
-  @Test
-  public void throws_ISE_if_error() {
-    db.componentLinks().insertCustomLink(PROJECT);
-    db.componentLinks().insertProvidedLink(PROJECT);
-    db.componentLinks().insertProvidedLink(PROJECT);
-    db.componentLinks().insertCustomLink(db.components().insertPrivateProject());
-
-    dumpWriter.failIfMoreThan(2, DumpElement.LINKS);
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Link export failed after processing 2 link(s) successfully");
-  }
-
-  @Test
-  public void test_all_fields() {
-    ProjectLinkDto link = db.componentLinks().insertCustomLink(PROJECT, l -> l.setName("name").setHref("href").setType("type"));
-
-    underTest.execute(new TestComputationStepContext());
-
-    Link reloaded = dumpWriter.getWrittenMessagesOf(DumpElement.LINKS).get(0);
-    assertThat(reloaded.getUuid()).isEqualTo(link.getUuid());
-    assertThat(reloaded.getName()).isEqualTo(link.getName());
-    assertThat(reloaded.getHref()).isEqualTo(link.getHref());
-    assertThat(reloaded.getType()).isEqualTo(link.getType());
-  }
-
-  @Test
-  public void getDescription_is_defined() {
-    assertThat(underTest.getDescription()).isEqualTo("Export links");
-  }
-
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/ExportLiveMeasuresStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/ExportLiveMeasuresStepTest.java
deleted file mode 100644 (file)
index e2747ca..0000000
+++ /dev/null
@@ -1,202 +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.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.metric.MetricDto;
-import org.sonar.db.project.ProjectDto;
-
-import static com.google.common.collect.Lists.newArrayList;
-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;
-import static org.sonar.api.measures.Metric.ValueType.INT;
-
-public class ExportLiveMeasuresStepTest {
-  @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 ExportLiveMeasuresStep underTest = new ExportLiveMeasuresStep(dbTester.getDbClient(), projectHolder, componentRepository, metricRepository, dumpWriter);
-
-  @Test
-  public void export_zero_measures() {
-    when(projectHolder.branches()).thenReturn(newArrayList());
-    when(projectHolder.projectDto()).thenReturn(new ProjectDto().setUuid("does not exist"));
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.LIVE_MEASURES)).isEmpty();
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("0 live measures exported");
-    assertThat(metricRepository.getRefByUuid()).isEmpty();
-  }
-
-  @Test
-  public void export_measures() {
-    ComponentDto project = createProject(true);
-    componentRepository.register(1, project.uuid(), false);
-    MetricDto metric = dbTester.measures().insertMetric(m -> m.setKey("metric1").setValueType(INT.name()));
-    dbTester.measures().insertLiveMeasure(project, metric, m -> m.setValue(4711.0d));
-    when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(project));
-    when(projectHolder.branches()).thenReturn(newArrayList(new BranchDto()
-      .setProjectUuid(project.uuid())
-      .setUuid(project.uuid())
-      .setKey("master")
-      .setBranchType(BranchType.BRANCH)));
-    underTest.execute(new TestComputationStepContext());
-
-    List<ProjectDump.LiveMeasure> exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.LIVE_MEASURES);
-    assertThat(exportedMeasures).hasSize(1);
-    assertThat(exportedMeasures)
-      .extracting(ProjectDump.LiveMeasure::getMetricRef, m -> m.getDoubleValue().getValue(), ProjectDump.LiveMeasure::hasVariation)
-      .containsOnly(tuple(0, 4711.0d, false));
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("1 live measures exported");
-    assertThat(metricRepository.getRefByUuid()).containsOnlyKeys(metric.getUuid());
-  }
-
-  @Test
-  public void do_not_export_measures_on_disabled_projects() {
-    ComponentDto project = createProject(false);
-    componentRepository.register(1, project.uuid(), false);
-    MetricDto metric = dbTester.measures().insertMetric(m -> m.setValueType(INT.name()));
-    dbTester.measures().insertLiveMeasure(project, metric, m -> m.setValue(4711.0d));
-    when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(project));
-    when(projectHolder.branches()).thenReturn(newArrayList(new BranchDto()
-      .setProjectUuid(project.uuid())
-      .setUuid(project.uuid())
-      .setKey("master")
-      .setBranchType(BranchType.BRANCH)));
-
-    underTest.execute(new TestComputationStepContext());
-
-    List<ProjectDump.LiveMeasure> exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.LIVE_MEASURES);
-    assertThat(exportedMeasures).isEmpty();
-  }
-
-  @Test
-  public void do_not_export_measures_on_disabled_metrics() {
-    ComponentDto project = createProject(true);
-    componentRepository.register(1, project.uuid(), false);
-    MetricDto metric = dbTester.measures().insertMetric(m -> m.setValueType(INT.name()).setEnabled(false));
-    dbTester.measures().insertLiveMeasure(project, metric, m -> m.setValue(4711.0d));
-    when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(project));
-    when(projectHolder.branches()).thenReturn(newArrayList(new BranchDto()
-      .setProjectUuid(project.uuid())
-      .setUuid(project.uuid())
-      .setKey("master")
-      .setBranchType(BranchType.BRANCH)));
-
-    underTest.execute(new TestComputationStepContext());
-
-    List<ProjectDump.LiveMeasure> exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.LIVE_MEASURES);
-    assertThat(exportedMeasures).isEmpty();
-  }
-
-  @Test
-  public void test_exported_fields() {
-    ComponentDto project = createProject(true);
-    componentRepository.register(1, project.uuid(), false);
-    MetricDto metric = dbTester.measures().insertMetric(m -> m.setKey("new_metric").setValueType(INT.name()));
-    dbTester.measures().insertLiveMeasure(project, metric, m -> m.setProjectUuid(project.uuid()).setValue(7.0d).setData("test"));
-    when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(project));
-    when(projectHolder.branches()).thenReturn(newArrayList(new BranchDto()
-      .setProjectUuid(project.uuid())
-      .setUuid(project.uuid())
-      .setKey("master")
-      .setBranchType(BranchType.BRANCH)));
-
-    underTest.execute(new TestComputationStepContext());
-
-    List<ProjectDump.LiveMeasure> exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.LIVE_MEASURES);
-    assertThat(exportedMeasures).hasSize(1);
-    assertThat(exportedMeasures)
-      .extracting(
-        ProjectDump.LiveMeasure::getComponentRef,
-        ProjectDump.LiveMeasure::getMetricRef,
-        m -> m.getDoubleValue().getValue(),
-        ProjectDump.LiveMeasure::getTextValue,
-        m -> m.getVariation().getValue())
-      .containsOnly(tuple(
-        1L,
-        0,
-        0.0d,
-        "test",
-        7.0d));
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("1 live measures exported");
-    assertThat(metricRepository.getRefByUuid()).containsOnlyKeys(metric.getUuid());
-  }
-
-  @Test
-  public void test_null_exported_fields() {
-    ComponentDto project = createProject(true);
-    componentRepository.register(1, project.uuid(), false);
-    MetricDto metric = dbTester.measures().insertMetric(m -> m.setValueType(INT.name()));
-    dbTester.measures().insertLiveMeasure(project, metric, m -> m.setProjectUuid(project.uuid()).setValue(null).setData((String) null));
-    when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(project));
-    when(projectHolder.branches()).thenReturn(newArrayList(new BranchDto()
-      .setProjectUuid(project.uuid())
-      .setUuid(project.uuid())
-      .setKey("master")
-      .setBranchType(BranchType.BRANCH)));
-
-    underTest.execute(new TestComputationStepContext());
-
-    List<ProjectDump.LiveMeasure> exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.LIVE_MEASURES);
-    assertThat(exportedMeasures).hasSize(1);
-    assertThat(exportedMeasures)
-      .extracting(
-        ProjectDump.LiveMeasure::hasDoubleValue,
-        ProjectDump.LiveMeasure::getTextValue,
-        ProjectDump.LiveMeasure::hasVariation)
-      .containsOnly(tuple(
-        false,
-        "",
-        false));
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("1 live measures exported");
-    assertThat(metricRepository.getRefByUuid()).containsOnlyKeys(metric.getUuid());
-  }
-
-  @Test
-  public void test_getDescription() {
-    assertThat(underTest.getDescription()).isEqualTo("Export live measures");
-  }
-
-  private ComponentDto createProject(boolean enabled) {
-    return dbTester.components().insertPrivateProject(p -> p.setEnabled(enabled));
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/ExportMetricsStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/ExportMetricsStepTest.java
deleted file mode 100644 (file)
index 2a72b0e..0000000
+++ /dev/null
@@ -1,111 +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 java.util.function.Predicate;
-import javax.annotation.Nonnull;
-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.step.TestComputationStepContext;
-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 ExportMetricsStepTest {
-
-  private static final MetricDto NCLOC = new MetricDto()
-    .setUuid("1")
-    .setKey("ncloc")
-    .setShortName("Lines of code")
-    .setEnabled(true);
-  private static final MetricDto COVERAGE = new MetricDto()
-    .setUuid("2")
-    .setKey("coverage")
-    .setShortName("Coverage")
-    .setEnabled(true);
-
-
-  @Rule
-  public LogTester logTester = new LogTester();
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  MutableMetricRepository metricsHolder = new MutableMetricRepositoryImpl();
-  FakeDumpWriter dumpWriter = new FakeDumpWriter();
-  ExportMetricsStep underTest = new ExportMetricsStep(dbTester.getDbClient(), metricsHolder, dumpWriter);
-
-  @Before
-  public void setUp() {
-    dbTester.getDbClient().metricDao().insert(dbTester.getSession(), NCLOC, COVERAGE);
-    dbTester.commit();
-  }
-
-  @Test
-  public void export_zero_metrics() {
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("0 metrics exported");
-  }
-
-  @Test
-  public void export_metrics() {
-    metricsHolder.add(NCLOC.getUuid());
-    metricsHolder.add(COVERAGE.getUuid());
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("2 metrics exported");
-    List<ProjectDump.Metric> exportedMetrics = dumpWriter.getWrittenMessagesOf(DumpElement.METRICS);
-
-    ProjectDump.Metric ncloc = exportedMetrics.stream().filter(input -> input.getRef() == 0).findAny().orElseThrow();
-    assertThat(ncloc.getRef()).isZero();
-    assertThat(ncloc.getKey()).isEqualTo("ncloc");
-    assertThat(ncloc.getName()).isEqualTo("Lines of code");
-
-    ProjectDump.Metric coverage = exportedMetrics.stream().filter(input -> input.getRef() == 1).findAny().orElseThrow();
-    assertThat(coverage.getRef()).isOne();
-    assertThat(coverage.getKey()).isEqualTo("coverage");
-    assertThat(coverage.getName()).isEqualTo("Coverage");
-  }
-
-  @Test
-  public void throw_ISE_if_error() {
-    metricsHolder.add(NCLOC.getUuid());
-    dumpWriter.failIfMoreThan(0, DumpElement.METRICS);
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Metric Export failed after processing 0 metrics successfully");
-  }
-
-  @Test
-  public void test_getDescription() {
-    assertThat(underTest.getDescription()).isEqualTo("Export metrics");
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/ExportNewCodePeriodsStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/ExportNewCodePeriodsStepTest.java
deleted file mode 100644 (file)
index 0540a66..0000000
+++ /dev/null
@@ -1,174 +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.google.common.collect.ImmutableList;
-import com.sonarsource.governance.projectdump.protobuf.ProjectDump;
-import java.util.Date;
-import java.util.List;
-import javax.annotation.Nullable;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.resources.Qualifiers;
-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.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.newcodeperiod.NewCodePeriodDto;
-import org.sonar.db.newcodeperiod.NewCodePeriodType;
-import org.sonar.db.project.ProjectExportMapper;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.sonar.db.component.ComponentDto.UUID_PATH_OF_ROOT;
-import static org.sonar.db.newcodeperiod.NewCodePeriodType.PREVIOUS_VERSION;
-import static org.sonar.db.newcodeperiod.NewCodePeriodType.SPECIFIC_ANALYSIS;
-
-public class ExportNewCodePeriodsStepTest {
-
-  private static final String PROJECT_UUID = "project_uuid";
-  private static final String ANOTHER_PROJECT_UUID = "another_project_uuid";
-  private static final ComponentDto PROJECT = new ComponentDto()
-    .setUuid(PROJECT_UUID)
-    .setUuidPath(UUID_PATH_OF_ROOT)
-    .setBranchUuid(PROJECT_UUID)
-    .setQualifier(Qualifiers.PROJECT)
-    .setName("project")
-    .setKey("the_project");
-  private static final ComponentDto ANOTHER_PROJECT = new ComponentDto()
-    .setUuid(ANOTHER_PROJECT_UUID)
-    .setUuidPath(UUID_PATH_OF_ROOT)
-    .setBranchUuid(ANOTHER_PROJECT_UUID)
-    .setQualifier(Qualifiers.PROJECT)
-    .setName("another_project")
-    .setKey("another_project");
-
-  private static final List<BranchDto> PROJECT_BRANCHES = ImmutableList.of(
-    new BranchDto().setBranchType(BranchType.PULL_REQUEST).setProjectUuid(PROJECT_UUID).setKey("pr-1").setUuid("pr-uuid-1").setMergeBranchUuid("master"),
-    new BranchDto().setBranchType(BranchType.BRANCH).setProjectUuid(PROJECT_UUID).setKey("branch-1").setUuid("branch-uuid-1").setMergeBranchUuid("master")
-      .setExcludeFromPurge(true),
-    new BranchDto().setBranchType(BranchType.BRANCH).setProjectUuid(PROJECT_UUID).setKey("branch-2").setUuid("branch-uuid-2").setMergeBranchUuid("master")
-      .setExcludeFromPurge(false));
-
-  private static final List<BranchDto> ANOTHER_PROJECT_BRANCHES = ImmutableList.of(
-    new BranchDto().setBranchType(BranchType.BRANCH).setProjectUuid(ANOTHER_PROJECT_UUID).setKey("branch-3").setUuid("branch-uuid-3").setMergeBranchUuid("master")
-      .setExcludeFromPurge(true));
-
-  @Rule
-  public LogTester logTester = new LogTester();
-  @Rule
-  public DbTester dbTester = DbTester.createWithExtensionMappers(System2.INSTANCE, ProjectExportMapper.class);
-
-  private MutableProjectHolder projectHolder = new MutableProjectHolderImpl();
-  private FakeDumpWriter dumpWriter = new FakeDumpWriter();
-  private ExportNewCodePeriodsStep underTest = new ExportNewCodePeriodsStep(dbTester.getDbClient(), projectHolder, dumpWriter);
-
-  @Before
-  public void setUp() {
-    Date createdAt = new Date();
-    ComponentDto projectDto = dbTester.components().insertPublicProject(PROJECT);
-    PROJECT_BRANCHES.forEach(branch -> dbTester.components().insertProjectBranch(projectDto, branch).setCreatedAt(createdAt));
-
-    ComponentDto anotherProjectDto = dbTester.components().insertPublicProject(ANOTHER_PROJECT);
-    ANOTHER_PROJECT_BRANCHES.forEach(branch -> dbTester.components().insertProjectBranch(anotherProjectDto, branch).setCreatedAt(createdAt));
-
-    dbTester.commit();
-    projectHolder.setProjectDto(dbTester.components().getProjectDto(PROJECT));
-  }
-
-  @Test
-  public void export_zero_new_code_periods() {
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.NEW_CODE_PERIODS)).isEmpty();
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("0 new code periods exported");
-  }
-
-  @Test
-  public void export_only_project_new_code_periods_on_branches_excluded_from_purge() {
-    NewCodePeriodDto newCodePeriod1 = newDto("uuid1", PROJECT.uuid(), null, SPECIFIC_ANALYSIS, "analysis-uuid");
-    NewCodePeriodDto newCodePeriod2 = newDto("uuid2", PROJECT.uuid(), "branch-uuid-1", SPECIFIC_ANALYSIS, "analysis-uuid");
-    // the following new code periods are not exported
-    NewCodePeriodDto newCodePeriod3 = newDto("uuid3", PROJECT.uuid(), "branch-uuid-2", SPECIFIC_ANALYSIS, "analysis-uuid");
-    NewCodePeriodDto anotherProjectNewCodePeriods = newDto("uuid4", ANOTHER_PROJECT.uuid(), "branch-uuid-3", SPECIFIC_ANALYSIS, "analysis-uuid");
-    NewCodePeriodDto globalNewCodePeriod = newDto("uuid5", null, null, PREVIOUS_VERSION, null);
-    insertNewCodePeriods(newCodePeriod1, newCodePeriod2, newCodePeriod3, anotherProjectNewCodePeriods, globalNewCodePeriod);
-
-    underTest.execute(new TestComputationStepContext());
-
-    List<ProjectDump.NewCodePeriod> exportedProps = dumpWriter.getWrittenMessagesOf(DumpElement.NEW_CODE_PERIODS);
-    assertThat(exportedProps).hasSize(2);
-    assertThat(exportedProps).extracting(ProjectDump.NewCodePeriod::getUuid).containsOnly("uuid1", "uuid2");
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("2 new code periods exported");
-  }
-
-  @Test
-  public void test_exported_fields() {
-    NewCodePeriodDto dto = newDto("uuid1", PROJECT.uuid(), "branch-uuid-1", SPECIFIC_ANALYSIS, "analysis-uuid");
-    insertNewCodePeriods(dto);
-
-    underTest.execute(new TestComputationStepContext());
-
-    ProjectDump.NewCodePeriod exportedNewCodePeriod = dumpWriter.getWrittenMessagesOf(DumpElement.NEW_CODE_PERIODS).get(0);
-    assertThat(exportedNewCodePeriod.getUuid()).isEqualTo(dto.getUuid());
-    assertThat(exportedNewCodePeriod.getProjectUuid()).isEqualTo(dto.getProjectUuid());
-    assertThat(exportedNewCodePeriod.getBranchUuid()).isEqualTo(dto.getBranchUuid());
-    assertThat(exportedNewCodePeriod.getType()).isEqualTo(dto.getType().name());
-    assertThat(exportedNewCodePeriod.getValue()).isEqualTo(dto.getValue());
-  }
-
-  @Test
-  public void throws_ISE_if_error() {
-    dumpWriter.failIfMoreThan(1, DumpElement.NEW_CODE_PERIODS);
-    insertNewCodePeriods(
-      newDto("uuid1", PROJECT.uuid(), null, SPECIFIC_ANALYSIS, "analysis-uuid"),
-      newDto("uuid2", PROJECT.uuid(), "branch-uuid-1", SPECIFIC_ANALYSIS, "analysis-uuid"));
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("New Code Periods Export failed after processing 1 new code periods successfully");
-  }
-
-  @Test
-  public void test_getDescription() {
-    assertThat(underTest.getDescription()).isEqualTo("Export new code periods");
-  }
-
-  private static NewCodePeriodDto newDto(String uuid, @Nullable String projectUuid, @Nullable String branchUuid, NewCodePeriodType type, @Nullable String value) {
-    return new NewCodePeriodDto()
-      .setUuid(uuid)
-      .setProjectUuid(projectUuid)
-      .setBranchUuid(branchUuid)
-      .setType(type)
-      .setValue(value);
-  }
-
-  private void insertNewCodePeriods(NewCodePeriodDto... dtos) {
-    for (NewCodePeriodDto dto : dtos) {
-      dbTester.getDbClient().newCodePeriodDao().insert(dbTester.getSession(), dto);
-    }
-    dbTester.commit();
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/ExportSettingsStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/ExportSettingsStepTest.java
deleted file mode 100644 (file)
index d895bb9..0000000
+++ /dev/null
@@ -1,166 +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 javax.annotation.Nullable;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.resources.Qualifiers;
-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.projectexport.component.MutableComponentRepository;
-import org.sonar.ce.task.step.TestComputationStepContext;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.project.ProjectExportMapper;
-import org.sonar.db.property.PropertyDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.sonar.db.component.ComponentDto.UUID_PATH_OF_ROOT;
-
-public class ExportSettingsStepTest {
-
-  private static final ComponentDto GLOBAL = null;
-  private static final ComponentDto PROJECT = new ComponentDto()
-    .setUuid("project_uuid")
-    .setUuidPath(UUID_PATH_OF_ROOT)
-    .setBranchUuid("project_uuid")
-    .setKey("the_project");
-  private static final ComponentDto ANOTHER_PROJECT = new ComponentDto()
-    .setUuid("another_project_uuid")
-    .setUuidPath(UUID_PATH_OF_ROOT)
-    .setBranchUuid("another_project_uuid")
-    .setKey("another_project");
-
-  @Rule
-  public LogTester logTester = new LogTester();
-  @Rule
-  public DbTester dbTester = DbTester.createWithExtensionMappers(System2.INSTANCE, ProjectExportMapper.class);
-  private MutableComponentRepository componentRepository = new ComponentRepositoryImpl();
-  private MutableProjectHolder projectHolder = new MutableProjectHolderImpl();
-  private FakeDumpWriter dumpWriter = new FakeDumpWriter();
-  private ExportSettingsStep underTest = new ExportSettingsStep(dbTester.getDbClient(), projectHolder, componentRepository, dumpWriter);
-
-  @Before
-  public void setUp() {
-    dbTester.components().insertPublicProject(PROJECT);
-    dbTester.components().insertPublicProject(ANOTHER_PROJECT);
-    dbTester.commit();
-    projectHolder.setProjectDto(dbTester.components().getProjectDto(PROJECT));
-    componentRepository.register(1, PROJECT.uuid(), false);
-  }
-
-  @Test
-  public void export_zero_settings() {
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.SETTINGS)).isEmpty();
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("0 settings exported");
-  }
-
-  @Test
-  public void export_only_project_settings() {
-    PropertyDto projectProperty1 = newDto("p1", "v1", PROJECT);
-    PropertyDto projectProperty2 = newDto("p2", "v2", PROJECT);
-    // the following properties are not exported
-    PropertyDto propOnOtherProject = newDto("p3", "v3", ANOTHER_PROJECT);
-    PropertyDto globalProperty = newDto("p4", "v4", GLOBAL);
-    insertProperties(PROJECT.getKey(), PROJECT.name(), projectProperty1, projectProperty2);
-    insertProperties(ANOTHER_PROJECT.getKey(), ANOTHER_PROJECT.name(), propOnOtherProject);
-    insertProperties(null, null, globalProperty);
-
-    underTest.execute(new TestComputationStepContext());
-
-    List<ProjectDump.Setting> exportedProps = dumpWriter.getWrittenMessagesOf(DumpElement.SETTINGS);
-    assertThat(exportedProps).hasSize(2);
-    assertThat(exportedProps).extracting(ProjectDump.Setting::getKey).containsOnly("p1", "p2");
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("2 settings exported");
-  }
-
-  @Test
-  public void exclude_properties_specific_to_environment() {
-    insertProperties(PROJECT.getKey(), PROJECT.name(), newDto("sonar.issues.defaultAssigneeLogin", null, PROJECT));
-
-    underTest.execute(new TestComputationStepContext());
-
-    assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.SETTINGS)).isEmpty();
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("0 settings exported");
-  }
-
-  @Test
-  public void test_exported_fields() {
-    PropertyDto dto = newDto("p1", "v1", PROJECT);
-    insertProperties(PROJECT.getKey(), PROJECT.name(), dto);
-
-    underTest.execute(new TestComputationStepContext());
-
-    ProjectDump.Setting exportedProp = dumpWriter.getWrittenMessagesOf(DumpElement.SETTINGS).get(0);
-    assertThat(exportedProp.getKey()).isEqualTo(dto.getKey());
-    assertThat(exportedProp.getValue()).isEqualTo(dto.getValue());
-  }
-
-  @Test
-  public void property_can_have_empty_value() {
-    insertProperties(PROJECT.getKey(), PROJECT.name(), newDto("p1", null, PROJECT));
-
-    underTest.execute(new TestComputationStepContext());
-
-    ProjectDump.Setting exportedProp = dumpWriter.getWrittenMessagesOf(DumpElement.SETTINGS).get(0);
-    assertThat(exportedProp.getKey()).isEqualTo("p1");
-    assertThat(exportedProp.getValue()).isEmpty();
-  }
-
-  @Test
-  public void throws_ISE_if_error() {
-    dumpWriter.failIfMoreThan(1, DumpElement.SETTINGS);
-    insertProperties(PROJECT.getKey(), PROJECT.name(), newDto("p1", null, PROJECT),
-      newDto("p2", null, PROJECT));
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Settings Export failed after processing 1 settings successfully");
-  }
-
-  @Test
-  public void test_getDescription() {
-    assertThat(underTest.getDescription()).isEqualTo("Export settings");
-  }
-
-  private static PropertyDto newDto(String key, @Nullable String value, @Nullable ComponentDto project) {
-    PropertyDto dto = new PropertyDto().setKey(key).setValue(value);
-    if (project != null) {
-      dto.setComponentUuid(project.uuid());
-    }
-    return dto;
-  }
-
-  private void insertProperties(@Nullable String componentKey, @Nullable String componentName, PropertyDto... dtos) {
-    for (PropertyDto dto : dtos) {
-      dbTester.getDbClient().propertiesDao().saveProperty(dbTester.getSession(), dto, null, componentKey, componentName, Qualifiers.VIEW);
-    }
-    dbTester.commit();
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/LoadProjectStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/LoadProjectStepTest.java
deleted file mode 100644 (file)
index bcc9699..0000000
+++ /dev/null
@@ -1,80 +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 org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.api.utils.MessageException;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.projectexport.taskprocessor.ProjectDescriptor;
-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;
-
-public class LoadProjectStepTest {
-
-  private static final String PROJECT_KEY = "project_key";
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private final ProjectDescriptor descriptor = new ProjectDescriptor("project_uuid", PROJECT_KEY, "Project Name");
-  private final MutableProjectHolder definitionHolder = new MutableProjectHolderImpl();
-  private final LoadProjectStep underTest = new LoadProjectStep(descriptor, definitionHolder, dbTester.getDbClient());
-
-  @Test
-  public void fails_if_project_does_not_exist() {
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(MessageException.class)
-      .hasMessage("Project with key [project_key] does not exist");
-  }
-
-  @Test
-  public void fails_if_component_is_not_a_project() {
-    // insert a module, but not a project
-    dbTester.executeInsert("projects",
-      "kee", PROJECT_KEY,
-      "qualifier", Qualifiers.APP,
-      "uuid", "not_used",
-      "private", false,
-      "created_at", 1L,
-      "updated_at", 1L);
-
-    assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext()))
-      .isInstanceOf(MessageException.class)
-      .hasMessage("Project with key [project_key] does not exist");
-  }
-
-  @Test
-  public void registers_project_if_valid() {
-    ComponentDto project = dbTester.components().insertPublicProject(c -> c.setKey(PROJECT_KEY));
-    underTest.execute(new TestComputationStepContext());
-    assertThat(definitionHolder.projectDto().getKey()).isEqualTo(project.getKey());
-  }
-
-  @Test
-  public void getDescription_is_defined() {
-    assertThat(underTest.getDescription()).isNotEmpty();
-  }
-}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/step/ExtractReportStepTest/zip-bomb.zip b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/step/ExtractReportStepTest/zip-bomb.zip
deleted file mode 100644 (file)
index d06da2c..0000000
Binary files a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/step/ExtractReportStepTest/zip-bomb.zip and /dev/null differ
diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectexport/issue/rubbish_data.txt b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectexport/issue/rubbish_data.txt
deleted file mode 100644 (file)
index 3b5aee3..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas hendrerit sapien magna, eget ultrices sapien ornare et. Donec interdum posuere magna nec tristique. Nam a dictum diam. Fusce consequat dolor vel justo egestas, a vulputate urna mattis. Nam at velit a quam eleifend aliquam quis vel est. Praesent facilisis consequat magna, eget aliquam leo eleifend non. Nunc et posuere tortor, sit amet tincidunt metus. Donec bibendum ligula id nisi lobortis, interdum vestibulum neque dignissim. Phasellus ornare sapien at auctor posuere. In hac habitasse platea dictumst. Sed nec tempor purus, sed interdum erat.
-
-Etiam nec porta magna, eget efficitur augue. Maecenas commodo ac nibh a dignissim. Curabitur eu ipsum nibh. Aenean id sodales velit. Vestibulum tristique at lectus eu gravida. Suspendisse vehicula gravida elit, eget fringilla nibh ullamcorper vitae. Integer est neque, aliquet sit amet turpis eget, tempor tincidunt nisi. Cras ornare mollis lacinia. Aenean at dolor scelerisque, rhoncus ex eget, pellentesque justo. Proin at malesuada ex. Praesent feugiat sapien id mollis vulputate. Fusce ornare, ligula id auctor elementum, enim erat finibus velit, vitae auctor metus ante vitae mauris. Quisque efficitur arcu vel enim sollicitudin accumsan. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec gravida erat vitae laoreet posuere.
diff --git a/server/sonar-ce-task/src/it/java/org/sonar/ce/task/log/CeTaskMessagesImplIT.java b/server/sonar-ce-task/src/it/java/org/sonar/ce/task/log/CeTaskMessagesImplIT.java
new file mode 100644 (file)
index 0000000..aab0384
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * 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.log;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.CeTask;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+
+import static java.util.stream.Collectors.toList;
+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.assertj.core.api.Assertions.tuple;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+public class CeTaskMessagesImplIT {
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private DbClient dbClient = dbTester.getDbClient();
+  private UuidFactory uuidFactory = mock(UuidFactory.class);
+  private String taskUuid = randomAlphabetic(12);
+
+  private CeTask ceTask = new CeTask.Builder()
+    .setUuid(taskUuid)
+    .setType(randomAlphabetic(5))
+    .build();
+
+  private CeTaskMessagesImpl underTest = new CeTaskMessagesImpl(dbClient, uuidFactory, ceTask);
+
+  @Test
+  public void add_fails_with_NPE_if_arg_is_null() {
+    assertThatThrownBy(() -> underTest.add(null))
+      .isInstanceOf(NullPointerException.class)
+      .hasMessage("message can't be null");
+  }
+
+  @Test
+  public void add_persist_message_to_DB() {
+    CeTaskMessages.Message message = new CeTaskMessages.Message(randomAlphabetic(20), 2_999L);
+    String uuid = randomAlphanumeric(40);
+    when(uuidFactory.create()).thenReturn(uuid);
+
+    underTest.add(message);
+
+    assertThat(dbTester.select("select uuid as \"UUID\", task_uuid as \"TASK_UUID\", message as \"MESSAGE\", created_at as \"CREATED_AT\" from ce_task_message"))
+      .extracting(t -> t.get("UUID"), t -> t.get("TASK_UUID"), t -> t.get("MESSAGE"), t -> t.get("CREATED_AT"))
+      .containsOnly(tuple(uuid, taskUuid, message.getText(), message.getTimestamp()));
+  }
+
+  @Test
+  public void addAll_fails_with_NPE_if_arg_is_null() {
+    assertThatThrownBy(() -> underTest.addAll(null))
+      .isInstanceOf(NullPointerException.class);
+  }
+
+  @Test
+  public void addAll_fails_with_NPE_if_any_message_in_list_is_null() {
+    Random random = new Random();
+    List<CeTaskMessages.Message> messages = Stream.of(
+      // some (or none) non null Message before null one
+      IntStream.range(0, random.nextInt(5)).mapToObj(i -> new CeTaskMessages.Message(randomAlphabetic(3) + "_i", 1_999L + i)),
+      Stream.of((CeTaskMessages.Message) null),
+      // some (or none) non null Message after null one
+      IntStream.range(0, random.nextInt(5)).mapToObj(i -> new CeTaskMessages.Message(randomAlphabetic(3) + "_i", 1_999L + i)))
+      .flatMap(t -> t)
+      .collect(toList());
+
+    assertThatThrownBy(() -> underTest.addAll(messages))
+      .isInstanceOf(NullPointerException.class)
+      .hasMessage("message can't be null");
+  }
+
+  @Test
+  public void addAll_has_no_effect_if_arg_is_empty() {
+    DbClient dbClientMock = mock(DbClient.class);
+    UuidFactory uuidFactoryMock = mock(UuidFactory.class);
+    CeTask ceTaskMock = mock(CeTask.class);
+    CeTaskMessagesImpl underTest = new CeTaskMessagesImpl(dbClientMock, uuidFactoryMock, ceTaskMock);
+
+    underTest.addAll(Collections.emptyList());
+
+    verifyNoInteractions(dbClientMock, uuidFactoryMock, ceTaskMock);
+  }
+
+  @Test
+  public void addAll_persists_all_messages_to_DB() {
+    int messageCount = 5;
+    String[] uuids = IntStream.range(0, messageCount).mapToObj(i -> "UUId_" + i).toArray(String[]::new);
+    CeTaskMessages.Message[] messages = IntStream.range(0, messageCount)
+      .mapToObj(i -> new CeTaskMessages.Message("message_" + i, 2_999L + i))
+      .toArray(CeTaskMessages.Message[]::new);
+    when(uuidFactory.create()).thenAnswer(new Answer<String>() {
+      int i = 0;
+
+      @Override
+      public String answer(InvocationOnMock invocation) {
+        return uuids[i++];
+      }
+    });
+
+    underTest.addAll(Arrays.stream(messages).collect(Collectors.toList()));
+
+    assertThat(dbTester.select("select uuid as \"UUID\", task_uuid as \"TASK_UUID\", message as \"MESSAGE\", created_at as \"CREATED_AT\" from ce_task_message"))
+      .extracting(t -> t.get("UUID"), t -> t.get("TASK_UUID"), t -> t.get("MESSAGE"), t -> t.get("CREATED_AT"))
+      .containsOnly(
+        tuple(uuids[0], taskUuid, messages[0].getText(), messages[0].getTimestamp()),
+        tuple(uuids[1], taskUuid, messages[1].getText(), messages[1].getTimestamp()),
+        tuple(uuids[2], taskUuid, messages[2].getText(), messages[2].getTimestamp()),
+        tuple(uuids[3], taskUuid, messages[3].getText(), messages[3].getTimestamp()),
+        tuple(uuids[4], taskUuid, messages[4].getText(), messages[4].getTimestamp()));
+  }
+}
diff --git a/server/sonar-ce-task/src/test/java/org/sonar/ce/task/log/CeTaskMessagesImplTest.java b/server/sonar-ce-task/src/test/java/org/sonar/ce/task/log/CeTaskMessagesImplTest.java
deleted file mode 100644 (file)
index f129cfc..0000000
+++ /dev/null
@@ -1,146 +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.log;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Random;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import java.util.stream.Stream;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.CeTask;
-import org.sonar.core.util.UuidFactory;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-
-import static java.util.stream.Collectors.toList;
-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.assertj.core.api.Assertions.tuple;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verifyNoInteractions;
-import static org.mockito.Mockito.when;
-
-public class CeTaskMessagesImplTest {
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private DbClient dbClient = dbTester.getDbClient();
-  private UuidFactory uuidFactory = mock(UuidFactory.class);
-  private String taskUuid = randomAlphabetic(12);
-
-  private CeTask ceTask = new CeTask.Builder()
-    .setUuid(taskUuid)
-    .setType(randomAlphabetic(5))
-    .build();
-
-  private CeTaskMessagesImpl underTest = new CeTaskMessagesImpl(dbClient, uuidFactory, ceTask);
-
-  @Test
-  public void add_fails_with_NPE_if_arg_is_null() {
-    assertThatThrownBy(() -> underTest.add(null))
-      .isInstanceOf(NullPointerException.class)
-      .hasMessage("message can't be null");
-  }
-
-  @Test
-  public void add_persist_message_to_DB() {
-    CeTaskMessages.Message message = new CeTaskMessages.Message(randomAlphabetic(20), 2_999L);
-    String uuid = randomAlphanumeric(40);
-    when(uuidFactory.create()).thenReturn(uuid);
-
-    underTest.add(message);
-
-    assertThat(dbTester.select("select uuid as \"UUID\", task_uuid as \"TASK_UUID\", message as \"MESSAGE\", created_at as \"CREATED_AT\" from ce_task_message"))
-      .extracting(t -> t.get("UUID"), t -> t.get("TASK_UUID"), t -> t.get("MESSAGE"), t -> t.get("CREATED_AT"))
-      .containsOnly(tuple(uuid, taskUuid, message.getText(), message.getTimestamp()));
-  }
-
-  @Test
-  public void addAll_fails_with_NPE_if_arg_is_null() {
-    assertThatThrownBy(() -> underTest.addAll(null))
-      .isInstanceOf(NullPointerException.class);
-  }
-
-  @Test
-  public void addAll_fails_with_NPE_if_any_message_in_list_is_null() {
-    Random random = new Random();
-    List<CeTaskMessages.Message> messages = Stream.of(
-      // some (or none) non null Message before null one
-      IntStream.range(0, random.nextInt(5)).mapToObj(i -> new CeTaskMessages.Message(randomAlphabetic(3) + "_i", 1_999L + i)),
-      Stream.of((CeTaskMessages.Message) null),
-      // some (or none) non null Message after null one
-      IntStream.range(0, random.nextInt(5)).mapToObj(i -> new CeTaskMessages.Message(randomAlphabetic(3) + "_i", 1_999L + i)))
-      .flatMap(t -> t)
-      .collect(toList());
-
-    assertThatThrownBy(() -> underTest.addAll(messages))
-      .isInstanceOf(NullPointerException.class)
-      .hasMessage("message can't be null");
-  }
-
-  @Test
-  public void addAll_has_no_effect_if_arg_is_empty() {
-    DbClient dbClientMock = mock(DbClient.class);
-    UuidFactory uuidFactoryMock = mock(UuidFactory.class);
-    CeTask ceTaskMock = mock(CeTask.class);
-    CeTaskMessagesImpl underTest = new CeTaskMessagesImpl(dbClientMock, uuidFactoryMock, ceTaskMock);
-
-    underTest.addAll(Collections.emptyList());
-
-    verifyNoInteractions(dbClientMock, uuidFactoryMock, ceTaskMock);
-  }
-
-  @Test
-  public void addAll_persists_all_messages_to_DB() {
-    int messageCount = 5;
-    String[] uuids = IntStream.range(0, messageCount).mapToObj(i -> "UUId_" + i).toArray(String[]::new);
-    CeTaskMessages.Message[] messages = IntStream.range(0, messageCount)
-      .mapToObj(i -> new CeTaskMessages.Message("message_" + i, 2_999L + i))
-      .toArray(CeTaskMessages.Message[]::new);
-    when(uuidFactory.create()).thenAnswer(new Answer<String>() {
-      int i = 0;
-
-      @Override
-      public String answer(InvocationOnMock invocation) {
-        return uuids[i++];
-      }
-    });
-
-    underTest.addAll(Arrays.stream(messages).collect(Collectors.toList()));
-
-    assertThat(dbTester.select("select uuid as \"UUID\", task_uuid as \"TASK_UUID\", message as \"MESSAGE\", created_at as \"CREATED_AT\" from ce_task_message"))
-      .extracting(t -> t.get("UUID"), t -> t.get("TASK_UUID"), t -> t.get("MESSAGE"), t -> t.get("CREATED_AT"))
-      .containsOnly(
-        tuple(uuids[0], taskUuid, messages[0].getText(), messages[0].getTimestamp()),
-        tuple(uuids[1], taskUuid, messages[1].getText(), messages[1].getTimestamp()),
-        tuple(uuids[2], taskUuid, messages[2].getText(), messages[2].getTimestamp()),
-        tuple(uuids[3], taskUuid, messages[3].getText(), messages[3].getTimestamp()),
-        tuple(uuids[4], taskUuid, messages[4].getText(), messages[4].getTimestamp()));
-  }
-}
diff --git a/server/sonar-ce/src/it/java/org/sonar/ce/analysis/cache/cleaning/AnalysisCacheCleaningSchedulerImplIT.java b/server/sonar-ce/src/it/java/org/sonar/ce/analysis/cache/cleaning/AnalysisCacheCleaningSchedulerImplIT.java
new file mode 100644 (file)
index 0000000..8aa991e
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * 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.analysis.cache.cleaning;
+
+import java.io.ByteArrayInputStream;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.platform.Server;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.SequenceUuidFactory;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.scannercache.ScannerAnalysisCacheDao;
+
+import static java.util.concurrent.TimeUnit.DAYS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+public class AnalysisCacheCleaningSchedulerImplIT {
+  private System2 system2 = mock(System2.class);
+  private final static UuidFactory uuidFactory = new SequenceUuidFactory();
+  @Rule
+  public DbTester dbTester = DbTester.create(system2);
+  private DbSession dbSession = dbTester.getSession();
+  private ScannerAnalysisCacheDao scannerAnalysisCacheDao = dbTester.getDbClient().scannerAnalysisCacheDao();
+
+  AnalysisCacheCleaningExecutorService executorService = mock(AnalysisCacheCleaningExecutorService.class);
+
+  AnalysisCacheCleaningSchedulerImpl underTest = new AnalysisCacheCleaningSchedulerImpl(executorService, dbTester.getDbClient());
+
+  @Test
+  public void startSchedulingOnServerStart() {
+    underTest.onServerStart(mock(Server.class));
+    verify(executorService, times(1)).scheduleAtFixedRate(any(Runnable.class), anyLong(), eq(DAYS.toSeconds(1)), eq(SECONDS));
+  }
+
+  @Test
+  public void clean_data_older_than_7_days() {
+    var snapshotDao = dbTester.getDbClient().snapshotDao();
+    var snapshot1 = createSnapshot(LocalDateTime.now().minusDays(1).toInstant(ZoneOffset.UTC).toEpochMilli());
+    snapshotDao.insert(dbSession, snapshot1);
+    scannerAnalysisCacheDao.insert(dbSession, snapshot1.getComponentUuid(), new ByteArrayInputStream("data".getBytes()));
+    var snapshot2 = createSnapshot(LocalDateTime.now().minusDays(6).toInstant(ZoneOffset.UTC).toEpochMilli());
+    snapshotDao.insert(dbSession, snapshot2);
+    scannerAnalysisCacheDao.insert(dbSession, snapshot2.getComponentUuid(), new ByteArrayInputStream("data".getBytes()));
+    var snapshot3 = createSnapshot(LocalDateTime.now().minusDays(8).toInstant(ZoneOffset.UTC).toEpochMilli());
+    snapshotDao.insert(dbSession, snapshot3);
+    scannerAnalysisCacheDao.insert(dbSession, snapshot3.getComponentUuid(), new ByteArrayInputStream("data".getBytes()));
+    var snapshot4 = createSnapshot(LocalDateTime.now().minusDays(30).toInstant(ZoneOffset.UTC).toEpochMilli());
+    snapshotDao.insert(dbSession, snapshot4);
+    scannerAnalysisCacheDao.insert(dbSession, snapshot4.getComponentUuid(), new ByteArrayInputStream("data".getBytes()));
+
+    assertThat(dbTester.countRowsOfTable("scanner_analysis_cache")).isEqualTo(4);
+
+    underTest.clean();
+
+    assertThat(dbTester.countRowsOfTable("scanner_analysis_cache")).isEqualTo(2);
+    assertThat(scannerAnalysisCacheDao.selectData(dbSession, snapshot1.getComponentUuid())).isNotNull();
+    assertThat(scannerAnalysisCacheDao.selectData(dbSession, snapshot2.getComponentUuid())).isNotNull();
+    assertThat(scannerAnalysisCacheDao.selectData(dbSession, snapshot3.getComponentUuid())).isNull();
+    assertThat(scannerAnalysisCacheDao.selectData(dbSession, snapshot4.getComponentUuid())).isNull();
+  }
+
+  private static SnapshotDto createSnapshot(long buildtime) {
+    return new SnapshotDto()
+      .setUuid(uuidFactory.create())
+      .setComponentUuid(uuidFactory.create())
+      .setStatus("P")
+      .setLast(true)
+      .setProjectVersion("2.1-SNAPSHOT")
+      .setPeriodMode("days1")
+      .setPeriodParam("30")
+      .setPeriodDate(buildtime)
+      .setBuildDate(buildtime);
+  }
+
+}
diff --git a/server/sonar-ce/src/it/java/org/sonar/ce/container/ComputeEngineContainerImplIT.java b/server/sonar-ce/src/it/java/org/sonar/ce/container/ComputeEngineContainerImplIT.java
new file mode 100644 (file)
index 0000000..8a57a3f
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * 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.container;
+
+import com.google.common.collect.ImmutableSet;
+import java.io.File;
+import java.io.IOException;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Properties;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang.StringUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.api.utils.System2;
+import org.sonar.core.extension.ServiceLoaderWrapper;
+import org.sonar.db.DbTester;
+import org.sonar.db.property.PropertyDto;
+import org.sonar.process.ProcessId;
+import org.sonar.process.ProcessProperties;
+import org.sonar.process.Props;
+import org.sonar.server.property.InternalProperties;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+import static java.lang.String.valueOf;
+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.ProcessEntryPoint.PROPERTY_PROCESS_INDEX;
+import static org.sonar.process.ProcessEntryPoint.PROPERTY_SHARED_PATH;
+import static org.sonar.process.ProcessProperties.Property.JDBC_PASSWORD;
+import static org.sonar.process.ProcessProperties.Property.JDBC_URL;
+import static org.sonar.process.ProcessProperties.Property.JDBC_USERNAME;
+import static org.sonar.process.ProcessProperties.Property.PATH_DATA;
+import static org.sonar.process.ProcessProperties.Property.PATH_HOME;
+import static org.sonar.process.ProcessProperties.Property.PATH_TEMP;
+
+public class ComputeEngineContainerImplIT {
+
+  @Rule
+  public TemporaryFolder tempFolder = new TemporaryFolder();
+  @Rule
+
+  public DbTester db = DbTester.create(System2.INSTANCE);
+
+  private final ServiceLoaderWrapper serviceLoaderWrapper = mock(ServiceLoaderWrapper.class);
+  private final ProcessProperties processProperties = new ProcessProperties(serviceLoaderWrapper);
+  private final ComputeEngineContainerImpl underTest = new ComputeEngineContainerImpl();
+
+  @Before
+  public void setUp() {
+    when(serviceLoaderWrapper.load()).thenReturn(ImmutableSet.of());
+    underTest.setComputeEngineStatus(mock(ComputeEngineStatus.class));
+  }
+
+  @Test
+  public void constructor_does_not_create_container() {
+    assertThat(underTest.getComponentContainer()).isNull();
+  }
+
+  @Test
+  public void test_real_start() throws IOException {
+    Properties properties = getProperties();
+
+    // required persisted properties
+    insertProperty(CoreProperties.SERVER_ID, "a_server_id");
+    insertProperty(CoreProperties.SERVER_STARTTIME, DateUtils.formatDateTime(new Date()));
+    insertInternalProperty(InternalProperties.SERVER_ID_CHECKSUM, DigestUtils.sha256Hex("a_server_id|" + cleanJdbcUrl()));
+
+    underTest.start(new Props(properties));
+
+    AnnotationConfigApplicationContext context = underTest.getComponentContainer().context();
+    try {
+      assertThat(context.getBeanDefinitionNames()).hasSizeGreaterThan(1);
+      assertThat(context.getParent().getBeanDefinitionNames()).hasSizeGreaterThan(1);
+      assertThat(context.getParent().getParent().getBeanDefinitionNames()).hasSizeGreaterThan(1);
+      assertThat(context.getParent().getParent().getParent().getBeanDefinitionNames()).hasSizeGreaterThan(1);
+      assertThat(context.getParent().getParent().getParent().getParent()).isNull();
+    } finally {
+      underTest.stop();
+    }
+
+    assertThat(context.isActive()).isFalse();
+  }
+
+  private String cleanJdbcUrl() {
+    return StringUtils.lowerCase(StringUtils.substringBefore(db.getUrl(), "?"), Locale.ENGLISH);
+  }
+
+  private Properties getProperties() throws IOException {
+    Properties properties = new Properties();
+    Props props = new Props(properties);
+    processProperties.completeDefaults(props);
+    properties = props.rawProperties();
+    File homeDir = tempFolder.newFolder();
+    File dataDir = new File(homeDir, "data");
+    dataDir.mkdirs();
+    File tmpDir = new File(homeDir, "tmp");
+    tmpDir.mkdirs();
+    properties.setProperty(PATH_HOME.getKey(), homeDir.getAbsolutePath());
+    properties.setProperty(PATH_DATA.getKey(), dataDir.getAbsolutePath());
+    properties.setProperty(PATH_TEMP.getKey(), tmpDir.getAbsolutePath());
+    properties.setProperty(PROPERTY_PROCESS_INDEX, valueOf(ProcessId.COMPUTE_ENGINE.getIpcIndex()));
+    properties.setProperty(PROPERTY_SHARED_PATH, tmpDir.getAbsolutePath());
+    properties.setProperty(JDBC_URL.getKey(), db.getUrl());
+    properties.setProperty(JDBC_USERNAME.getKey(), "sonar");
+    properties.setProperty(JDBC_PASSWORD.getKey(), "sonar");
+    return properties;
+  }
+
+  private void insertProperty(String key, String value) {
+    PropertyDto dto = new PropertyDto().setKey(key).setValue(value);
+    db.getDbClient().propertiesDao().saveProperty(db.getSession(), dto, null, null, null, null);
+    db.commit();
+  }
+
+  private void insertInternalProperty(String key, String value) {
+    db.getDbClient().internalPropertiesDao().save(db.getSession(), key, value);
+    db.commit();
+  }
+}
diff --git a/server/sonar-ce/src/it/java/org/sonar/ce/monitoring/CeDatabaseMBeanImplIT.java b/server/sonar-ce/src/it/java/org/sonar/ce/monitoring/CeDatabaseMBeanImplIT.java
new file mode 100644 (file)
index 0000000..8f43bd9
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * 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.monitoring;
+
+import java.lang.management.ManagementFactory;
+import javax.annotation.CheckForNull;
+import javax.management.InstanceNotFoundException;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.utils.System2;
+import org.sonar.db.DbTester;
+import org.sonar.process.Jmx;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class CeDatabaseMBeanImplIT {
+
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private final CeDatabaseMBeanImpl underTest = new CeDatabaseMBeanImpl(dbTester.getDbClient());
+
+  @BeforeClass
+  public static void beforeClass() {
+    // if any other class starts a container where CeDatabaseMBeanImpl is added, it will have been registered
+    Jmx.unregister("SonarQube:name=ComputeEngineDatabaseConnection");
+  }
+
+  @Test
+  public void register_and_unregister() throws Exception {
+    assertThat(getMBean()).isNull();
+
+    underTest.start();
+    assertThat(getMBean()).isNotNull();
+
+    underTest.stop();
+    assertThat(getMBean()).isNull();
+  }
+
+  @Test
+  public void export_system_info() {
+    ProtobufSystemInfo.Section section = underTest.toProtobuf();
+    assertThat(section.getName()).isEqualTo("Compute Engine Database Connection");
+    assertThat(section.getAttributesCount()).isEqualTo(7);
+    assertThat(section.getAttributes(0).getKey()).isEqualTo("Pool Total Connections");
+    assertThat(section.getAttributes(0).getLongValue()).isPositive();
+  }
+
+  @CheckForNull
+  private ObjectInstance getMBean() throws Exception {
+    try {
+      return ManagementFactory.getPlatformMBeanServer().getObjectInstance(new ObjectName("SonarQube:name=ComputeEngineDatabaseConnection"));
+    } catch (InstanceNotFoundException e) {
+      return null;
+    }
+  }
+}
diff --git a/server/sonar-ce/src/it/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationExecutionListenerIT.java b/server/sonar-ce/src/it/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationExecutionListenerIT.java
new file mode 100644 (file)
index 0000000..97d228c
--- /dev/null
@@ -0,0 +1,337 @@
+/*
+ * 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.notification;
+
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.Random;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.CeTask;
+import org.sonar.ce.task.CeTaskResult;
+import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotification;
+import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotificationBuilder;
+import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotificationSerializer;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.RowNotFoundException;
+import org.sonar.db.ce.CeActivityDto;
+import org.sonar.db.ce.CeQueueDto;
+import org.sonar.db.ce.CeTaskTypes;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.project.ProjectDto;
+import org.sonar.server.notification.NotificationService;
+
+import static java.util.Collections.singleton;
+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.ArgumentMatchers.same;
+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.db.component.ComponentTesting.newDirectory;
+
+public class ReportAnalysisFailureNotificationExecutionListenerIT {
+  @Rule
+  public DbTester dbTester = DbTester.create(System2.INSTANCE);
+
+  private final Random random = new Random();
+  private final DbClient dbClient = dbTester.getDbClient();
+  private final NotificationService notificationService = mock(NotificationService.class);
+  private final ReportAnalysisFailureNotificationSerializer serializer = mock(ReportAnalysisFailureNotificationSerializer.class);
+  private final System2 system2 = mock(System2.class);
+  private final DbClient dbClientMock = mock(DbClient.class);
+  private final CeTask ceTaskMock = mock(CeTask.class);
+  private final Throwable throwableMock = mock(Throwable.class);
+  private final CeTaskResult ceTaskResultMock = mock(CeTaskResult.class);
+  private final ReportAnalysisFailureNotificationExecutionListener fullMockedUnderTest = new ReportAnalysisFailureNotificationExecutionListener(
+    notificationService, dbClientMock, serializer, system2);
+  private final ReportAnalysisFailureNotificationExecutionListener underTest = new ReportAnalysisFailureNotificationExecutionListener(
+    notificationService, dbClient, serializer, system2);
+
+  @Test
+  public void onStart_has_no_effect() {
+    CeTask mockedCeTask = mock(CeTask.class);
+
+    fullMockedUnderTest.onStart(mockedCeTask);
+
+    verifyNoInteractions(mockedCeTask, notificationService, dbClientMock, serializer, system2);
+  }
+
+  @Test
+  public void onEnd_has_no_effect_if_status_is_SUCCESS() {
+    fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.SUCCESS, randomDuration(), ceTaskResultMock, throwableMock);
+
+    verifyNoInteractions(ceTaskMock, ceTaskResultMock, throwableMock, notificationService, dbClientMock, serializer, system2);
+  }
+
+  @Test
+  public void onEnd_has_no_effect_if_CeTask_type_is_not_report() {
+    when(ceTaskMock.getType()).thenReturn(randomAlphanumeric(12));
+
+    fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
+
+    verifyNoInteractions(ceTaskResultMock, throwableMock, notificationService, dbClientMock, serializer, system2);
+  }
+
+  @Test
+  public void onEnd_has_no_effect_if_CeTask_has_no_component_uuid() {
+    when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
+
+    fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
+
+    verifyNoInteractions(ceTaskResultMock, throwableMock, notificationService, dbClientMock, serializer, system2);
+  }
+
+  @Test
+  public void onEnd_has_no_effect_if_there_is_no_subscriber_for_ReportAnalysisFailureNotification_type() {
+    String componentUuid = randomAlphanumeric(6);
+    when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
+    when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
+    when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class)))
+      .thenReturn(false);
+
+    fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
+
+    verifyNoInteractions(ceTaskResultMock, throwableMock, dbClientMock, serializer, system2);
+  }
+
+  @Test
+  public void onEnd_fails_with_ISE_if_project_does_not_exist_in_DB() {
+    String componentUuid = randomAlphanumeric(6);
+    when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
+    when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
+    when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class)))
+      .thenReturn(true);
+
+    Duration randomDuration = randomDuration();
+    assertThatThrownBy(() -> underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Could not find project uuid " + componentUuid);
+  }
+
+  @Test
+  public void onEnd_fails_with_ISE_if_branch_does_not_exist_in_DB() {
+    String componentUuid = randomAlphanumeric(6);
+    ProjectDto project = new ProjectDto().setUuid(componentUuid).setKey(randomAlphanumeric(5)).setQualifier(Qualifiers.PROJECT);
+    dbTester.getDbClient().projectDao().insert(dbTester.getSession(), project);
+    dbTester.getSession().commit();
+    when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
+    when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
+    when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class)))
+      .thenReturn(true);
+
+    Duration randomDuration = randomDuration();
+    assertThatThrownBy(() -> underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessage("Could not find a branch for project uuid " + componentUuid);
+  }
+
+  @Test
+  public void onEnd_fails_with_IAE_if_component_is_not_a_project() {
+    when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
+    ComponentDto project = dbTester.components().insertPrivateProject();
+    ComponentDto directory = dbTester.components().insertComponent(newDirectory(project, randomAlphanumeric(12)));
+    ComponentDto file = dbTester.components().insertComponent(ComponentTesting.newFileDto(project));
+    ComponentDto view = dbTester.components().insertComponent(ComponentTesting.newPortfolio());
+    ComponentDto subView = dbTester.components().insertComponent(ComponentTesting.newSubPortfolio(view));
+    ComponentDto projectCopy = dbTester.components().insertComponent(ComponentTesting.newProjectCopy(project, subView));
+    ComponentDto application = dbTester.components().insertComponent(ComponentTesting.newApplication());
+
+    Arrays.asList(directory, file, view, subView, projectCopy, application)
+      .forEach(component -> {
+
+      when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(component.uuid(), null, null)));
+      when(notificationService.hasProjectSubscribersForTypes(component.uuid(), singleton(ReportAnalysisFailureNotification.class)))
+        .thenReturn(true);
+
+        Duration randomDuration = randomDuration();
+        try {
+          underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock);
+
+          fail("An IllegalArgumentException should have been thrown for component " + component);
+        } catch (IllegalArgumentException e) {
+          assertThat(e.getMessage()).isEqualTo(String.format("Component %s must be a project (qualifier=%s)", component.uuid(), component.qualifier()));
+        } catch (IllegalStateException e) {
+          assertThat(e.getMessage()).isEqualTo("Could not find project uuid " + component.uuid());
+        }
+      });
+  }
+
+  @Test
+  public void onEnd_fails_with_RowNotFoundException_if_activity_for_task_does_not_exist_in_DB() {
+    String componentUuid = randomAlphanumeric(6);
+    String taskUuid = randomAlphanumeric(6);
+    when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
+    when(ceTaskMock.getUuid()).thenReturn(taskUuid);
+    when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
+    when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class)))
+      .thenReturn(true);
+    dbTester.components().insertPrivateProject(s -> s.setUuid(componentUuid));
+
+    Duration randomDuration = randomDuration();
+    assertThatThrownBy(() -> underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock))
+      .isInstanceOf(RowNotFoundException.class)
+      .hasMessage("CeActivity with uuid '" + taskUuid + "' not found");
+  }
+
+  @Test
+  public void onEnd_creates_notification_with_data_from_activity_and_project_and_deliver_it() {
+    String taskUuid = randomAlphanumeric(12);
+    int createdAt = random.nextInt(999_999);
+    long executedAt = random.nextInt(999_999);
+    ComponentDto project = initMocksToPassConditions(taskUuid, createdAt, executedAt);
+    Notification notificationMock = mockSerializer();
+
+    underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
+
+    ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
+    verify(notificationService).deliver(same(notificationMock));
+
+    ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
+
+    ReportAnalysisFailureNotificationBuilder.Project notificationProject = reportAnalysisFailureNotificationBuilder.project();
+    assertThat(notificationProject.name()).isEqualTo(project.name());
+    assertThat(notificationProject.key()).isEqualTo(project.getKey());
+    assertThat(notificationProject.uuid()).isEqualTo(project.uuid());
+    assertThat(notificationProject.branchName()).isNull();
+    ReportAnalysisFailureNotificationBuilder.Task notificationTask = reportAnalysisFailureNotificationBuilder.task();
+    assertThat(notificationTask.uuid()).isEqualTo(taskUuid);
+    assertThat(notificationTask.createdAt()).isEqualTo(createdAt);
+    assertThat(notificationTask.failedAt()).isEqualTo(executedAt);
+  }
+
+  @Test
+  public void onEnd_creates_notification_with_error_message_from_Throwable_argument_message() {
+    initMocksToPassConditions(randomAlphanumeric(12), random.nextInt(999_999), (long) random.nextInt(999_999));
+    String message = randomAlphanumeric(66);
+    when(throwableMock.getMessage()).thenReturn(message);
+
+    underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
+
+    ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
+
+    ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
+    assertThat(reportAnalysisFailureNotificationBuilder.errorMessage()).isEqualTo(message);
+  }
+
+  @Test
+  public void onEnd_creates_notification_with_null_error_message_if_Throwable_is_null() {
+    String taskUuid = randomAlphanumeric(12);
+    initMocksToPassConditions(taskUuid, random.nextInt(999_999), (long) random.nextInt(999_999));
+    Notification notificationMock = mockSerializer();
+
+    underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, null);
+
+    verify(notificationService).deliver(same(notificationMock));
+    ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
+
+    ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
+    assertThat(reportAnalysisFailureNotificationBuilder.errorMessage()).isNull();
+  }
+
+  @Test
+  public void onEnd_ignores_null_CeTaskResult_argument() {
+    String taskUuid = randomAlphanumeric(12);
+    initMocksToPassConditions(taskUuid, random.nextInt(999_999), (long) random.nextInt(999_999));
+    Notification notificationMock = mockSerializer();
+
+    underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), null, null);
+
+    verify(notificationService).deliver(same(notificationMock));
+  }
+
+  @Test
+  public void onEnd_ignores_CeTaskResult_argument() {
+    String taskUuid = randomAlphanumeric(12);
+    initMocksToPassConditions(taskUuid, random.nextInt(999_999), (long) random.nextInt(999_999));
+    Notification notificationMock = mockSerializer();
+
+    underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, null);
+
+    verify(notificationService).deliver(same(notificationMock));
+    verifyNoInteractions(ceTaskResultMock);
+  }
+
+  @Test
+  public void onEnd_uses_system_data_as_failedAt_if_task_has_no_executedAt() {
+    String taskUuid = randomAlphanumeric(12);
+    initMocksToPassConditions(taskUuid, random.nextInt(999_999), null);
+    long now = random.nextInt(999_999);
+    when(system2.now()).thenReturn(now);
+    Notification notificationMock = mockSerializer();
+
+    underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, null);
+
+    verify(notificationService).deliver(same(notificationMock));
+    ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
+    assertThat(notificationCaptor.getValue().task().failedAt()).isEqualTo(now);
+  }
+
+  private ReportAnalysisFailureNotification mockSerializer() {
+    ReportAnalysisFailureNotification notificationMock = mock(ReportAnalysisFailureNotification.class);
+    when(serializer.toNotification(any(ReportAnalysisFailureNotificationBuilder.class))).thenReturn(notificationMock);
+    return notificationMock;
+  }
+
+  private ComponentDto initMocksToPassConditions(String taskUuid, int createdAt, @Nullable Long executedAt) {
+    ComponentDto project = random.nextBoolean() ? dbTester.components().insertPrivateProject() : dbTester.components().insertPublicProject();
+    when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
+    when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(project.uuid(), null, null)));
+    when(ceTaskMock.getUuid()).thenReturn(taskUuid);
+    when(notificationService.hasProjectSubscribersForTypes(project.uuid(), singleton(ReportAnalysisFailureNotification.class)))
+      .thenReturn(true);
+    insertActivityDto(taskUuid, createdAt, executedAt, project);
+    return project;
+  }
+
+  private void insertActivityDto(String taskUuid, int createdAt, @Nullable Long executedAt, ComponentDto project) {
+    dbClient.ceActivityDao().insert(dbTester.getSession(), new CeActivityDto(new CeQueueDto()
+      .setUuid(taskUuid)
+      .setTaskType(CeTaskTypes.REPORT)
+      .setComponentUuid(project.uuid())
+      .setCreatedAt(createdAt))
+      .setExecutedAt(executedAt)
+      .setStatus(CeActivityDto.Status.FAILED));
+    dbTester.getSession().commit();
+  }
+
+  private ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> verifyAndCaptureSerializedNotification() {
+    ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = ArgumentCaptor.forClass(ReportAnalysisFailureNotificationBuilder.class);
+    verify(serializer).toNotification(notificationCaptor.capture());
+    return notificationCaptor;
+  }
+
+  private Duration randomDuration() {
+    return Duration.of(random.nextLong(), ChronoUnit.MILLIS);
+  }
+}
diff --git a/server/sonar-ce/src/it/java/org/sonar/ce/queue/InternalCeQueueImplIT.java b/server/sonar-ce/src/it/java/org/sonar/ce/queue/InternalCeQueueImplIT.java
new file mode 100644 (file)
index 0000000..fdf5b58
--- /dev/null
@@ -0,0 +1,728 @@
+/*
+ * 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.queue;
+
+import com.google.common.collect.ImmutableSet;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+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.config.Configuration;
+import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.container.ComputeEngineStatus;
+import org.sonar.ce.monitoring.CEQueueStatus;
+import org.sonar.ce.monitoring.CEQueueStatusImpl;
+import org.sonar.ce.task.CeTask;
+import org.sonar.ce.task.CeTaskResult;
+import org.sonar.ce.task.TypedException;
+import org.sonar.core.util.UuidFactory;
+import org.sonar.core.util.UuidFactoryImpl;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.ce.CeActivityDto;
+import org.sonar.db.ce.CeQueueDto;
+import org.sonar.db.ce.CeQueueTesting;
+import org.sonar.db.ce.CeTaskTypes;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.user.UserDto;
+import org.sonar.server.platform.NodeInformation;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyMap;
+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.ArgumentMatchers.anyLong;
+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.container.ComputeEngineStatus.Status.STARTED;
+import static org.sonar.ce.container.ComputeEngineStatus.Status.STOPPING;
+
+public class InternalCeQueueImplIT {
+
+  private static final String AN_ANALYSIS_UUID = "U1";
+  private static final String WORKER_UUID_1 = "worker uuid 1";
+  private static final String WORKER_UUID_2 = "worker uuid 2";
+  private static final String NODE_NAME = "nodeName1";
+
+  private System2 system2 = new AlwaysIncreasingSystem2();
+
+  @Rule
+  public DbTester db = DbTester.create(system2);
+
+  private DbSession session = db.getSession();
+
+  private UuidFactory uuidFactory = UuidFactoryImpl.INSTANCE;
+  private CEQueueStatus queueStatus = new CEQueueStatusImpl(db.getDbClient(), mock(System2.class));
+  private ComputeEngineStatus computeEngineStatus = mock(ComputeEngineStatus.class);
+  private Configuration config = mock(Configuration.class);
+  private NextPendingTaskPicker nextPendingTaskPicker = new NextPendingTaskPicker(config, db.getDbClient());
+  private NodeInformation nodeInformation = mock(NodeInformation.class);
+  private InternalCeQueue underTest = new InternalCeQueueImpl(system2, db.getDbClient(), uuidFactory, queueStatus,
+    computeEngineStatus, nextPendingTaskPicker, nodeInformation);
+
+  @Before
+  public void setUp() {
+    when(config.getBoolean(any())).thenReturn(Optional.of(false));
+    when(computeEngineStatus.getStatus()).thenReturn(STARTED);
+    when(nodeInformation.getNodeName()).thenReturn(Optional.of(NODE_NAME));
+  }
+
+  @Test
+  public void submit_returns_task_populated_from_CeTaskSubmit_and_creates_CeQueue_row() {
+    CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"), "rob");
+    CeTask task = underTest.submit(taskSubmit);
+    UserDto userDto = db.getDbClient().userDao().selectByUuid(db.getSession(), taskSubmit.getSubmitterUuid());
+    verifyCeTask(taskSubmit, task, null, userDto);
+    verifyCeQueueDtoForTaskSubmit(taskSubmit);
+  }
+
+  @Test
+  public void submit_populates_component_name_and_key_of_CeTask_if_component_exists() {
+    ComponentDto componentDto = insertComponent(newProjectDto("PROJECT_1"));
+    CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, componentDto, null);
+
+    CeTask task = underTest.submit(taskSubmit);
+
+    verifyCeTask(taskSubmit, task, componentDto, null);
+  }
+
+  @Test
+  public void submit_returns_task_without_component_info_when_submit_has_none() {
+    CeTaskSubmit taskSubmit = createTaskSubmit("not cpt related");
+
+    CeTask task = underTest.submit(taskSubmit);
+
+    verifyCeTask(taskSubmit, task, null, null);
+  }
+
+  @Test
+  public void massSubmit_returns_tasks_for_each_CeTaskSubmit_populated_from_CeTaskSubmit_and_creates_CeQueue_row_for_each() {
+    CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"), "rob");
+    CeTaskSubmit taskSubmit2 = createTaskSubmit("some type");
+
+    List<CeTask> tasks = underTest.massSubmit(asList(taskSubmit1, taskSubmit2));
+
+    UserDto userDto1 = db.getDbClient().userDao().selectByUuid(db.getSession(), taskSubmit1.getSubmitterUuid());
+    assertThat(tasks).hasSize(2);
+    verifyCeTask(taskSubmit1, tasks.get(0), null, userDto1);
+    verifyCeTask(taskSubmit2, tasks.get(1), null, null);
+    verifyCeQueueDtoForTaskSubmit(taskSubmit1);
+    verifyCeQueueDtoForTaskSubmit(taskSubmit2);
+  }
+
+  @Test
+  public void massSubmit_populates_component_name_and_key_of_CeTask_if_component_exists() {
+    ComponentDto componentDto1 = insertComponent(newProjectDto("PROJECT_1"));
+    CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, componentDto1, null);
+    CeTaskSubmit taskSubmit2 = createTaskSubmit("something", newProjectDto("non existing component uuid"), null);
+
+    List<CeTask> tasks = underTest.massSubmit(asList(taskSubmit1, taskSubmit2));
+
+    assertThat(tasks).hasSize(2);
+    verifyCeTask(taskSubmit1, tasks.get(0), componentDto1, null);
+    verifyCeTask(taskSubmit2, tasks.get(1), null, null);
+  }
+
+  @Test
+  public void peek_throws_NPE_if_workerUUid_is_null() {
+    assertThatThrownBy(() -> underTest.peek(null, true))
+      .isInstanceOf(NullPointerException.class)
+      .hasMessage("workerUuid can't be null");
+  }
+
+  @Test
+  public void test_remove() {
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
+    Optional<CeTask> peek = underTest.peek(WORKER_UUID_1, true);
+    underTest.remove(peek.get(), CeActivityDto.Status.SUCCESS, null, null);
+
+    // queue is empty
+    assertThat(db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), task.getUuid())).isNotPresent();
+    assertThat(underTest.peek(WORKER_UUID_2, true)).isNotPresent();
+
+    // available in history
+    Optional<CeActivityDto> history = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), task.getUuid());
+    assertThat(history).isPresent();
+    assertThat(history.get().getStatus()).isEqualTo(CeActivityDto.Status.SUCCESS);
+    assertThat(history.get().getIsLast()).isTrue();
+    assertThat(history.get().getAnalysisUuid()).isNull();
+  }
+
+  @Test
+  public void remove_throws_IAE_if_exception_is_provided_but_status_is_SUCCESS() {
+    assertThatThrownBy(() -> underTest.remove(mock(CeTask.class), CeActivityDto.Status.SUCCESS, null, new RuntimeException("Some error")))
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Error can be provided only when status is FAILED");
+  }
+
+  @Test
+  public void remove_throws_IAE_if_exception_is_provided_but_status_is_CANCELED() {
+    assertThatThrownBy(() -> underTest.remove(mock(CeTask.class), CeActivityDto.Status.CANCELED, null, new RuntimeException("Some error")))
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Error can be provided only when status is FAILED");
+  }
+
+  @Test
+  public void remove_does_not_set_analysisUuid_in_CeActivity_when_CeTaskResult_has_no_analysis_uuid() {
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
+    Optional<CeTask> peek = underTest.peek(WORKER_UUID_1, true);
+    underTest.remove(peek.get(), CeActivityDto.Status.SUCCESS, newTaskResult(null), null);
+
+    // available in history
+    Optional<CeActivityDto> history = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), task.getUuid());
+    assertThat(history).isPresent();
+    assertThat(history.get().getAnalysisUuid()).isNull();
+  }
+
+  @Test
+  public void remove_sets_analysisUuid_in_CeActivity_when_CeTaskResult_has_analysis_uuid() {
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
+
+    Optional<CeTask> peek = underTest.peek(WORKER_UUID_2, true);
+    underTest.remove(peek.get(), CeActivityDto.Status.SUCCESS, newTaskResult(AN_ANALYSIS_UUID), null);
+
+    // available in history
+    Optional<CeActivityDto> history = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), task.getUuid());
+    assertThat(history).isPresent();
+    assertThat(history.get().getAnalysisUuid()).isEqualTo("U1");
+  }
+
+  @Test
+  public void remove_sets_nodeName_in_CeActivity_when_nodeInformation_defines_node_name() {
+    when(nodeInformation.getNodeName()).thenReturn(Optional.of(NODE_NAME));
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
+
+    Optional<CeTask> peek = underTest.peek(WORKER_UUID_2, true);
+    underTest.remove(peek.get(), CeActivityDto.Status.SUCCESS, newTaskResult(AN_ANALYSIS_UUID), null);
+
+    Optional<CeActivityDto> history = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), task.getUuid());
+    assertThat(history).isPresent();
+    assertThat(history.get().getNodeName()).isEqualTo(NODE_NAME);
+  }
+
+  @Test
+  public void remove_do_not_set_nodeName_in_CeActivity_when_nodeInformation_does_not_define_node_name() {
+    when(nodeInformation.getNodeName()).thenReturn(Optional.empty());
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
+
+    Optional<CeTask> peek = underTest.peek(WORKER_UUID_2, true);
+    underTest.remove(peek.get(), CeActivityDto.Status.SUCCESS, newTaskResult(AN_ANALYSIS_UUID), null);
+
+    Optional<CeActivityDto> history = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), task.getUuid());
+    assertThat(history).isPresent();
+    assertThat(history.get().getNodeName()).isNull();
+  }
+
+  @Test
+  public void remove_saves_error_message_and_stacktrace_when_exception_is_provided() {
+    Throwable error = new NullPointerException("Fake NPE to test persistence to DB");
+
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
+    Optional<CeTask> peek = underTest.peek(WORKER_UUID_1, true);
+    underTest.remove(peek.get(), CeActivityDto.Status.FAILED, null, error);
+
+    Optional<CeActivityDto> activityDto = db.getDbClient().ceActivityDao().selectByUuid(session, task.getUuid());
+    assertThat(activityDto).isPresent();
+
+    assertThat(activityDto.get().getErrorMessage()).isEqualTo(error.getMessage());
+    assertThat(activityDto.get().getErrorStacktrace()).isEqualToIgnoringWhitespace(stacktraceToString(error));
+    assertThat(activityDto.get().getErrorType()).isNull();
+  }
+
+  @Test
+  public void remove_saves_error_when_TypedMessageException_is_provided() {
+    Throwable error = new TypedExceptionImpl("aType", "aMessage");
+
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
+    Optional<CeTask> peek = underTest.peek(WORKER_UUID_1, true);
+    underTest.remove(peek.get(), CeActivityDto.Status.FAILED, null, error);
+
+    CeActivityDto activityDto = db.getDbClient().ceActivityDao().selectByUuid(session, task.getUuid()).get();
+    assertThat(activityDto.getErrorType()).isEqualTo("aType");
+    assertThat(activityDto.getErrorMessage()).isEqualTo("aMessage");
+    assertThat(activityDto.getErrorStacktrace()).isEqualToIgnoringWhitespace(stacktraceToString(error));
+  }
+
+  @Test
+  public void remove_updates_queueStatus_success_even_if_task_does_not_exist_in_DB() {
+    CEQueueStatus queueStatus = mock(CEQueueStatus.class);
+
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
+    db.getDbClient().ceQueueDao().deleteByUuid(db.getSession(), task.getUuid());
+    db.commit();
+
+    InternalCeQueueImpl underTest = new InternalCeQueueImpl(system2, db.getDbClient(), null, queueStatus, null, null, nodeInformation);
+
+    try {
+      underTest.remove(task, CeActivityDto.Status.SUCCESS, null, null);
+      fail("remove should have thrown a IllegalStateException");
+    } catch (IllegalStateException e) {
+      verify(queueStatus).addSuccess(anyLong());
+    }
+  }
+
+  @Test
+  public void remove_updates_queueStatus_failure_even_if_task_does_not_exist_in_DB() {
+    CEQueueStatus queueStatusMock = mock(CEQueueStatus.class);
+
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
+    db.getDbClient().ceQueueDao().deleteByUuid(db.getSession(), task.getUuid());
+    db.commit();
+    InternalCeQueueImpl underTest = new InternalCeQueueImpl(system2, db.getDbClient(), null, queueStatusMock, null, null, nodeInformation);
+
+    try {
+      underTest.remove(task, CeActivityDto.Status.FAILED, null, null);
+      fail("remove should have thrown a IllegalStateException");
+    } catch (IllegalStateException e) {
+      verify(queueStatusMock).addError(anyLong());
+    }
+  }
+
+  @Test
+  public void cancelWornOuts_does_not_update_queueStatus() {
+
+    CEQueueStatus queueStatusMock = mock(CEQueueStatus.class);
+
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
+    db.executeUpdateSql("update ce_queue set status = 'PENDING', started_at = 123 where uuid = '" + task.getUuid() + "'");
+    db.commit();
+    InternalCeQueueImpl underTest = new InternalCeQueueImpl(system2, db.getDbClient(), null, queueStatusMock, null, null, nodeInformation);
+
+    underTest.cancelWornOuts();
+
+    Optional<CeActivityDto> ceActivityDto = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), task.getUuid());
+    assertThat(ceActivityDto).isPresent();
+    assertThat(ceActivityDto.get().getNodeName()).isEqualTo(NODE_NAME);
+    verifyNoInteractions(queueStatusMock);
+  }
+
+  private static class TypedExceptionImpl extends RuntimeException implements TypedException {
+    private final String type;
+
+    private TypedExceptionImpl(String type, String message) {
+      super(message);
+      this.type = type;
+    }
+
+    @Override
+    public String getType() {
+      return type;
+    }
+  }
+
+  @Test
+  public void remove_copies_workerUuid() {
+    CeQueueDto ceQueueDto = db.getDbClient().ceQueueDao().insert(session, new CeQueueDto()
+      .setUuid("uuid")
+      .setTaskType("foo")
+      .setStatus(CeQueueDto.Status.PENDING));
+    makeInProgress(ceQueueDto, "Dustin");
+    db.commit();
+
+    underTest.remove(new CeTask.Builder()
+      .setUuid("uuid")
+      .setType("bar")
+      .build(), CeActivityDto.Status.SUCCESS, null, null);
+
+    CeActivityDto dto = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), "uuid").get();
+    assertThat(dto.getWorkerUuid()).isEqualTo("Dustin");
+  }
+
+  @Test
+  public void fail_to_remove_if_not_in_queue() {
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
+    underTest.remove(task, CeActivityDto.Status.SUCCESS, null, null);
+
+    assertThatThrownBy(() -> underTest.remove(task, CeActivityDto.Status.SUCCESS, null, null))
+      .isInstanceOf(IllegalStateException.class);
+  }
+
+  @Test
+  public void test_peek() {
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
+
+    Optional<CeTask> peek = underTest.peek(WORKER_UUID_1, true);
+    assertThat(peek).isPresent();
+    assertThat(peek.get().getUuid()).isEqualTo(task.getUuid());
+    assertThat(peek.get().getType()).isEqualTo(CeTaskTypes.REPORT);
+    assertThat(peek.get().getComponent()).contains(new CeTask.Component("PROJECT_1", null, null));
+    assertThat(peek.get().getMainComponent()).contains(peek.get().getComponent().get());
+
+    // no more pending tasks
+    peek = underTest.peek(WORKER_UUID_2, true);
+    assertThat(peek).isEmpty();
+  }
+
+  @Test
+  public void peek_populates_name_and_key_for_existing_component_and_main_component() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto branch = db.components().insertProjectBranch(project);
+    CeTask task = submit(CeTaskTypes.REPORT, branch);
+
+    Optional<CeTask> peek = underTest.peek(WORKER_UUID_1, true);
+    assertThat(peek).isPresent();
+    assertThat(peek.get().getUuid()).isEqualTo(task.getUuid());
+    assertThat(peek.get().getType()).isEqualTo(CeTaskTypes.REPORT);
+    assertThat(peek.get().getComponent()).contains(new CeTask.Component(branch.uuid(), branch.getKey(), branch.name()));
+    assertThat(peek.get().getMainComponent()).contains(new CeTask.Component(project.uuid(), project.getKey(), project.name()));
+
+    // no more pending tasks
+    peek = underTest.peek(WORKER_UUID_2, true);
+    assertThat(peek).isEmpty();
+  }
+
+  @Test
+  public void peek_is_paused_then_resumed() {
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
+    underTest.pauseWorkers();
+
+    Optional<CeTask> peek = underTest.peek(WORKER_UUID_1, true);
+    assertThat(peek).isEmpty();
+
+    underTest.resumeWorkers();
+    peek = underTest.peek(WORKER_UUID_1, true);
+    assertThat(peek).isPresent();
+    assertThat(peek.get().getUuid()).isEqualTo(task.getUuid());
+  }
+
+  @Test
+  public void peek_ignores_in_progress_tasks() {
+    CeQueueDto dto = db.getDbClient().ceQueueDao().insert(session, new CeQueueDto()
+      .setUuid("uuid")
+      .setTaskType("foo")
+      .setStatus(CeQueueDto.Status.PENDING));
+    makeInProgress(dto, "foo");
+    db.commit();
+
+    assertThat(underTest.peek(WORKER_UUID_1, true)).isEmpty();
+  }
+
+  @Test
+  public void peek_nothing_if_application_status_stopping() {
+    submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
+    when(computeEngineStatus.getStatus()).thenReturn(STOPPING);
+
+    Optional<CeTask> peek = underTest.peek(WORKER_UUID_1, true);
+    assertThat(peek).isEmpty();
+  }
+
+  @Test
+  public void peek_peeks_pending_task() {
+    db.getDbClient().ceQueueDao().insert(session, new CeQueueDto()
+      .setUuid("uuid")
+      .setTaskType("foo")
+      .setStatus(CeQueueDto.Status.PENDING));
+    db.commit();
+
+    assertThat(underTest.peek(WORKER_UUID_1, true).get().getUuid()).isEqualTo("uuid");
+  }
+
+  @Test
+  public void peek_resets_to_pending_any_task_in_progress_for_specified_worker_uuid_and_updates_updatedAt() {
+    insertPending("u0"); // add a pending one that will be picked so that u1 isn't peek and status reset is visible in DB
+    CeQueueDto u1 = insertPending("u1");// will be picked-because older than any of the reset ones
+    CeQueueDto u2 = insertInProgress("u2", WORKER_UUID_1);// will be reset
+
+    assertThat(underTest.peek(WORKER_UUID_1, true).get().getUuid()).isEqualTo("u0");
+
+    verifyUnmodifiedTask(u1);
+    verifyResetTask(u2);
+  }
+
+  @Test
+  public void peek_resets_to_pending_any_task_in_progress_for_specified_worker_uuid_and_only_this_uuid() {
+    insertPending("u0"); // add a pending one that will be picked so that u1 isn't peek and status reset is visible in DB
+    CeQueueDto u1 = insertInProgress("u1", WORKER_UUID_1);
+    CeQueueDto u2 = insertInProgress("u2", WORKER_UUID_2);
+    CeQueueDto u3 = insertInProgress("u3", WORKER_UUID_1);
+    CeQueueDto u4 = insertInProgress("u4", WORKER_UUID_2);
+
+    assertThat(underTest.peek(WORKER_UUID_1, true).get().getUuid()).isEqualTo("u0");
+
+    verifyResetTask(u1);
+    verifyUnmodifiedTask(u2);
+    verifyResetTask(u3);
+    verifyUnmodifiedTask(u4);
+  }
+
+  private void verifyResetTask(CeQueueDto originalDto) {
+    CeQueueDto dto = db.getDbClient().ceQueueDao().selectByUuid(session, originalDto.getUuid()).get();
+    assertThat(dto.getStatus()).isEqualTo(CeQueueDto.Status.PENDING);
+    assertThat(dto.getCreatedAt()).isEqualTo(originalDto.getCreatedAt());
+    assertThat(dto.getUpdatedAt()).isGreaterThan(originalDto.getUpdatedAt());
+  }
+
+  private void verifyUnmodifiedTask(CeQueueDto originalDto) {
+    CeQueueDto dto = db.getDbClient().ceQueueDao().selectByUuid(session, originalDto.getUuid()).get();
+    assertThat(dto.getStatus()).isEqualTo(originalDto.getStatus());
+    assertThat(dto.getCreatedAt()).isEqualTo(originalDto.getCreatedAt());
+    assertThat(dto.getUpdatedAt()).isEqualTo(originalDto.getUpdatedAt());
+  }
+
+  private CeQueueDto insertInProgress(String uuid, String workerUuid) {
+    CeQueueDto dto = new CeQueueDto()
+      .setUuid(uuid)
+      .setTaskType("foo")
+      .setStatus(CeQueueDto.Status.PENDING);
+    db.getDbClient().ceQueueDao().insert(session, dto);
+    makeInProgress(dto, workerUuid);
+    db.commit();
+    return db.getDbClient().ceQueueDao().selectByUuid(session, uuid).get();
+  }
+
+  private CeQueueDto insertPending(String uuid) {
+    CeQueueDto dto = new CeQueueDto()
+      .setUuid(uuid)
+      .setTaskType("foo")
+      .setStatus(CeQueueDto.Status.PENDING);
+    db.getDbClient().ceQueueDao().insert(session, dto);
+    db.commit();
+    return dto;
+  }
+
+  @Test
+  public void cancel_pending() {
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
+    CeQueueDto queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), task.getUuid()).get();
+
+    underTest.cancel(db.getSession(), queueDto);
+
+    Optional<CeActivityDto> activity = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), task.getUuid());
+    assertThat(activity).isPresent();
+    assertThat(activity.get().getStatus()).isEqualTo(CeActivityDto.Status.CANCELED);
+  }
+
+  @Test
+  public void fail_to_cancel_if_in_progress() {
+    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
+    underTest.peek(WORKER_UUID_2, true);
+    CeQueueDto queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), task.getUuid()).get();
+
+    assertThatThrownBy(() -> underTest.cancel(db.getSession(), queueDto))
+      .isInstanceOf(IllegalStateException.class)
+      .hasMessageContaining("Task is in progress and can't be canceled");
+  }
+
+  @Test
+  public void cancelAll_pendings_but_not_in_progress() {
+    CeTask inProgressTask = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
+    CeTask pendingTask1 = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_2"));
+    CeTask pendingTask2 = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_3"));
+    underTest.peek(WORKER_UUID_2, true);
+
+    int canceledCount = underTest.cancelAll();
+    assertThat(canceledCount).isEqualTo(2);
+
+    Optional<CeActivityDto> history = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), pendingTask1.getUuid());
+    assertThat(history.get().getStatus()).isEqualTo(CeActivityDto.Status.CANCELED);
+    history = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), pendingTask2.getUuid());
+    assertThat(history.get().getStatus()).isEqualTo(CeActivityDto.Status.CANCELED);
+    history = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), inProgressTask.getUuid());
+    assertThat(history).isEmpty();
+  }
+
+  @Test
+  public void resetTasksWithUnknownWorkerUUIDs_reset_only_in_progress_tasks() {
+    CeQueueDto u1 = insertCeQueueDto("u1");
+    CeQueueDto u2 = insertCeQueueDto("u2");
+    CeQueueDto u6 = insertInProgress("u6", "worker1");
+    CeQueueDto u7 = insertInProgress("u7", "worker2");
+    CeQueueDto u8 = insertInProgress("u8", "worker3");
+
+    underTest.resetTasksWithUnknownWorkerUUIDs(ImmutableSet.of("worker2", "worker3"));
+
+    // Pending tasks must not be modified even if a workerUUID is not present
+    verifyUnmodified(u1);
+    verifyUnmodified(u2);
+
+    // Unknown worker : null, "worker1"
+    verifyReset(u6);
+
+    // Known workers : "worker2", "worker3"
+    verifyUnmodified(u7);
+    verifyUnmodified(u8);
+  }
+
+  @Test
+  public void resetTasksWithUnknownWorkerUUIDs_with_empty_set_will_reset_all_in_progress_tasks() {
+    CeQueueDto u1 = insertCeQueueDto("u1");
+    CeQueueDto u2 = insertCeQueueDto("u2");
+    CeQueueDto u6 = insertInProgress("u6", "worker1");
+    CeQueueDto u7 = insertInProgress("u7", "worker2");
+    CeQueueDto u8 = insertInProgress("u8", "worker3");
+
+    underTest.resetTasksWithUnknownWorkerUUIDs(ImmutableSet.of());
+
+    // Pending tasks must not be modified even if a workerUUID is not present
+    verifyUnmodified(u1);
+    verifyUnmodified(u2);
+
+    // Unknown worker : null, "worker1"
+    verifyReset(u6);
+    verifyReset(u7);
+    verifyReset(u8);
+  }
+
+  @Test
+  public void resetTasksWithUnknownWorkerUUIDs_with_worker_without_tasks_will_reset_all_in_progress_tasks() {
+    CeQueueDto u1 = insertCeQueueDto("u1");
+    CeQueueDto u2 = insertCeQueueDto("u2");
+    CeQueueDto u6 = insertInProgress("u6", "worker1");
+    CeQueueDto u7 = insertInProgress("u7", "worker2");
+    CeQueueDto u8 = insertInProgress("u8", "worker3");
+
+    underTest.resetTasksWithUnknownWorkerUUIDs(ImmutableSet.of("worker1000", "worker1001"));
+
+    // Pending tasks must not be modified even if a workerUUID is not present
+    verifyUnmodified(u1);
+    verifyUnmodified(u2);
+
+    // Unknown worker : null, "worker1"
+    verifyReset(u6);
+    verifyReset(u7);
+    verifyReset(u8);
+  }
+
+  private void verifyReset(CeQueueDto original) {
+    CeQueueDto dto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), original.getUuid()).get();
+    // We do not touch CreatedAt
+    assertThat(dto.getCreatedAt()).isEqualTo(original.getCreatedAt());
+
+    // Status must have changed to PENDING and must not be equal to previous status
+    assertThat(dto.getStatus()).isEqualTo(CeQueueDto.Status.PENDING).isNotEqualTo(original.getStatus());
+    // UpdatedAt must have been updated
+    assertThat(dto.getUpdatedAt()).isNotEqualTo(original.getUpdatedAt());
+    assertThat(dto.getStartedAt()).isEqualTo(original.getStartedAt());
+    // WorkerUuid must be null
+    assertThat(dto.getWorkerUuid()).isNull();
+  }
+
+  private void verifyUnmodified(CeQueueDto original) {
+    CeQueueDto dto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), original.getUuid()).get();
+    assertThat(dto.getStatus()).isEqualTo(original.getStatus());
+    assertThat(dto.getCreatedAt()).isEqualTo(original.getCreatedAt());
+    assertThat(dto.getUpdatedAt()).isEqualTo(original.getUpdatedAt());
+  }
+
+  private CeQueueDto insertCeQueueDto(String uuid) {
+    CeQueueDto dto = new CeQueueDto()
+      .setUuid(uuid)
+      .setTaskType("foo")
+      .setStatus(CeQueueDto.Status.PENDING);
+    db.getDbClient().ceQueueDao().insert(db.getSession(), dto);
+    db.commit();
+    return dto;
+  }
+
+  private void verifyCeTask(CeTaskSubmit taskSubmit, CeTask task, @Nullable ComponentDto componentDto, @Nullable UserDto userDto) {
+    assertThat(task.getUuid()).isEqualTo(taskSubmit.getUuid());
+    assertThat(task.getType()).isEqualTo(taskSubmit.getType());
+    if (componentDto != null) {
+      CeTask.Component component = task.getComponent().get();
+      assertThat(component.getUuid()).isEqualTo(componentDto.uuid());
+      assertThat(component.getKey()).contains(componentDto.getKey());
+      assertThat(component.getName()).contains(componentDto.name());
+    } else if (taskSubmit.getComponent().isPresent()) {
+      assertThat(task.getComponent()).contains(new CeTask.Component(taskSubmit.getComponent().get().getUuid(), null, null));
+    } else {
+      assertThat(task.getComponent()).isEmpty();
+    }
+    if (taskSubmit.getSubmitterUuid() != null) {
+      if (userDto == null) {
+        assertThat(task.getSubmitter().uuid()).isEqualTo(taskSubmit.getSubmitterUuid());
+        assertThat(task.getSubmitter().login()).isNull();
+      } else {
+        assertThat(task.getSubmitter().uuid()).isEqualTo(userDto.getUuid()).isEqualTo(taskSubmit.getSubmitterUuid());
+        assertThat(task.getSubmitter().uuid()).isEqualTo(userDto.getLogin());
+      }
+    }
+  }
+
+  private void verifyCeQueueDtoForTaskSubmit(CeTaskSubmit taskSubmit) {
+    Optional<CeQueueDto> queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), taskSubmit.getUuid());
+    assertThat(queueDto).isPresent();
+    CeQueueDto dto = queueDto.get();
+    assertThat(dto.getTaskType()).isEqualTo(taskSubmit.getType());
+    Optional<CeTaskSubmit.Component> component = taskSubmit.getComponent();
+    if (component.isPresent()) {
+      assertThat(dto.getMainComponentUuid()).isEqualTo(component.get().getMainComponentUuid());
+      assertThat(dto.getComponentUuid()).isEqualTo(component.get().getUuid());
+    } else {
+      assertThat(dto.getMainComponentUuid()).isNull();
+      assertThat(dto.getComponentUuid()).isNull();
+    }
+    assertThat(dto.getSubmitterUuid()).isEqualTo(taskSubmit.getSubmitterUuid());
+    assertThat(dto.getCreatedAt()).isEqualTo(dto.getUpdatedAt());
+  }
+
+  private ComponentDto newProjectDto(String uuid) {
+    return ComponentTesting.newPublicProjectDto(uuid).setName("name_" + uuid).setKey("key_" + uuid);
+  }
+
+  private CeTask submit(String reportType, ComponentDto componentDto) {
+    return underTest.submit(createTaskSubmit(reportType, componentDto, null));
+  }
+
+  private CeTaskSubmit createTaskSubmit(String type) {
+    return createTaskSubmit(type, null, null);
+  }
+
+  private CeTaskSubmit createTaskSubmit(String type, @Nullable ComponentDto componentDto, @Nullable String submitterUuid) {
+    CeTaskSubmit.Builder builder = underTest.prepareSubmit()
+      .setType(type)
+      .setSubmitterUuid(submitterUuid)
+      .setCharacteristics(emptyMap());
+    if (componentDto != null) {
+      builder.setComponent(CeTaskSubmit.Component.fromDto(componentDto));
+    }
+    return builder.build();
+  }
+
+  private CeTaskResult newTaskResult(@Nullable String analysisUuid) {
+    CeTaskResult taskResult = mock(CeTaskResult.class);
+    when(taskResult.getAnalysisUuid()).thenReturn(java.util.Optional.ofNullable(analysisUuid));
+    return taskResult;
+  }
+
+  private ComponentDto insertComponent(ComponentDto componentDto) {
+    return db.components().insertComponent(componentDto);
+  }
+
+  private CeQueueDto makeInProgress(CeQueueDto ceQueueDto, String workerUuid) {
+    CeQueueTesting.makeInProgress(session, workerUuid, system2.now(), ceQueueDto);
+    return db.getDbClient().ceQueueDao().selectByUuid(session, ceQueueDto.getUuid()).get();
+  }
+
+  private static String stacktraceToString(Throwable error) {
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    error.printStackTrace(new PrintStream(out));
+    return out.toString();
+  }
+}
diff --git a/server/sonar-ce/src/it/java/org/sonar/ce/queue/NextPendingTaskPickerIT.java b/server/sonar-ce/src/it/java/org/sonar/ce/queue/NextPendingTaskPickerIT.java
new file mode 100644 (file)
index 0000000..282ade6
--- /dev/null
@@ -0,0 +1,384 @@
+/*
+ * 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.queue;
+
+import java.util.Optional;
+import java.util.UUID;
+import java.util.function.Consumer;
+import javax.annotation.Nullable;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.core.config.ComputeEngineProperties;
+import org.sonar.db.DbTester;
+import org.sonar.db.ce.CeQueueDto;
+import org.sonar.db.ce.CeTaskCharacteristicDto;
+import org.sonar.db.ce.CeTaskTypes;
+import org.sonar.db.component.ComponentDto;
+
+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.ce.CeQueueDto.Status.IN_PROGRESS;
+import static org.sonar.db.ce.CeQueueDto.Status.PENDING;
+import static org.sonar.db.ce.CeTaskCharacteristicDto.BRANCH_KEY;
+import static org.sonar.db.ce.CeTaskCharacteristicDto.PULL_REQUEST;
+
+public class NextPendingTaskPickerIT {
+
+  private final System2 alwaysIncreasingSystem2 = new AlwaysIncreasingSystem2(1L, 1);
+
+  private final Configuration config = mock(Configuration.class);
+
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  private NextPendingTaskPicker underTest;
+
+  @Rule
+  public DbTester db = DbTester.create(alwaysIncreasingSystem2);
+
+  @Before
+  public void before() {
+    underTest = new NextPendingTaskPicker(config, db.getDbClient());
+    when(config.getBoolean(ComputeEngineProperties.CE_PARALLEL_PROJECT_TASKS_ENABLED)).thenReturn(Optional.of(true));
+  }
+
+  @Test
+  public void findPendingTask_whenNoTasksPending_returnsEmpty() {
+    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
+
+    assertThat(ceQueueDto).isEmpty();
+  }
+
+  @Test
+  public void findPendingTask_whenTwoTasksPending_returnsTheOlderOne() {
+    // both the 'eligibleTask' and 'parallelEligibleTask' will point to this one
+    insertPending("1");
+    insertPending("2");
+    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
+
+    assertThat(ceQueueDto).isPresent();
+    assertThat(ceQueueDto.get().getUuid()).isEqualTo("1");
+  }
+
+  @Test
+  public void findPendingTask_whenTwoTasksPendingWithSameCreationDate_returnsLowestUuid() {
+    insertPending("d", c -> c.setCreatedAt(1L).setUpdatedAt(1L));
+    insertPending("c", c -> c.setCreatedAt(1L).setUpdatedAt(1L));
+    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
+
+    assertThat(ceQueueDto).isPresent();
+    assertThat(ceQueueDto.get().getUuid()).isEqualTo("c");
+  }
+
+  @Test
+  public void findPendingTask_givenBranchInProgressAndPropertySet_returnQueuedPR() {
+    insertInProgress("1");
+    insertPendingPullRequest("2");
+    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
+
+    assertThat(ceQueueDto).isPresent();
+    assertThat(ceQueueDto.get().getUuid()).isEqualTo("2");
+
+    assertThat(logTester.logs()).contains("Task [uuid = " + ceQueueDto.get().getUuid() + "] will be run concurrently with other tasks for the same project");
+  }
+
+  @Test
+  public void findPendingTask_givenBranchInProgressAndPropertyNotSet_dontReturnQueuedPR() {
+    when(config.getBoolean(ComputeEngineProperties.CE_PARALLEL_PROJECT_TASKS_ENABLED)).thenReturn(Optional.of(false));
+    insertInProgress("1");
+    insertPendingPullRequest("2");
+    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
+
+    assertThat(ceQueueDto).isEmpty();
+  }
+
+  @Test
+  public void findPendingTask_given2PRsQueued_returnBothQueuedPR() {
+    insertPendingPullRequest("1");
+    insertPendingPullRequest("2");
+    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
+    Optional<CeQueueDto> ceQueueDto2 = underTest.findPendingTask("workerUuid2", db.getSession(), true);
+
+    assertThat(ceQueueDto).isPresent();
+    assertThat(ceQueueDto2).isPresent();
+    assertThat(ceQueueDto.get().getUuid()).isEqualTo("1");
+    assertThat(ceQueueDto.get().getStatus()).isEqualTo(IN_PROGRESS);
+    assertThat(ceQueueDto2.get().getStatus()).isEqualTo(IN_PROGRESS);
+  }
+
+  @Test
+  public void findPendingTask_given1MainBranch_2PRsQueued_returnMainBranchAndPRs() {
+    insertPending("1");
+    insertPendingPullRequest("2");
+    insertPendingPullRequest("3");
+    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
+    Optional<CeQueueDto> ceQueueDto2 = underTest.findPendingTask("workerUuid2", db.getSession(), true);
+    Optional<CeQueueDto> ceQueueDto3 = underTest.findPendingTask("workerUuid3", db.getSession(), true);
+
+    assertThat(ceQueueDto).isPresent();
+    assertThat(ceQueueDto2).isPresent();
+    assertThat(ceQueueDto3).isPresent();
+    assertThat(ceQueueDto.get().getUuid()).isEqualTo("1");
+    assertThat(ceQueueDto.get().getStatus()).isEqualTo(IN_PROGRESS);
+    assertThat(ceQueueDto2.get().getUuid()).isEqualTo("2");
+    assertThat(ceQueueDto2.get().getStatus()).isEqualTo(IN_PROGRESS);
+    assertThat(ceQueueDto3.get().getUuid()).isEqualTo("3");
+    assertThat(ceQueueDto3.get().getStatus()).isEqualTo(IN_PROGRESS);
+  }
+
+  @Test
+  public void findPendingTask_given1MainBranch_2BranchesQueued_returnOnyMainBranch() {
+    insertPending("1", null);
+    insertPendingBranch("2");
+    insertPendingBranch("3");
+    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
+    Optional<CeQueueDto> ceQueueDto2 = underTest.findPendingTask("workerUuid2", db.getSession(), true);
+
+    assertThat(ceQueueDto).isPresent();
+    assertThat(ceQueueDto2).isEmpty();
+    assertThat(ceQueueDto.get().getUuid()).isEqualTo("1");
+    assertThat(ceQueueDto.get().getStatus()).isEqualTo(IN_PROGRESS);
+  }
+
+  @Test
+  public void findPendingTask_given2BranchesQueued_returnOnlyFirstQueuedBranch() {
+    insertPending("1");
+    insertPendingBranch("2");
+    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
+    Optional<CeQueueDto> ceQueueDto2 = underTest.findPendingTask("workerUuid2", db.getSession(), true);
+
+    assertThat(ceQueueDto).isPresent();
+    assertThat(ceQueueDto2).isEmpty();
+    assertThat(ceQueueDto.get().getStatus()).isEqualTo(IN_PROGRESS);
+  }
+
+  @Test
+  public void findPendingTask_given2SamePRsQueued_returnOnlyFirstQueuedPR() {
+    insertPendingPullRequest("1", c -> c.setComponentUuid("pr1"));
+    insertPendingPullRequest("2", c -> c.setComponentUuid("pr1"));
+    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
+    Optional<CeQueueDto> ceQueueDto2 = underTest.findPendingTask("workerUuid2", db.getSession(), true);
+
+    assertThat(ceQueueDto).isPresent();
+    assertThat(ceQueueDto2).isEmpty();
+    assertThat(ceQueueDto.get().getStatus()).isEqualTo(IN_PROGRESS);
+  }
+
+  @Test
+  public void findPendingTask_givenBranchInTheQueueOlderThanPrInTheQueue_dontJumpAheadOfBranch() {
+    // we have branch task in progress. Next branch task needs to wait for this one to finish. We dont allow PRs to jump ahead of this branch
+    insertInProgress("1");
+    insertPending("2");
+    insertPendingPullRequest("3");
+
+    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
+
+    assertThat(ceQueueDto).isEmpty();
+  }
+
+  @Test
+  public void findPendingTask_givenDifferentProjectAndPrInTheQueue_dontJumpAheadOfDifferentProject() {
+    // we have branch task in progress.
+    insertInProgress("1");
+    // The PR can run in parallel, but needs to wait for this other project to finish. We dont allow PRs to jump ahead
+    insertPending("2", c -> c.setMainComponentUuid("different project"));
+    insertPendingPullRequest("3");
+
+    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
+
+    assertThat(ceQueueDto).isPresent();
+    assertThat(ceQueueDto.get().getUuid()).isEqualTo("2");
+  }
+
+  @Test
+  public void findPendingTask_givenDifferentProjectAndPrInTheQueue_prCanRunFirst() {
+    // we have branch task in progress.
+    insertInProgress("1");
+    // The PR can run in parallel and is ahead of the other project
+    insertPendingPullRequest("2");
+    insertPending("3", c -> c.setMainComponentUuid("different project"));
+
+    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
+
+    assertThat(ceQueueDto).isPresent();
+    assertThat(ceQueueDto.get().getUuid()).isEqualTo("2");
+  }
+
+  @Test
+  public void findPendingTask_givenFivePrsInProgress_branchCanBeScheduled() {
+    insertInProgressPullRequest("1");
+    insertInProgressPullRequest("2");
+    insertInProgressPullRequest("3");
+    insertInProgressPullRequest("4");
+    insertInProgressPullRequest("5");
+    insertPending("6");
+
+    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
+
+    assertThat(ceQueueDto).isPresent();
+    assertThat(ceQueueDto.get().getUuid()).isEqualTo("6");
+  }
+
+  @Test
+  public void findPendingTask_excludingViewPickUpOrphanBranches() {
+    insertPending("1", dto -> dto
+      .setComponentUuid("1")
+      .setMainComponentUuid("non-existing-uuid")
+      .setStatus(PENDING)
+      .setTaskType(CeTaskTypes.BRANCH_ISSUE_SYNC)
+      .setCreatedAt(100_000L));
+
+    Optional<CeQueueDto> peek = underTest.findPendingTask("1", db.getSession(), false);
+    assertThat(peek).isPresent();
+    assertThat(peek.get().getUuid()).isEqualTo("1");
+  }
+
+  @Test
+  public void exclude_portfolios_computation_when_indexing_issues() {
+    String taskUuid1 = "1", taskUuid2 = "2";
+    String mainComponentUuid = "1";
+    insertBranch(mainComponentUuid);
+    insertPending(taskUuid1, dto -> dto
+      .setComponentUuid(mainComponentUuid)
+      .setMainComponentUuid(mainComponentUuid)
+      .setStatus(PENDING)
+      .setTaskType(CeTaskTypes.BRANCH_ISSUE_SYNC)
+      .setCreatedAt(100_000L));
+
+    String view_uuid = "view_uuid";
+    insertView(view_uuid);
+    insertPending(taskUuid2, dto -> dto
+      .setComponentUuid(view_uuid)
+      .setMainComponentUuid(view_uuid)
+      .setStatus(PENDING)
+      .setTaskType(CeTaskTypes.REPORT)
+      .setCreatedAt(100_000L));
+
+    Optional<CeQueueDto> peek = underTest.findPendingTask("1", db.getSession(), false);
+    assertThat(peek).isPresent();
+    assertThat(peek.get().getUuid()).isEqualTo(taskUuid1);
+
+    Optional<CeQueueDto> peek2 = underTest.findPendingTask("1", db.getSession(), false);
+    assertThat(peek2).isPresent();
+    assertThat(peek2.get().getUuid()).isEqualTo(taskUuid2);
+  }
+
+  private CeQueueDto insertPending(String uuid) {
+    return insertPending(uuid, null);
+  }
+
+  private CeQueueDto insertPendingBranch(String uuid) {
+    CeQueueDto queue = insertPending(uuid, null);
+    insertCharacteristics(queue.getUuid(), BRANCH_KEY);
+    return queue;
+  }
+
+  private CeQueueDto insertPendingPullRequest(String uuid) {
+    return insertPendingPullRequest(uuid, null);
+  }
+
+  private CeQueueDto insertPendingPullRequest(String uuid, @Nullable Consumer<CeQueueDto> ceQueueDto) {
+    CeQueueDto queue = insertPending(uuid, ceQueueDto);
+    insertCharacteristics(queue.getUuid(), PULL_REQUEST);
+    return queue;
+  }
+
+  private CeQueueDto insertInProgressPullRequest(String uuid) {
+    CeQueueDto queue = insertInProgress(uuid, null);
+    insertCharacteristics(queue.getUuid(), PULL_REQUEST);
+    return queue;
+  }
+
+  private CeQueueDto insertInProgress(String uuid) {
+    return insertInProgress(uuid, null);
+  }
+
+  private CeQueueDto insertInProgress(String uuid, @Nullable Consumer<CeQueueDto> ceQueueDto) {
+    return insertTask(uuid, IN_PROGRESS, ceQueueDto);
+  }
+
+  private CeQueueDto insertPending(String uuid, @Nullable Consumer<CeQueueDto> ceQueueDto) {
+    return insertTask(uuid, PENDING, ceQueueDto);
+  }
+
+  private CeTaskCharacteristicDto insertCharacteristics(String taskUuid, String branchType) {
+    var ctcDto = new CeTaskCharacteristicDto();
+    ctcDto.setUuid(UUID.randomUUID().toString());
+    ctcDto.setTaskUuid(taskUuid);
+    ctcDto.setKey(branchType);
+    ctcDto.setValue("value");
+    db.getDbClient().ceTaskCharacteristicsDao().insert(db.getSession(), ctcDto);
+    db.getSession().commit();
+    return ctcDto;
+  }
+
+  private CeQueueDto insertTask(String uuid, CeQueueDto.Status status, @Nullable Consumer<CeQueueDto> ceQueueDtoConsumer) {
+    CeQueueDto dto = createCeQueue(uuid, status, ceQueueDtoConsumer);
+    db.getDbClient().ceQueueDao().insert(db.getSession(), dto);
+    db.getSession().commit();
+    return dto;
+  }
+
+  @NotNull
+  private static CeQueueDto createCeQueue(String uuid, CeQueueDto.Status status, @Nullable Consumer<CeQueueDto> ceQueueDtoConsumer) {
+    CeQueueDto dto = new CeQueueDto();
+    dto.setUuid(uuid);
+    dto.setTaskType(CeTaskTypes.REPORT);
+    dto.setStatus(status);
+    dto.setSubmitterUuid("henri");
+    dto.setComponentUuid(UUID.randomUUID().toString());
+    dto.setMainComponentUuid("1");
+    if (ceQueueDtoConsumer != null) {
+      ceQueueDtoConsumer.accept(dto);
+    }
+    return dto;
+  }
+
+  private void insertView(String view_uuid) {
+    ComponentDto view = new ComponentDto();
+    view.setQualifier("VW");
+    view.setKey(view_uuid + "_key");
+    view.setUuid(view_uuid);
+    view.setPrivate(false);
+    view.setUuidPath("uuid_path");
+    view.setBranchUuid(view_uuid);
+    db.components().insertPortfolioAndSnapshot(view);
+    db.commit();
+  }
+
+  private void insertBranch(String uuid) {
+    ComponentDto branch = new ComponentDto();
+    branch.setQualifier("TRK");
+    branch.setKey(uuid + "_key");
+    branch.setUuid(uuid);
+    branch.setPrivate(false);
+    branch.setUuidPath("uuid_path");
+    branch.setBranchUuid(uuid);
+    db.components().insertComponent(branch);
+    db.commit();
+  }
+}
diff --git a/server/sonar-ce/src/it/java/org/sonar/ce/taskprocessor/CeWorkerImplIT.java b/server/sonar-ce/src/it/java/org/sonar/ce/taskprocessor/CeWorkerImplIT.java
new file mode 100644 (file)
index 0000000..f0acbec
--- /dev/null
@@ -0,0 +1,788 @@
+/*
+ * 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.taskprocessor;
+
+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.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.stubbing.Answer;
+import org.sonar.api.impl.utils.TestSystem2;
+import org.sonar.api.utils.MessageException;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.log.LogAndArguments;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.ce.queue.InternalCeQueue;
+import org.sonar.ce.task.CeTask;
+import org.sonar.ce.task.CeTaskResult;
+import org.sonar.ce.task.projectanalysis.taskprocessor.ReportTaskProcessor;
+import org.sonar.ce.task.taskprocessor.CeTaskProcessor;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.ce.CeActivityDto;
+import org.sonar.db.ce.CeTaskTypes;
+import org.sonar.db.user.UserDto;
+import org.sonar.db.user.UserTesting;
+
+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.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.ce.taskprocessor.CeWorker.Result.DISABLED;
+import static org.sonar.ce.taskprocessor.CeWorker.Result.NO_TASK;
+import static org.sonar.ce.taskprocessor.CeWorker.Result.TASK_PROCESSED;
+
+public class CeWorkerImplIT {
+
+  private System2 system2 = new TestSystem2().setNow(1_450_000_000_000L);
+
+  @Rule
+  public CeTaskProcessorRepositoryRule taskProcessorRepository = new CeTaskProcessorRepositoryRule();
+  @Rule
+  public LogTester logTester = new LogTester();
+  @Rule
+  public DbTester db = DbTester.create(system2);
+
+  private DbSession session = db.getSession();
+
+  private InternalCeQueue queue = mock(InternalCeQueue.class);
+  private ReportTaskProcessor taskProcessor = mock(ReportTaskProcessor.class);
+  private CeWorker.ExecutionListener executionListener1 = mock(CeWorker.ExecutionListener.class);
+  private CeWorker.ExecutionListener executionListener2 = mock(CeWorker.ExecutionListener.class);
+  private CeWorkerController ceWorkerController = mock(CeWorkerController.class);
+  private ArgumentCaptor<String> workerUuidCaptor = ArgumentCaptor.forClass(String.class);
+  private int randomOrdinal = new Random().nextInt(50);
+  private String workerUuid = UUID.randomUUID().toString();
+  private CeWorker underTest = new CeWorkerImpl(randomOrdinal, workerUuid, queue, taskProcessorRepository, ceWorkerController,
+    executionListener1, executionListener2);
+  private CeWorker underTestNoListener = new CeWorkerImpl(randomOrdinal, workerUuid, queue, taskProcessorRepository, ceWorkerController);
+  private InOrder inOrder = inOrder(taskProcessor, queue, executionListener1, executionListener2);
+  private final CeTask.User submitter = new CeTask.User("UUID_USER_1", "LOGIN_1");
+
+  @Before
+  public void setUp() {
+    when(ceWorkerController.isEnabled(any(CeWorker.class))).thenReturn(true);
+  }
+
+  @Test
+  public void constructor_throws_IAE_if_ordinal_is_less_than_zero() {
+    assertThatThrownBy(() -> new CeWorkerImpl(-1 - new Random().nextInt(20), workerUuid, queue, taskProcessorRepository, ceWorkerController))
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Ordinal must be >= 0");
+  }
+
+  @Test
+  public void getUUID_must_return_the_uuid_of_constructor() {
+    String uuid = UUID.randomUUID().toString();
+    CeWorker underTest = new CeWorkerImpl(randomOrdinal, uuid, queue, taskProcessorRepository, ceWorkerController);
+    assertThat(underTest.getUUID()).isEqualTo(uuid);
+  }
+
+  @Test
+  public void worker_disabled() throws Exception {
+    reset(ceWorkerController);
+    when(ceWorkerController.isEnabled(underTest)).thenReturn(false);
+
+    assertThat(underTest.call()).isEqualTo(DISABLED);
+
+    verifyNoInteractions(taskProcessor, executionListener1, executionListener2);
+  }
+
+  @Test
+  public void worker_disabled_no_listener() throws Exception {
+    reset(ceWorkerController);
+    when(ceWorkerController.isEnabled(underTest)).thenReturn(false);
+
+    assertThat(underTestNoListener.call()).isEqualTo(DISABLED);
+
+    verifyNoInteractions(taskProcessor, executionListener1, executionListener2);
+  }
+
+  @Test
+  public void no_pending_tasks_in_queue() throws Exception {
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.empty());
+
+    assertThat(underTest.call()).isEqualTo(NO_TASK);
+
+    verifyNoInteractions(taskProcessor, executionListener1, executionListener2);
+  }
+
+  @Test
+  public void no_pending_tasks_in_queue_without_listener() throws Exception {
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.empty());
+
+    assertThat(underTestNoListener.call()).isEqualTo(NO_TASK);
+
+    verifyNoInteractions(taskProcessor, executionListener1, executionListener2);
+  }
+
+  @Test
+  public void fail_when_no_CeTaskProcessor_is_found_in_repository() throws Exception {
+    CeTask task = createCeTask(null);
+    taskProcessorRepository.setNoProcessorForTask(CeTaskTypes.REPORT);
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(task));
+
+    assertThat(underTest.call()).isEqualTo(TASK_PROCESSED);
+
+    verifyWorkerUuid();
+    inOrder.verify(executionListener1).onStart(task);
+    inOrder.verify(executionListener2).onStart(task);
+    inOrder.verify(queue).remove(task, CeActivityDto.Status.FAILED, null, null);
+    inOrder.verify(executionListener1).onEnd(eq(task), eq(CeActivityDto.Status.FAILED), any(), isNull(), isNull());
+    inOrder.verify(executionListener2).onEnd(eq(task), eq(CeActivityDto.Status.FAILED), any(), isNull(), isNull());
+  }
+
+  @Test
+  public void fail_when_no_CeTaskProcessor_is_found_in_repository_without_listener() throws Exception {
+    CeTask task = createCeTask(null);
+    taskProcessorRepository.setNoProcessorForTask(CeTaskTypes.REPORT);
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(task));
+
+    assertThat(underTestNoListener.call()).isEqualTo(TASK_PROCESSED);
+
+    verifyWorkerUuid();
+    inOrder.verify(queue).remove(task, CeActivityDto.Status.FAILED, null, null);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void peek_and_process_task() throws Exception {
+    CeTask task = createCeTask(null);
+    taskProcessorRepository.setProcessorForTask(task.getType(), taskProcessor);
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(task));
+
+    assertThat(underTest.call()).isEqualTo(TASK_PROCESSED);
+
+    verifyWorkerUuid();
+    inOrder.verify(executionListener1).onStart(task);
+    inOrder.verify(executionListener2).onStart(task);
+    inOrder.verify(taskProcessor).process(task);
+    inOrder.verify(queue).remove(task, CeActivityDto.Status.SUCCESS, null, null);
+    inOrder.verify(executionListener1).onEnd(eq(task), eq(CeActivityDto.Status.SUCCESS), any(), isNull(), isNull());
+    inOrder.verify(executionListener2).onEnd(eq(task), eq(CeActivityDto.Status.SUCCESS), any(), isNull(), isNull());
+  }
+
+  @Test
+  public void peek_and_process_task_without_listeners() throws Exception {
+    CeTask task = createCeTask(null);
+    taskProcessorRepository.setProcessorForTask(task.getType(), taskProcessor);
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(task));
+
+    assertThat(underTestNoListener.call()).isEqualTo(TASK_PROCESSED);
+
+    verifyWorkerUuid();
+    inOrder.verify(taskProcessor).process(task);
+    inOrder.verify(queue).remove(task, CeActivityDto.Status.SUCCESS, null, null);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void fail_to_process_task() throws Exception {
+    CeTask task = createCeTask(null);
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(task));
+    taskProcessorRepository.setProcessorForTask(task.getType(), taskProcessor);
+    Throwable error = makeTaskProcessorFail(task);
+
+    assertThat(underTest.call()).isEqualTo(TASK_PROCESSED);
+
+    verifyWorkerUuid();
+    inOrder.verify(executionListener1).onStart(task);
+    inOrder.verify(executionListener2).onStart(task);
+    inOrder.verify(taskProcessor).process(task);
+    inOrder.verify(queue).remove(task, CeActivityDto.Status.FAILED, null, error);
+    inOrder.verify(executionListener1).onEnd(eq(task), eq(CeActivityDto.Status.FAILED), any(), isNull(), eq(error));
+    inOrder.verify(executionListener2).onEnd(eq(task), eq(CeActivityDto.Status.FAILED), any(), isNull(), eq(error));
+  }
+
+  @Test
+  public void fail_to_process_task_without_listeners() throws Exception {
+    CeTask task = createCeTask(null);
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(task));
+    taskProcessorRepository.setProcessorForTask(task.getType(), taskProcessor);
+    Throwable error = makeTaskProcessorFail(task);
+
+    assertThat(underTestNoListener.call()).isEqualTo(TASK_PROCESSED);
+
+    verifyWorkerUuid();
+    inOrder.verify(taskProcessor).process(task);
+    inOrder.verify(queue).remove(task, CeActivityDto.Status.FAILED, null, error);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void log_task_characteristics() throws Exception {
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(createCeTask(null, "pullRequest", "123", "branch", "foo")));
+    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
+
+    underTest.call();
+
+    List<String> logs = logTester.logs(LoggerLevel.INFO);
+    assertThat(logs).hasSize(2);
+    for (int i = 0; i < 2; i++) {
+      assertThat(logs.get(i)).contains("pullRequest=123");
+      assertThat(logs.get(i)).contains("branch=foo");
+    }
+  }
+
+  @Test
+  public void do_not_log_submitter_param_if_anonymous_and_success() throws Exception {
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(createCeTask(null)));
+    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
+
+    underTest.call();
+
+    verifyWorkerUuid();
+    List<String> logs = logTester.logs(LoggerLevel.INFO);
+    assertThat(logs).hasSize(2);
+    for (int i = 0; i < 2; i++) {
+      assertThat(logs.get(i)).doesNotContain("submitter=");
+    }
+  }
+
+  @Test
+  public void do_not_log_submitter_param_if_anonymous_and_error() throws Exception {
+    CeTask ceTask = createCeTask(null);
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(ceTask));
+    taskProcessorRepository.setProcessorForTask(ceTask.getType(), taskProcessor);
+    makeTaskProcessorFail(ceTask);
+
+    underTest.call();
+
+    verifyWorkerUuid();
+    List<String> logs = logTester.logs(LoggerLevel.INFO);
+    assertThat(logs).hasSize(2);
+    assertThat(logs.get(0)).doesNotContain("submitter=");
+    assertThat(logs.get(1)).doesNotContain("submitter=");
+    logs = logTester.logs(LoggerLevel.ERROR);
+    assertThat(logs).hasSize(1);
+    assertThat(logs.iterator().next()).doesNotContain("submitter=");
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).isEmpty();
+  }
+
+  @Test
+  public void log_submitter_login_if_authenticated_and_success() throws Exception {
+    UserDto userDto = insertRandomUser();
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(createCeTask(toTaskSubmitter(userDto))));
+    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
+
+    underTest.call();
+
+    verifyWorkerUuid();
+    List<String> logs = logTester.logs(LoggerLevel.INFO);
+    assertThat(logs).hasSize(2);
+    assertThat(logs.get(0)).contains(String.format("submitter=%s", userDto.getLogin()));
+    assertThat(logs.get(1)).contains(String.format("submitter=%s | status=SUCCESS | time=", userDto.getLogin()));
+    assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).isEmpty();
+  }
+
+  @Test
+  public void log_submitterUuid_if_user_matching_submitterUuid_can_not_be_found() throws Exception {
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(createCeTask(new CeTask.User("UUID_USER", null))));
+    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
+
+    underTest.call();
+
+    verifyWorkerUuid();
+    List<String> logs = logTester.logs(LoggerLevel.INFO);
+    assertThat(logs).hasSize(2);
+    assertThat(logs.get(0)).contains("submitter=UUID_USER");
+    assertThat(logs.get(1)).contains("submitter=UUID_USER | status=SUCCESS | time=");
+    assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).isEmpty();
+  }
+
+  @Test
+  public void display_submitterLogin_in_logs_when_set_in_case_of_error() throws Exception {
+    UserDto userDto = insertRandomUser();
+    CeTask ceTask = createCeTask(toTaskSubmitter(userDto));
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(ceTask));
+    taskProcessorRepository.setProcessorForTask(ceTask.getType(), taskProcessor);
+    makeTaskProcessorFail(ceTask);
+
+    underTest.call();
+
+    verifyWorkerUuid();
+    List<String> logs = logTester.logs(LoggerLevel.INFO);
+    assertThat(logs).hasSize(2);
+    assertThat(logs.get(0)).contains(String.format("submitter=%s", userDto.getLogin()));
+    assertThat(logs.get(1)).contains(String.format("submitter=%s | status=FAILED | time=", userDto.getLogin()));
+    logs = logTester.logs(LoggerLevel.ERROR);
+    assertThat(logs).hasSize(1);
+    assertThat(logs.get(0)).isEqualTo("Failed to execute task " + ceTask.getUuid());
+  }
+
+  @Test
+  public void display_start_stop_at_debug_level_for_console_if_DEBUG_is_enabled_and_task_successful() throws Exception {
+    logTester.setLevel(LoggerLevel.DEBUG);
+
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(createCeTask(submitter)));
+    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
+
+    underTest.call();
+
+    verifyWorkerUuid();
+    List<String> logs = logTester.logs(LoggerLevel.INFO);
+    assertThat(logs).hasSize(2);
+    assertThat(logs.get(0)).contains(" | submitter=" + submitter.login());
+    assertThat(logs.get(1)).contains(String.format(" | submitter=%s | status=SUCCESS | time=", submitter.login()));
+    assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).isEmpty();
+  }
+
+  @Test
+  public void display_start_at_debug_level_stop_at_error_level_for_console_if_DEBUG_is_enabled_and_task_failed() throws Exception {
+    logTester.setLevel(LoggerLevel.DEBUG);
+
+    CeTask ceTask = createCeTask(submitter);
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(ceTask));
+    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
+    makeTaskProcessorFail(ceTask);
+
+    underTest.call();
+
+    verifyWorkerUuid();
+    List<String> logs = logTester.logs(LoggerLevel.INFO);
+    assertThat(logs).hasSize(2);
+    assertThat(logs.get(0)).contains(" | submitter=" + submitter.login());
+    assertThat(logs.get(1)).contains(String.format(" | submitter=%s | status=FAILED | time=", submitter.login()));
+    logs = logTester.logs(LoggerLevel.ERROR);
+    assertThat(logs).hasSize(1);
+    assertThat(logs.iterator().next()).isEqualTo("Failed to execute task " + ceTask.getUuid());
+    assertThat(logTester.logs(LoggerLevel.DEBUG)).isEmpty();
+  }
+
+  @Test
+  public void call_sets_and_restores_thread_name_with_information_of_worker_when_there_is_no_task_to_process() throws Exception {
+    String threadName = randomAlphabetic(3);
+    when(queue.peek(anyString(), anyBoolean())).thenAnswer(invocation -> {
+      assertThat(Thread.currentThread().getName())
+        .isEqualTo("Worker " + randomOrdinal + " (UUID=" + workerUuid + ") on " + threadName);
+      return Optional.empty();
+    });
+    Thread newThread = createThreadNameVerifyingThread(threadName);
+
+    newThread.start();
+    newThread.join();
+  }
+
+  @Test
+  public void call_sets_and_restores_thread_name_with_information_of_worker_when_a_task_is_processed() throws Exception {
+    String threadName = randomAlphabetic(3);
+    when(queue.peek(anyString(), anyBoolean())).thenAnswer(invocation -> {
+      assertThat(Thread.currentThread().getName())
+        .isEqualTo("Worker " + randomOrdinal + " (UUID=" + workerUuid + ") on " + threadName);
+      return Optional.of(createCeTask(submitter));
+    });
+    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
+    Thread newThread = createThreadNameVerifyingThread(threadName);
+
+    newThread.start();
+    newThread.join();
+  }
+
+  @Test
+  public void call_sets_and_restores_thread_name_with_information_of_worker_when_an_error_occurs() throws Exception {
+    String threadName = randomAlphabetic(3);
+    CeTask ceTask = createCeTask(submitter);
+    when(queue.peek(anyString(), anyBoolean())).thenAnswer(invocation -> {
+      assertThat(Thread.currentThread().getName())
+        .isEqualTo("Worker " + randomOrdinal + " (UUID=" + workerUuid + ") on " + threadName);
+      return Optional.of(ceTask);
+    });
+    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
+    makeTaskProcessorFail(ceTask);
+    Thread newThread = createThreadNameVerifyingThread(threadName);
+
+    newThread.start();
+    newThread.join();
+  }
+
+  @Test
+  public void call_sets_and_restores_thread_name_with_information_of_worker_when_worker_is_disabled() throws Exception {
+    reset(ceWorkerController);
+    when(ceWorkerController.isEnabled(underTest)).thenReturn(false);
+
+    String threadName = randomAlphabetic(3);
+    Thread newThread = createThreadNameVerifyingThread(threadName);
+
+    newThread.start();
+    newThread.join();
+  }
+
+  @Test
+  public void log_error_when_task_fails_with_not_MessageException() throws Exception {
+    CeTask ceTask = createCeTask(submitter);
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(ceTask));
+    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
+    makeTaskProcessorFail(ceTask);
+
+    underTest.call();
+
+    List<String> logs = logTester.logs(LoggerLevel.INFO);
+    assertThat(logs).hasSize(2);
+    assertThat(logs.get(0)).contains(" | submitter=" + submitter.login());
+    assertThat(logs.get(1)).contains(String.format(" | submitter=%s | status=FAILED | time=", submitter.login()));
+    logs = logTester.logs(LoggerLevel.ERROR);
+    assertThat(logs).hasSize(1);
+    assertThat(logs.iterator().next()).isEqualTo("Failed to execute task " + ceTask.getUuid());
+  }
+
+  @Test
+  public void do_no_log_error_when_task_fails_with_MessageException() throws Exception {
+    CeTask ceTask = createCeTask(submitter);
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(ceTask));
+    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
+    makeTaskProcessorFail(ceTask, MessageException.of("simulate MessageException thrown by TaskProcessor#process"));
+
+    underTest.call();
+
+    List<String> logs = logTester.logs(LoggerLevel.INFO);
+    assertThat(logs).hasSize(2);
+    assertThat(logs.get(1)).contains(" | submitter=" + submitter.login());
+    assertThat(logs.get(1)).contains(String.format(" | submitter=%s | status=FAILED | time=", submitter.login()));
+    assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
+  }
+
+  @Test
+  public void log_error_when_task_was_successful_but_ending_state_can_not_be_persisted_to_db() throws Exception {
+    CeTask ceTask = createCeTask(submitter);
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(ceTask));
+    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
+    doThrow(new RuntimeException("Simulate queue#remove failing")).when(queue).remove(ceTask, CeActivityDto.Status.SUCCESS, null, null);
+
+    underTest.call();
+
+    assertThat(logTester.logs(LoggerLevel.ERROR)).containsOnly("Failed to finalize task with uuid '" + ceTask.getUuid() + "' and persist its state to db");
+  }
+
+  @Test
+  public void log_error_when_task_failed_and_ending_state_can_not_be_persisted_to_db() throws Exception {
+    CeTask ceTask = createCeTask(submitter);
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(ceTask));
+    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
+    IllegalStateException ex = makeTaskProcessorFail(ceTask);
+    RuntimeException runtimeException = new RuntimeException("Simulate queue#remove failing");
+    doThrow(runtimeException).when(queue).remove(ceTask, CeActivityDto.Status.FAILED, null, ex);
+
+    underTest.call();
+
+    List<String> logs = logTester.logs(LoggerLevel.INFO);
+    assertThat(logs).hasSize(2);
+    assertThat(logs.get(0)).contains(" | submitter=" + submitter.login());
+    assertThat(logs.get(1)).contains(String.format(" | submitter=%s | status=FAILED | time=", submitter.login()));
+    List<LogAndArguments> logAndArguments = logTester.getLogs(LoggerLevel.ERROR);
+    assertThat(logAndArguments).hasSize(2);
+
+    LogAndArguments executionErrorLog = logAndArguments.get(0);
+    assertThat(executionErrorLog.getFormattedMsg()).isEqualTo("Failed to execute task " + ceTask.getUuid());
+    assertThat(executionErrorLog.getArgs().get()).containsOnly(ceTask.getUuid(), ex);
+
+    LogAndArguments finalizingErrorLog = logAndArguments.get(1);
+    assertThat(finalizingErrorLog.getFormattedMsg()).isEqualTo("Failed to finalize task with uuid '" + ceTask.getUuid() + "' and persist its state to db");
+    Object arg1 = finalizingErrorLog.getArgs().get()[0];
+    assertThat(arg1).isSameAs(runtimeException);
+    assertThat(((Exception) arg1).getSuppressed()).containsOnly(ex);
+  }
+
+  @Test
+  public void log_error_as_suppressed_when_task_failed_with_MessageException_and_ending_state_can_not_be_persisted_to_db() throws Exception {
+    CeTask ceTask = createCeTask(submitter);
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(ceTask));
+    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
+    MessageException ex = makeTaskProcessorFail(ceTask, MessageException.of("simulate MessageException thrown by TaskProcessor#process"));
+    RuntimeException runtimeException = new RuntimeException("Simulate queue#remove failing");
+    doThrow(runtimeException).when(queue).remove(ceTask, CeActivityDto.Status.FAILED, null, ex);
+
+    underTest.call();
+
+    List<String> logs = logTester.logs(LoggerLevel.INFO);
+    assertThat(logs).hasSize(2);
+    assertThat(logs.get(0)).contains(" | submitter=" + submitter.login());
+    assertThat(logs.get(1)).contains(String.format(" | submitter=%s | status=FAILED | time=", submitter.login()));
+    List<LogAndArguments> logAndArguments = logTester.getLogs(LoggerLevel.ERROR);
+    assertThat(logAndArguments).hasSize(1);
+    assertThat(logAndArguments.get(0).getFormattedMsg()).isEqualTo("Failed to finalize task with uuid '" + ceTask.getUuid() + "' and persist its state to db");
+    Object arg1 = logAndArguments.get(0).getArgs().get()[0];
+    assertThat(arg1).isSameAs(runtimeException);
+    assertThat(((Exception) arg1).getSuppressed()).containsOnly(ex);
+  }
+
+  @Test
+  public void isExecutedBy_returns_false_when_no_interaction_with_instance() {
+    assertThat(underTest.isExecutedBy(Thread.currentThread())).isFalse();
+    assertThat(underTest.isExecutedBy(new Thread())).isFalse();
+  }
+
+  @Test
+  public void isExecutedBy_returns_false_unless_a_thread_is_currently_calling_call() throws InterruptedException {
+    CountDownLatch inCallLatch = new CountDownLatch(1);
+    CountDownLatch assertionsDoneLatch = new CountDownLatch(1);
+    // mock long running peek(String) call => Thread is executing call() but not running a task
+    when(queue.peek(anyString(), anyBoolean())).thenAnswer((Answer<Optional<CeTask>>) invocation -> {
+      inCallLatch.countDown();
+      try {
+        assertionsDoneLatch.await(10, TimeUnit.SECONDS);
+      } catch (InterruptedException e) {
+        throw new RuntimeException(e);
+      }
+      return Optional.empty();
+    });
+    Thread t = callCallInNewThread(underTest);
+
+    try {
+      t.start();
+
+      inCallLatch.await(10, TimeUnit.SECONDS);
+      assertThat(underTest.isExecutedBy(Thread.currentThread())).isFalse();
+      assertThat(underTest.isExecutedBy(new Thread())).isFalse();
+      assertThat(underTest.isExecutedBy(t)).isTrue();
+    } finally {
+      assertionsDoneLatch.countDown();
+      t.join();
+    }
+
+    assertThat(underTest.isExecutedBy(Thread.currentThread())).isFalse();
+    assertThat(underTest.isExecutedBy(new Thread())).isFalse();
+    assertThat(underTest.isExecutedBy(t)).isFalse();
+  }
+
+  @Test
+  public void isExecutedBy_returns_false_unless_a_thread_is_currently_executing_a_task() throws InterruptedException {
+    CountDownLatch inCallLatch = new CountDownLatch(1);
+    CountDownLatch assertionsDoneLatch = new CountDownLatch(1);
+    String taskType = randomAlphabetic(12);
+    CeTask ceTask = mock(CeTask.class);
+    when(ceTask.getType()).thenReturn(taskType);
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(ceTask));
+    taskProcessorRepository.setProcessorForTask(taskType, new SimpleCeTaskProcessor() {
+      @CheckForNull
+      @Override
+      public CeTaskResult process(CeTask task) {
+        inCallLatch.countDown();
+        try {
+          assertionsDoneLatch.await(10, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+          throw new RuntimeException(e);
+        }
+        return null;
+      }
+    });
+    Thread t = callCallInNewThread(underTest);
+
+    try {
+      t.start();
+
+      inCallLatch.await(10, TimeUnit.SECONDS);
+      assertThat(underTest.isExecutedBy(Thread.currentThread())).isFalse();
+      assertThat(underTest.isExecutedBy(new Thread())).isFalse();
+      assertThat(underTest.isExecutedBy(t)).isTrue();
+    } finally {
+      assertionsDoneLatch.countDown();
+      t.join();
+    }
+
+    assertThat(underTest.isExecutedBy(Thread.currentThread())).isFalse();
+    assertThat(underTest.isExecutedBy(new Thread())).isFalse();
+    assertThat(underTest.isExecutedBy(t)).isFalse();
+  }
+
+  @Test
+  public void getCurrentTask_returns_empty_when_no_interaction_with_instance() {
+    assertThat(underTest.getCurrentTask()).isEmpty();
+  }
+
+  @Test
+  public void do_not_exclude_portfolio_when_indexation_task_lookup_is_disabled() throws Exception {
+    // first call with empty queue to disable indexationTaskLookupEnabled
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.empty());
+    assertThat(underTest.call()).isEqualTo(NO_TASK);
+
+    // following calls should not exclude portfolios
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(createCeTask(submitter)));
+    assertThat(underTest.call()).isEqualTo(TASK_PROCESSED);
+    verify(queue, times(2)).peek(anyString(), anyBoolean());
+  }
+
+  @Test
+  public void getCurrentTask_returns_empty_when_a_thread_is_currently_calling_call_but_not_executing_a_task() throws InterruptedException {
+    CountDownLatch inCallLatch = new CountDownLatch(1);
+    CountDownLatch assertionsDoneLatch = new CountDownLatch(1);
+    // mock long running peek(String) call => Thread is executing call() but not running a task
+    when(queue.peek(anyString(), anyBoolean())).thenAnswer((Answer<Optional<CeTask>>) invocation -> {
+      inCallLatch.countDown();
+      try {
+        assertionsDoneLatch.await(10, TimeUnit.SECONDS);
+      } catch (InterruptedException e) {
+        throw new RuntimeException(e);
+      }
+      return Optional.empty();
+    });
+    Thread t = callCallInNewThread(underTest);
+
+    try {
+      t.start();
+
+      inCallLatch.await(10, TimeUnit.SECONDS);
+      assertThat(underTest.getCurrentTask()).isEmpty();
+    } finally {
+      assertionsDoneLatch.countDown();
+      t.join();
+    }
+
+    assertThat(underTest.getCurrentTask()).isEmpty();
+  }
+
+  @Test
+  public void getCurrentTask_returns_empty_unless_a_thread_is_currently_executing_a_task() throws InterruptedException {
+    CountDownLatch inCallLatch = new CountDownLatch(1);
+    CountDownLatch assertionsDoneLatch = new CountDownLatch(1);
+    String taskType = randomAlphabetic(12);
+    CeTask ceTask = mock(CeTask.class);
+    when(ceTask.getType()).thenReturn(taskType);
+    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(ceTask));
+    taskProcessorRepository.setProcessorForTask(taskType, new SimpleCeTaskProcessor() {
+
+      @CheckForNull
+      @Override
+      public CeTaskResult process(CeTask task) {
+        inCallLatch.countDown();
+        try {
+          assertionsDoneLatch.await(10, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+          throw new RuntimeException(e);
+        }
+        return null;
+      }
+    });
+    Thread t = callCallInNewThread(underTest);
+
+    try {
+      t.start();
+
+      inCallLatch.await(10, TimeUnit.SECONDS);
+      assertThat(underTest.getCurrentTask()).contains(ceTask);
+    } finally {
+      assertionsDoneLatch.countDown();
+      t.join();
+    }
+
+    assertThat(underTest.getCurrentTask()).isEmpty();
+  }
+
+  private Thread callCallInNewThread(CeWorker underTest) {
+    return new Thread(() -> {
+      try {
+        underTest.call();
+      } catch (Exception e) {
+        throw new RuntimeException("call to call() failed and this is unexpected. Fix the UT.", e);
+      }
+    });
+  }
+
+  private Thread createThreadNameVerifyingThread(String threadName) {
+    return new Thread(() -> {
+      verifyUnchangedThreadName(threadName);
+      try {
+        underTest.call();
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+      verifyUnchangedThreadName(threadName);
+    }, threadName);
+  }
+
+  private void verifyUnchangedThreadName(String threadName) {
+    assertThat(Thread.currentThread().getName()).isEqualTo(threadName);
+  }
+
+  private void verifyWorkerUuid() {
+    verify(queue, atLeastOnce()).peek(workerUuidCaptor.capture(), anyBoolean());
+    assertThat(workerUuidCaptor.getValue()).isEqualTo(workerUuid);
+  }
+
+  private static CeTask createCeTask(@Nullable CeTask.User submitter, String... characteristics) {
+    Map<String, String> characteristicMap = new HashMap<>();
+    for (int i = 0; i < characteristics.length; i += 2) {
+      characteristicMap.put(characteristics[i], characteristics[i + 1]);
+    }
+    CeTask.Component component = new CeTask.Component("PROJECT_1", null, null);
+    return new CeTask.Builder()
+      .setUuid("TASK_1").setType(CeTaskTypes.REPORT)
+      .setComponent(component)
+      .setMainComponent(component)
+      .setSubmitter(submitter)
+      .setCharacteristics(characteristicMap)
+      .build();
+  }
+
+  private UserDto insertRandomUser() {
+    UserDto userDto = UserTesting.newUserDto();
+    db.getDbClient().userDao().insert(session, userDto);
+    session.commit();
+    return userDto;
+  }
+
+  private CeTask.User toTaskSubmitter(UserDto userDto) {
+    return new CeTask.User(userDto.getUuid(), userDto.getLogin());
+  }
+
+  private IllegalStateException makeTaskProcessorFail(CeTask task) {
+    return makeTaskProcessorFail(task, new IllegalStateException("simulate exception thrown by TaskProcessor#process"));
+  }
+
+  private <T extends Throwable> T makeTaskProcessorFail(CeTask task, T t) {
+    doThrow(t).when(taskProcessor).process(task);
+    return t;
+  }
+
+  private static abstract class SimpleCeTaskProcessor implements CeTaskProcessor {
+    @Override
+    public Set<String> getHandledCeTaskTypes() {
+      throw new UnsupportedOperationException("getHandledCeTaskTypes should not be called");
+    }
+  }
+}
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/analysis/cache/cleaning/AnalysisCacheCleaningSchedulerImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/analysis/cache/cleaning/AnalysisCacheCleaningSchedulerImplTest.java
deleted file mode 100644 (file)
index 468d94f..0000000
+++ /dev/null
@@ -1,105 +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.analysis.cache.cleaning;
-
-import java.io.ByteArrayInputStream;
-import java.time.LocalDateTime;
-import java.time.ZoneOffset;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.platform.Server;
-import org.sonar.api.utils.System2;
-import org.sonar.core.util.SequenceUuidFactory;
-import org.sonar.core.util.UuidFactory;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.SnapshotDto;
-import org.sonar.db.scannercache.ScannerAnalysisCacheDao;
-
-import static java.util.concurrent.TimeUnit.DAYS;
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class AnalysisCacheCleaningSchedulerImplTest {
-  private System2 system2 = mock(System2.class);
-  private final static UuidFactory uuidFactory = new SequenceUuidFactory();
-  @Rule
-  public DbTester dbTester = DbTester.create(system2);
-  private DbSession dbSession = dbTester.getSession();
-  private ScannerAnalysisCacheDao scannerAnalysisCacheDao = dbTester.getDbClient().scannerAnalysisCacheDao();
-
-  AnalysisCacheCleaningExecutorService executorService = mock(AnalysisCacheCleaningExecutorService.class);
-
-  AnalysisCacheCleaningSchedulerImpl underTest = new AnalysisCacheCleaningSchedulerImpl(executorService, dbTester.getDbClient());
-
-  @Test
-  public void startSchedulingOnServerStart() {
-    underTest.onServerStart(mock(Server.class));
-    verify(executorService, times(1)).scheduleAtFixedRate(any(Runnable.class), anyLong(), eq(DAYS.toSeconds(1)), eq(SECONDS));
-  }
-
-  @Test
-  public void clean_data_older_than_7_days() {
-    var snapshotDao = dbTester.getDbClient().snapshotDao();
-    var snapshot1 = createSnapshot(LocalDateTime.now().minusDays(1).toInstant(ZoneOffset.UTC).toEpochMilli());
-    snapshotDao.insert(dbSession, snapshot1);
-    scannerAnalysisCacheDao.insert(dbSession, snapshot1.getComponentUuid(), new ByteArrayInputStream("data".getBytes()));
-    var snapshot2 = createSnapshot(LocalDateTime.now().minusDays(6).toInstant(ZoneOffset.UTC).toEpochMilli());
-    snapshotDao.insert(dbSession, snapshot2);
-    scannerAnalysisCacheDao.insert(dbSession, snapshot2.getComponentUuid(), new ByteArrayInputStream("data".getBytes()));
-    var snapshot3 = createSnapshot(LocalDateTime.now().minusDays(8).toInstant(ZoneOffset.UTC).toEpochMilli());
-    snapshotDao.insert(dbSession, snapshot3);
-    scannerAnalysisCacheDao.insert(dbSession, snapshot3.getComponentUuid(), new ByteArrayInputStream("data".getBytes()));
-    var snapshot4 = createSnapshot(LocalDateTime.now().minusDays(30).toInstant(ZoneOffset.UTC).toEpochMilli());
-    snapshotDao.insert(dbSession, snapshot4);
-    scannerAnalysisCacheDao.insert(dbSession, snapshot4.getComponentUuid(), new ByteArrayInputStream("data".getBytes()));
-
-    assertThat(dbTester.countRowsOfTable("scanner_analysis_cache")).isEqualTo(4);
-
-    underTest.clean();
-
-    assertThat(dbTester.countRowsOfTable("scanner_analysis_cache")).isEqualTo(2);
-    assertThat(scannerAnalysisCacheDao.selectData(dbSession, snapshot1.getComponentUuid())).isNotNull();
-    assertThat(scannerAnalysisCacheDao.selectData(dbSession, snapshot2.getComponentUuid())).isNotNull();
-    assertThat(scannerAnalysisCacheDao.selectData(dbSession, snapshot3.getComponentUuid())).isNull();
-    assertThat(scannerAnalysisCacheDao.selectData(dbSession, snapshot4.getComponentUuid())).isNull();
-  }
-
-  private static SnapshotDto createSnapshot(long buildtime) {
-    return new SnapshotDto()
-      .setUuid(uuidFactory.create())
-      .setComponentUuid(uuidFactory.create())
-      .setStatus("P")
-      .setLast(true)
-      .setProjectVersion("2.1-SNAPSHOT")
-      .setPeriodMode("days1")
-      .setPeriodParam("30")
-      .setPeriodDate(buildtime)
-      .setBuildDate(buildtime);
-  }
-
-}
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
deleted file mode 100644 (file)
index 4da1608..0000000
+++ /dev/null
@@ -1,142 +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.container;
-
-import com.google.common.collect.ImmutableSet;
-import java.io.File;
-import java.io.IOException;
-import java.util.Date;
-import java.util.Locale;
-import java.util.Properties;
-import org.apache.commons.codec.digest.DigestUtils;
-import org.apache.commons.lang.StringUtils;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.utils.DateUtils;
-import org.sonar.api.utils.System2;
-import org.sonar.core.extension.ServiceLoaderWrapper;
-import org.sonar.db.DbTester;
-import org.sonar.db.property.PropertyDto;
-import org.sonar.process.ProcessId;
-import org.sonar.process.ProcessProperties;
-import org.sonar.process.Props;
-import org.sonar.server.property.InternalProperties;
-import org.springframework.context.annotation.AnnotationConfigApplicationContext;
-
-import static java.lang.String.valueOf;
-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.ProcessEntryPoint.PROPERTY_PROCESS_INDEX;
-import static org.sonar.process.ProcessEntryPoint.PROPERTY_SHARED_PATH;
-import static org.sonar.process.ProcessProperties.Property.JDBC_PASSWORD;
-import static org.sonar.process.ProcessProperties.Property.JDBC_URL;
-import static org.sonar.process.ProcessProperties.Property.JDBC_USERNAME;
-import static org.sonar.process.ProcessProperties.Property.PATH_DATA;
-import static org.sonar.process.ProcessProperties.Property.PATH_HOME;
-import static org.sonar.process.ProcessProperties.Property.PATH_TEMP;
-
-public class ComputeEngineContainerImplTest {
-
-  @Rule
-  public TemporaryFolder tempFolder = new TemporaryFolder();
-  @Rule
-
-  public DbTester db = DbTester.create(System2.INSTANCE);
-
-  private final ServiceLoaderWrapper serviceLoaderWrapper = mock(ServiceLoaderWrapper.class);
-  private final ProcessProperties processProperties = new ProcessProperties(serviceLoaderWrapper);
-  private final ComputeEngineContainerImpl underTest = new ComputeEngineContainerImpl();
-
-  @Before
-  public void setUp() {
-    when(serviceLoaderWrapper.load()).thenReturn(ImmutableSet.of());
-    underTest.setComputeEngineStatus(mock(ComputeEngineStatus.class));
-  }
-
-  @Test
-  public void constructor_does_not_create_container() {
-    assertThat(underTest.getComponentContainer()).isNull();
-  }
-
-  @Test
-  public void test_real_start() throws IOException {
-    Properties properties = getProperties();
-
-    // required persisted properties
-    insertProperty(CoreProperties.SERVER_ID, "a_server_id");
-    insertProperty(CoreProperties.SERVER_STARTTIME, DateUtils.formatDateTime(new Date()));
-    insertInternalProperty(InternalProperties.SERVER_ID_CHECKSUM, DigestUtils.sha256Hex("a_server_id|" + cleanJdbcUrl()));
-
-    underTest.start(new Props(properties));
-
-    AnnotationConfigApplicationContext context = underTest.getComponentContainer().context();
-    try {
-      assertThat(context.getBeanDefinitionNames()).hasSizeGreaterThan(1);
-      assertThat(context.getParent().getBeanDefinitionNames()).hasSizeGreaterThan(1);
-      assertThat(context.getParent().getParent().getBeanDefinitionNames()).hasSizeGreaterThan(1);
-      assertThat(context.getParent().getParent().getParent().getBeanDefinitionNames()).hasSizeGreaterThan(1);
-      assertThat(context.getParent().getParent().getParent().getParent()).isNull();
-    } finally {
-      underTest.stop();
-    }
-
-    assertThat(context.isActive()).isFalse();
-  }
-
-  private String cleanJdbcUrl() {
-    return StringUtils.lowerCase(StringUtils.substringBefore(db.getUrl(), "?"), Locale.ENGLISH);
-  }
-
-  private Properties getProperties() throws IOException {
-    Properties properties = new Properties();
-    Props props = new Props(properties);
-    processProperties.completeDefaults(props);
-    properties = props.rawProperties();
-    File homeDir = tempFolder.newFolder();
-    File dataDir = new File(homeDir, "data");
-    dataDir.mkdirs();
-    File tmpDir = new File(homeDir, "tmp");
-    tmpDir.mkdirs();
-    properties.setProperty(PATH_HOME.getKey(), homeDir.getAbsolutePath());
-    properties.setProperty(PATH_DATA.getKey(), dataDir.getAbsolutePath());
-    properties.setProperty(PATH_TEMP.getKey(), tmpDir.getAbsolutePath());
-    properties.setProperty(PROPERTY_PROCESS_INDEX, valueOf(ProcessId.COMPUTE_ENGINE.getIpcIndex()));
-    properties.setProperty(PROPERTY_SHARED_PATH, tmpDir.getAbsolutePath());
-    properties.setProperty(JDBC_URL.getKey(), db.getUrl());
-    properties.setProperty(JDBC_USERNAME.getKey(), "sonar");
-    properties.setProperty(JDBC_PASSWORD.getKey(), "sonar");
-    return properties;
-  }
-
-  private void insertProperty(String key, String value) {
-    PropertyDto dto = new PropertyDto().setKey(key).setValue(value);
-    db.getDbClient().propertiesDao().saveProperty(db.getSession(), dto, null, null, null, null);
-    db.commit();
-  }
-
-  private void insertInternalProperty(String key, String value) {
-    db.getDbClient().internalPropertiesDao().save(db.getSession(), key, value);
-    db.commit();
-  }
-}
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/monitoring/CeDatabaseMBeanImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/monitoring/CeDatabaseMBeanImplTest.java
deleted file mode 100644 (file)
index ae4db99..0000000
+++ /dev/null
@@ -1,78 +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.monitoring;
-
-import java.lang.management.ManagementFactory;
-import javax.annotation.CheckForNull;
-import javax.management.InstanceNotFoundException;
-import javax.management.ObjectInstance;
-import javax.management.ObjectName;
-import org.junit.BeforeClass;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.utils.System2;
-import org.sonar.db.DbTester;
-import org.sonar.process.Jmx;
-import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class CeDatabaseMBeanImplTest {
-
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private final CeDatabaseMBeanImpl underTest = new CeDatabaseMBeanImpl(dbTester.getDbClient());
-
-  @BeforeClass
-  public static void beforeClass() {
-    // if any other class starts a container where CeDatabaseMBeanImpl is added, it will have been registered
-    Jmx.unregister("SonarQube:name=ComputeEngineDatabaseConnection");
-  }
-
-  @Test
-  public void register_and_unregister() throws Exception {
-    assertThat(getMBean()).isNull();
-
-    underTest.start();
-    assertThat(getMBean()).isNotNull();
-
-    underTest.stop();
-    assertThat(getMBean()).isNull();
-  }
-
-  @Test
-  public void export_system_info() {
-    ProtobufSystemInfo.Section section = underTest.toProtobuf();
-    assertThat(section.getName()).isEqualTo("Compute Engine Database Connection");
-    assertThat(section.getAttributesCount()).isEqualTo(7);
-    assertThat(section.getAttributes(0).getKey()).isEqualTo("Pool Total Connections");
-    assertThat(section.getAttributes(0).getLongValue()).isPositive();
-  }
-
-  @CheckForNull
-  private ObjectInstance getMBean() throws Exception {
-    try {
-      return ManagementFactory.getPlatformMBeanServer().getObjectInstance(new ObjectName("SonarQube:name=ComputeEngineDatabaseConnection"));
-    } catch (InstanceNotFoundException e) {
-      return null;
-    }
-  }
-}
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationExecutionListenerTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationExecutionListenerTest.java
deleted file mode 100644 (file)
index 9d2100f..0000000
+++ /dev/null
@@ -1,337 +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.notification;
-
-import java.time.Duration;
-import java.time.temporal.ChronoUnit;
-import java.util.Arrays;
-import java.util.Optional;
-import java.util.Random;
-import javax.annotation.Nullable;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.sonar.api.notifications.Notification;
-import org.sonar.api.resources.Qualifiers;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.task.CeTask;
-import org.sonar.ce.task.CeTaskResult;
-import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotification;
-import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotificationBuilder;
-import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotificationSerializer;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbTester;
-import org.sonar.db.RowNotFoundException;
-import org.sonar.db.ce.CeActivityDto;
-import org.sonar.db.ce.CeQueueDto;
-import org.sonar.db.ce.CeTaskTypes;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.project.ProjectDto;
-import org.sonar.server.notification.NotificationService;
-
-import static java.util.Collections.singleton;
-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.ArgumentMatchers.same;
-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.db.component.ComponentTesting.newDirectory;
-
-public class ReportAnalysisFailureNotificationExecutionListenerTest {
-  @Rule
-  public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
-  private final Random random = new Random();
-  private final DbClient dbClient = dbTester.getDbClient();
-  private final NotificationService notificationService = mock(NotificationService.class);
-  private final ReportAnalysisFailureNotificationSerializer serializer = mock(ReportAnalysisFailureNotificationSerializer.class);
-  private final System2 system2 = mock(System2.class);
-  private final DbClient dbClientMock = mock(DbClient.class);
-  private final CeTask ceTaskMock = mock(CeTask.class);
-  private final Throwable throwableMock = mock(Throwable.class);
-  private final CeTaskResult ceTaskResultMock = mock(CeTaskResult.class);
-  private final ReportAnalysisFailureNotificationExecutionListener fullMockedUnderTest = new ReportAnalysisFailureNotificationExecutionListener(
-    notificationService, dbClientMock, serializer, system2);
-  private final ReportAnalysisFailureNotificationExecutionListener underTest = new ReportAnalysisFailureNotificationExecutionListener(
-    notificationService, dbClient, serializer, system2);
-
-  @Test
-  public void onStart_has_no_effect() {
-    CeTask mockedCeTask = mock(CeTask.class);
-
-    fullMockedUnderTest.onStart(mockedCeTask);
-
-    verifyNoInteractions(mockedCeTask, notificationService, dbClientMock, serializer, system2);
-  }
-
-  @Test
-  public void onEnd_has_no_effect_if_status_is_SUCCESS() {
-    fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.SUCCESS, randomDuration(), ceTaskResultMock, throwableMock);
-
-    verifyNoInteractions(ceTaskMock, ceTaskResultMock, throwableMock, notificationService, dbClientMock, serializer, system2);
-  }
-
-  @Test
-  public void onEnd_has_no_effect_if_CeTask_type_is_not_report() {
-    when(ceTaskMock.getType()).thenReturn(randomAlphanumeric(12));
-
-    fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
-
-    verifyNoInteractions(ceTaskResultMock, throwableMock, notificationService, dbClientMock, serializer, system2);
-  }
-
-  @Test
-  public void onEnd_has_no_effect_if_CeTask_has_no_component_uuid() {
-    when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
-
-    fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
-
-    verifyNoInteractions(ceTaskResultMock, throwableMock, notificationService, dbClientMock, serializer, system2);
-  }
-
-  @Test
-  public void onEnd_has_no_effect_if_there_is_no_subscriber_for_ReportAnalysisFailureNotification_type() {
-    String componentUuid = randomAlphanumeric(6);
-    when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
-    when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
-    when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class)))
-      .thenReturn(false);
-
-    fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
-
-    verifyNoInteractions(ceTaskResultMock, throwableMock, dbClientMock, serializer, system2);
-  }
-
-  @Test
-  public void onEnd_fails_with_ISE_if_project_does_not_exist_in_DB() {
-    String componentUuid = randomAlphanumeric(6);
-    when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
-    when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
-    when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class)))
-      .thenReturn(true);
-
-    Duration randomDuration = randomDuration();
-    assertThatThrownBy(() -> underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Could not find project uuid " + componentUuid);
-  }
-
-  @Test
-  public void onEnd_fails_with_ISE_if_branch_does_not_exist_in_DB() {
-    String componentUuid = randomAlphanumeric(6);
-    ProjectDto project = new ProjectDto().setUuid(componentUuid).setKey(randomAlphanumeric(5)).setQualifier(Qualifiers.PROJECT);
-    dbTester.getDbClient().projectDao().insert(dbTester.getSession(), project);
-    dbTester.getSession().commit();
-    when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
-    when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
-    when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class)))
-      .thenReturn(true);
-
-    Duration randomDuration = randomDuration();
-    assertThatThrownBy(() -> underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessage("Could not find a branch for project uuid " + componentUuid);
-  }
-
-  @Test
-  public void onEnd_fails_with_IAE_if_component_is_not_a_project() {
-    when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
-    ComponentDto project = dbTester.components().insertPrivateProject();
-    ComponentDto directory = dbTester.components().insertComponent(newDirectory(project, randomAlphanumeric(12)));
-    ComponentDto file = dbTester.components().insertComponent(ComponentTesting.newFileDto(project));
-    ComponentDto view = dbTester.components().insertComponent(ComponentTesting.newPortfolio());
-    ComponentDto subView = dbTester.components().insertComponent(ComponentTesting.newSubPortfolio(view));
-    ComponentDto projectCopy = dbTester.components().insertComponent(ComponentTesting.newProjectCopy(project, subView));
-    ComponentDto application = dbTester.components().insertComponent(ComponentTesting.newApplication());
-
-    Arrays.asList(directory, file, view, subView, projectCopy, application)
-      .forEach(component -> {
-
-      when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(component.uuid(), null, null)));
-      when(notificationService.hasProjectSubscribersForTypes(component.uuid(), singleton(ReportAnalysisFailureNotification.class)))
-        .thenReturn(true);
-
-        Duration randomDuration = randomDuration();
-        try {
-          underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock);
-
-          fail("An IllegalArgumentException should have been thrown for component " + component);
-        } catch (IllegalArgumentException e) {
-          assertThat(e.getMessage()).isEqualTo(String.format("Component %s must be a project (qualifier=%s)", component.uuid(), component.qualifier()));
-        } catch (IllegalStateException e) {
-          assertThat(e.getMessage()).isEqualTo("Could not find project uuid " + component.uuid());
-        }
-      });
-  }
-
-  @Test
-  public void onEnd_fails_with_RowNotFoundException_if_activity_for_task_does_not_exist_in_DB() {
-    String componentUuid = randomAlphanumeric(6);
-    String taskUuid = randomAlphanumeric(6);
-    when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
-    when(ceTaskMock.getUuid()).thenReturn(taskUuid);
-    when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
-    when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class)))
-      .thenReturn(true);
-    dbTester.components().insertPrivateProject(s -> s.setUuid(componentUuid));
-
-    Duration randomDuration = randomDuration();
-    assertThatThrownBy(() -> underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock))
-      .isInstanceOf(RowNotFoundException.class)
-      .hasMessage("CeActivity with uuid '" + taskUuid + "' not found");
-  }
-
-  @Test
-  public void onEnd_creates_notification_with_data_from_activity_and_project_and_deliver_it() {
-    String taskUuid = randomAlphanumeric(12);
-    int createdAt = random.nextInt(999_999);
-    long executedAt = random.nextInt(999_999);
-    ComponentDto project = initMocksToPassConditions(taskUuid, createdAt, executedAt);
-    Notification notificationMock = mockSerializer();
-
-    underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
-
-    ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
-    verify(notificationService).deliver(same(notificationMock));
-
-    ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
-
-    ReportAnalysisFailureNotificationBuilder.Project notificationProject = reportAnalysisFailureNotificationBuilder.project();
-    assertThat(notificationProject.name()).isEqualTo(project.name());
-    assertThat(notificationProject.key()).isEqualTo(project.getKey());
-    assertThat(notificationProject.uuid()).isEqualTo(project.uuid());
-    assertThat(notificationProject.branchName()).isNull();
-    ReportAnalysisFailureNotificationBuilder.Task notificationTask = reportAnalysisFailureNotificationBuilder.task();
-    assertThat(notificationTask.uuid()).isEqualTo(taskUuid);
-    assertThat(notificationTask.createdAt()).isEqualTo(createdAt);
-    assertThat(notificationTask.failedAt()).isEqualTo(executedAt);
-  }
-
-  @Test
-  public void onEnd_creates_notification_with_error_message_from_Throwable_argument_message() {
-    initMocksToPassConditions(randomAlphanumeric(12), random.nextInt(999_999), (long) random.nextInt(999_999));
-    String message = randomAlphanumeric(66);
-    when(throwableMock.getMessage()).thenReturn(message);
-
-    underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
-
-    ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
-
-    ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
-    assertThat(reportAnalysisFailureNotificationBuilder.errorMessage()).isEqualTo(message);
-  }
-
-  @Test
-  public void onEnd_creates_notification_with_null_error_message_if_Throwable_is_null() {
-    String taskUuid = randomAlphanumeric(12);
-    initMocksToPassConditions(taskUuid, random.nextInt(999_999), (long) random.nextInt(999_999));
-    Notification notificationMock = mockSerializer();
-
-    underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, null);
-
-    verify(notificationService).deliver(same(notificationMock));
-    ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
-
-    ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
-    assertThat(reportAnalysisFailureNotificationBuilder.errorMessage()).isNull();
-  }
-
-  @Test
-  public void onEnd_ignores_null_CeTaskResult_argument() {
-    String taskUuid = randomAlphanumeric(12);
-    initMocksToPassConditions(taskUuid, random.nextInt(999_999), (long) random.nextInt(999_999));
-    Notification notificationMock = mockSerializer();
-
-    underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), null, null);
-
-    verify(notificationService).deliver(same(notificationMock));
-  }
-
-  @Test
-  public void onEnd_ignores_CeTaskResult_argument() {
-    String taskUuid = randomAlphanumeric(12);
-    initMocksToPassConditions(taskUuid, random.nextInt(999_999), (long) random.nextInt(999_999));
-    Notification notificationMock = mockSerializer();
-
-    underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, null);
-
-    verify(notificationService).deliver(same(notificationMock));
-    verifyNoInteractions(ceTaskResultMock);
-  }
-
-  @Test
-  public void onEnd_uses_system_data_as_failedAt_if_task_has_no_executedAt() {
-    String taskUuid = randomAlphanumeric(12);
-    initMocksToPassConditions(taskUuid, random.nextInt(999_999), null);
-    long now = random.nextInt(999_999);
-    when(system2.now()).thenReturn(now);
-    Notification notificationMock = mockSerializer();
-
-    underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, null);
-
-    verify(notificationService).deliver(same(notificationMock));
-    ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
-    assertThat(notificationCaptor.getValue().task().failedAt()).isEqualTo(now);
-  }
-
-  private ReportAnalysisFailureNotification mockSerializer() {
-    ReportAnalysisFailureNotification notificationMock = mock(ReportAnalysisFailureNotification.class);
-    when(serializer.toNotification(any(ReportAnalysisFailureNotificationBuilder.class))).thenReturn(notificationMock);
-    return notificationMock;
-  }
-
-  private ComponentDto initMocksToPassConditions(String taskUuid, int createdAt, @Nullable Long executedAt) {
-    ComponentDto project = random.nextBoolean() ? dbTester.components().insertPrivateProject() : dbTester.components().insertPublicProject();
-    when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
-    when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(project.uuid(), null, null)));
-    when(ceTaskMock.getUuid()).thenReturn(taskUuid);
-    when(notificationService.hasProjectSubscribersForTypes(project.uuid(), singleton(ReportAnalysisFailureNotification.class)))
-      .thenReturn(true);
-    insertActivityDto(taskUuid, createdAt, executedAt, project);
-    return project;
-  }
-
-  private void insertActivityDto(String taskUuid, int createdAt, @Nullable Long executedAt, ComponentDto project) {
-    dbClient.ceActivityDao().insert(dbTester.getSession(), new CeActivityDto(new CeQueueDto()
-      .setUuid(taskUuid)
-      .setTaskType(CeTaskTypes.REPORT)
-      .setComponentUuid(project.uuid())
-      .setCreatedAt(createdAt))
-      .setExecutedAt(executedAt)
-      .setStatus(CeActivityDto.Status.FAILED));
-    dbTester.getSession().commit();
-  }
-
-  private ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> verifyAndCaptureSerializedNotification() {
-    ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = ArgumentCaptor.forClass(ReportAnalysisFailureNotificationBuilder.class);
-    verify(serializer).toNotification(notificationCaptor.capture());
-    return notificationCaptor;
-  }
-
-  private Duration randomDuration() {
-    return Duration.of(random.nextLong(), ChronoUnit.MILLIS);
-  }
-}
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/queue/InternalCeQueueImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/queue/InternalCeQueueImplTest.java
deleted file mode 100644 (file)
index 4a537ed..0000000
+++ /dev/null
@@ -1,728 +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.queue;
-
-import com.google.common.collect.ImmutableSet;
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
-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.config.Configuration;
-import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
-import org.sonar.api.utils.System2;
-import org.sonar.ce.container.ComputeEngineStatus;
-import org.sonar.ce.monitoring.CEQueueStatus;
-import org.sonar.ce.monitoring.CEQueueStatusImpl;
-import org.sonar.ce.task.CeTask;
-import org.sonar.ce.task.CeTaskResult;
-import org.sonar.ce.task.TypedException;
-import org.sonar.core.util.UuidFactory;
-import org.sonar.core.util.UuidFactoryImpl;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.ce.CeActivityDto;
-import org.sonar.db.ce.CeQueueDto;
-import org.sonar.db.ce.CeQueueTesting;
-import org.sonar.db.ce.CeTaskTypes;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.user.UserDto;
-import org.sonar.server.platform.NodeInformation;
-
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptyMap;
-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.ArgumentMatchers.anyLong;
-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.container.ComputeEngineStatus.Status.STARTED;
-import static org.sonar.ce.container.ComputeEngineStatus.Status.STOPPING;
-
-public class InternalCeQueueImplTest {
-
-  private static final String AN_ANALYSIS_UUID = "U1";
-  private static final String WORKER_UUID_1 = "worker uuid 1";
-  private static final String WORKER_UUID_2 = "worker uuid 2";
-  private static final String NODE_NAME = "nodeName1";
-
-  private System2 system2 = new AlwaysIncreasingSystem2();
-
-  @Rule
-  public DbTester db = DbTester.create(system2);
-
-  private DbSession session = db.getSession();
-
-  private UuidFactory uuidFactory = UuidFactoryImpl.INSTANCE;
-  private CEQueueStatus queueStatus = new CEQueueStatusImpl(db.getDbClient(), mock(System2.class));
-  private ComputeEngineStatus computeEngineStatus = mock(ComputeEngineStatus.class);
-  private Configuration config = mock(Configuration.class);
-  private NextPendingTaskPicker nextPendingTaskPicker = new NextPendingTaskPicker(config, db.getDbClient());
-  private NodeInformation nodeInformation = mock(NodeInformation.class);
-  private InternalCeQueue underTest = new InternalCeQueueImpl(system2, db.getDbClient(), uuidFactory, queueStatus,
-    computeEngineStatus, nextPendingTaskPicker, nodeInformation);
-
-  @Before
-  public void setUp() {
-    when(config.getBoolean(any())).thenReturn(Optional.of(false));
-    when(computeEngineStatus.getStatus()).thenReturn(STARTED);
-    when(nodeInformation.getNodeName()).thenReturn(Optional.of(NODE_NAME));
-  }
-
-  @Test
-  public void submit_returns_task_populated_from_CeTaskSubmit_and_creates_CeQueue_row() {
-    CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"), "rob");
-    CeTask task = underTest.submit(taskSubmit);
-    UserDto userDto = db.getDbClient().userDao().selectByUuid(db.getSession(), taskSubmit.getSubmitterUuid());
-    verifyCeTask(taskSubmit, task, null, userDto);
-    verifyCeQueueDtoForTaskSubmit(taskSubmit);
-  }
-
-  @Test
-  public void submit_populates_component_name_and_key_of_CeTask_if_component_exists() {
-    ComponentDto componentDto = insertComponent(newProjectDto("PROJECT_1"));
-    CeTaskSubmit taskSubmit = createTaskSubmit(CeTaskTypes.REPORT, componentDto, null);
-
-    CeTask task = underTest.submit(taskSubmit);
-
-    verifyCeTask(taskSubmit, task, componentDto, null);
-  }
-
-  @Test
-  public void submit_returns_task_without_component_info_when_submit_has_none() {
-    CeTaskSubmit taskSubmit = createTaskSubmit("not cpt related");
-
-    CeTask task = underTest.submit(taskSubmit);
-
-    verifyCeTask(taskSubmit, task, null, null);
-  }
-
-  @Test
-  public void massSubmit_returns_tasks_for_each_CeTaskSubmit_populated_from_CeTaskSubmit_and_creates_CeQueue_row_for_each() {
-    CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"), "rob");
-    CeTaskSubmit taskSubmit2 = createTaskSubmit("some type");
-
-    List<CeTask> tasks = underTest.massSubmit(asList(taskSubmit1, taskSubmit2));
-
-    UserDto userDto1 = db.getDbClient().userDao().selectByUuid(db.getSession(), taskSubmit1.getSubmitterUuid());
-    assertThat(tasks).hasSize(2);
-    verifyCeTask(taskSubmit1, tasks.get(0), null, userDto1);
-    verifyCeTask(taskSubmit2, tasks.get(1), null, null);
-    verifyCeQueueDtoForTaskSubmit(taskSubmit1);
-    verifyCeQueueDtoForTaskSubmit(taskSubmit2);
-  }
-
-  @Test
-  public void massSubmit_populates_component_name_and_key_of_CeTask_if_component_exists() {
-    ComponentDto componentDto1 = insertComponent(newProjectDto("PROJECT_1"));
-    CeTaskSubmit taskSubmit1 = createTaskSubmit(CeTaskTypes.REPORT, componentDto1, null);
-    CeTaskSubmit taskSubmit2 = createTaskSubmit("something", newProjectDto("non existing component uuid"), null);
-
-    List<CeTask> tasks = underTest.massSubmit(asList(taskSubmit1, taskSubmit2));
-
-    assertThat(tasks).hasSize(2);
-    verifyCeTask(taskSubmit1, tasks.get(0), componentDto1, null);
-    verifyCeTask(taskSubmit2, tasks.get(1), null, null);
-  }
-
-  @Test
-  public void peek_throws_NPE_if_workerUUid_is_null() {
-    assertThatThrownBy(() -> underTest.peek(null, true))
-      .isInstanceOf(NullPointerException.class)
-      .hasMessage("workerUuid can't be null");
-  }
-
-  @Test
-  public void test_remove() {
-    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
-    Optional<CeTask> peek = underTest.peek(WORKER_UUID_1, true);
-    underTest.remove(peek.get(), CeActivityDto.Status.SUCCESS, null, null);
-
-    // queue is empty
-    assertThat(db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), task.getUuid())).isNotPresent();
-    assertThat(underTest.peek(WORKER_UUID_2, true)).isNotPresent();
-
-    // available in history
-    Optional<CeActivityDto> history = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), task.getUuid());
-    assertThat(history).isPresent();
-    assertThat(history.get().getStatus()).isEqualTo(CeActivityDto.Status.SUCCESS);
-    assertThat(history.get().getIsLast()).isTrue();
-    assertThat(history.get().getAnalysisUuid()).isNull();
-  }
-
-  @Test
-  public void remove_throws_IAE_if_exception_is_provided_but_status_is_SUCCESS() {
-    assertThatThrownBy(() -> underTest.remove(mock(CeTask.class), CeActivityDto.Status.SUCCESS, null, new RuntimeException("Some error")))
-      .isInstanceOf(IllegalArgumentException.class)
-      .hasMessage("Error can be provided only when status is FAILED");
-  }
-
-  @Test
-  public void remove_throws_IAE_if_exception_is_provided_but_status_is_CANCELED() {
-    assertThatThrownBy(() -> underTest.remove(mock(CeTask.class), CeActivityDto.Status.CANCELED, null, new RuntimeException("Some error")))
-      .isInstanceOf(IllegalArgumentException.class)
-      .hasMessage("Error can be provided only when status is FAILED");
-  }
-
-  @Test
-  public void remove_does_not_set_analysisUuid_in_CeActivity_when_CeTaskResult_has_no_analysis_uuid() {
-    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
-    Optional<CeTask> peek = underTest.peek(WORKER_UUID_1, true);
-    underTest.remove(peek.get(), CeActivityDto.Status.SUCCESS, newTaskResult(null), null);
-
-    // available in history
-    Optional<CeActivityDto> history = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), task.getUuid());
-    assertThat(history).isPresent();
-    assertThat(history.get().getAnalysisUuid()).isNull();
-  }
-
-  @Test
-  public void remove_sets_analysisUuid_in_CeActivity_when_CeTaskResult_has_analysis_uuid() {
-    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
-
-    Optional<CeTask> peek = underTest.peek(WORKER_UUID_2, true);
-    underTest.remove(peek.get(), CeActivityDto.Status.SUCCESS, newTaskResult(AN_ANALYSIS_UUID), null);
-
-    // available in history
-    Optional<CeActivityDto> history = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), task.getUuid());
-    assertThat(history).isPresent();
-    assertThat(history.get().getAnalysisUuid()).isEqualTo("U1");
-  }
-
-  @Test
-  public void remove_sets_nodeName_in_CeActivity_when_nodeInformation_defines_node_name() {
-    when(nodeInformation.getNodeName()).thenReturn(Optional.of(NODE_NAME));
-    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
-
-    Optional<CeTask> peek = underTest.peek(WORKER_UUID_2, true);
-    underTest.remove(peek.get(), CeActivityDto.Status.SUCCESS, newTaskResult(AN_ANALYSIS_UUID), null);
-
-    Optional<CeActivityDto> history = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), task.getUuid());
-    assertThat(history).isPresent();
-    assertThat(history.get().getNodeName()).isEqualTo(NODE_NAME);
-  }
-
-  @Test
-  public void remove_do_not_set_nodeName_in_CeActivity_when_nodeInformation_does_not_define_node_name() {
-    when(nodeInformation.getNodeName()).thenReturn(Optional.empty());
-    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
-
-    Optional<CeTask> peek = underTest.peek(WORKER_UUID_2, true);
-    underTest.remove(peek.get(), CeActivityDto.Status.SUCCESS, newTaskResult(AN_ANALYSIS_UUID), null);
-
-    Optional<CeActivityDto> history = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), task.getUuid());
-    assertThat(history).isPresent();
-    assertThat(history.get().getNodeName()).isNull();
-  }
-
-  @Test
-  public void remove_saves_error_message_and_stacktrace_when_exception_is_provided() {
-    Throwable error = new NullPointerException("Fake NPE to test persistence to DB");
-
-    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
-    Optional<CeTask> peek = underTest.peek(WORKER_UUID_1, true);
-    underTest.remove(peek.get(), CeActivityDto.Status.FAILED, null, error);
-
-    Optional<CeActivityDto> activityDto = db.getDbClient().ceActivityDao().selectByUuid(session, task.getUuid());
-    assertThat(activityDto).isPresent();
-
-    assertThat(activityDto.get().getErrorMessage()).isEqualTo(error.getMessage());
-    assertThat(activityDto.get().getErrorStacktrace()).isEqualToIgnoringWhitespace(stacktraceToString(error));
-    assertThat(activityDto.get().getErrorType()).isNull();
-  }
-
-  @Test
-  public void remove_saves_error_when_TypedMessageException_is_provided() {
-    Throwable error = new TypedExceptionImpl("aType", "aMessage");
-
-    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
-    Optional<CeTask> peek = underTest.peek(WORKER_UUID_1, true);
-    underTest.remove(peek.get(), CeActivityDto.Status.FAILED, null, error);
-
-    CeActivityDto activityDto = db.getDbClient().ceActivityDao().selectByUuid(session, task.getUuid()).get();
-    assertThat(activityDto.getErrorType()).isEqualTo("aType");
-    assertThat(activityDto.getErrorMessage()).isEqualTo("aMessage");
-    assertThat(activityDto.getErrorStacktrace()).isEqualToIgnoringWhitespace(stacktraceToString(error));
-  }
-
-  @Test
-  public void remove_updates_queueStatus_success_even_if_task_does_not_exist_in_DB() {
-    CEQueueStatus queueStatus = mock(CEQueueStatus.class);
-
-    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
-    db.getDbClient().ceQueueDao().deleteByUuid(db.getSession(), task.getUuid());
-    db.commit();
-
-    InternalCeQueueImpl underTest = new InternalCeQueueImpl(system2, db.getDbClient(), null, queueStatus, null, null, nodeInformation);
-
-    try {
-      underTest.remove(task, CeActivityDto.Status.SUCCESS, null, null);
-      fail("remove should have thrown a IllegalStateException");
-    } catch (IllegalStateException e) {
-      verify(queueStatus).addSuccess(anyLong());
-    }
-  }
-
-  @Test
-  public void remove_updates_queueStatus_failure_even_if_task_does_not_exist_in_DB() {
-    CEQueueStatus queueStatusMock = mock(CEQueueStatus.class);
-
-    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
-    db.getDbClient().ceQueueDao().deleteByUuid(db.getSession(), task.getUuid());
-    db.commit();
-    InternalCeQueueImpl underTest = new InternalCeQueueImpl(system2, db.getDbClient(), null, queueStatusMock, null, null, nodeInformation);
-
-    try {
-      underTest.remove(task, CeActivityDto.Status.FAILED, null, null);
-      fail("remove should have thrown a IllegalStateException");
-    } catch (IllegalStateException e) {
-      verify(queueStatusMock).addError(anyLong());
-    }
-  }
-
-  @Test
-  public void cancelWornOuts_does_not_update_queueStatus() {
-
-    CEQueueStatus queueStatusMock = mock(CEQueueStatus.class);
-
-    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
-    db.executeUpdateSql("update ce_queue set status = 'PENDING', started_at = 123 where uuid = '" + task.getUuid() + "'");
-    db.commit();
-    InternalCeQueueImpl underTest = new InternalCeQueueImpl(system2, db.getDbClient(), null, queueStatusMock, null, null, nodeInformation);
-
-    underTest.cancelWornOuts();
-
-    Optional<CeActivityDto> ceActivityDto = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), task.getUuid());
-    assertThat(ceActivityDto).isPresent();
-    assertThat(ceActivityDto.get().getNodeName()).isEqualTo(NODE_NAME);
-    verifyNoInteractions(queueStatusMock);
-  }
-
-  private static class TypedExceptionImpl extends RuntimeException implements TypedException {
-    private final String type;
-
-    private TypedExceptionImpl(String type, String message) {
-      super(message);
-      this.type = type;
-    }
-
-    @Override
-    public String getType() {
-      return type;
-    }
-  }
-
-  @Test
-  public void remove_copies_workerUuid() {
-    CeQueueDto ceQueueDto = db.getDbClient().ceQueueDao().insert(session, new CeQueueDto()
-      .setUuid("uuid")
-      .setTaskType("foo")
-      .setStatus(CeQueueDto.Status.PENDING));
-    makeInProgress(ceQueueDto, "Dustin");
-    db.commit();
-
-    underTest.remove(new CeTask.Builder()
-      .setUuid("uuid")
-      .setType("bar")
-      .build(), CeActivityDto.Status.SUCCESS, null, null);
-
-    CeActivityDto dto = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), "uuid").get();
-    assertThat(dto.getWorkerUuid()).isEqualTo("Dustin");
-  }
-
-  @Test
-  public void fail_to_remove_if_not_in_queue() {
-    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
-    underTest.remove(task, CeActivityDto.Status.SUCCESS, null, null);
-
-    assertThatThrownBy(() -> underTest.remove(task, CeActivityDto.Status.SUCCESS, null, null))
-      .isInstanceOf(IllegalStateException.class);
-  }
-
-  @Test
-  public void test_peek() {
-    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
-
-    Optional<CeTask> peek = underTest.peek(WORKER_UUID_1, true);
-    assertThat(peek).isPresent();
-    assertThat(peek.get().getUuid()).isEqualTo(task.getUuid());
-    assertThat(peek.get().getType()).isEqualTo(CeTaskTypes.REPORT);
-    assertThat(peek.get().getComponent()).contains(new CeTask.Component("PROJECT_1", null, null));
-    assertThat(peek.get().getMainComponent()).contains(peek.get().getComponent().get());
-
-    // no more pending tasks
-    peek = underTest.peek(WORKER_UUID_2, true);
-    assertThat(peek).isEmpty();
-  }
-
-  @Test
-  public void peek_populates_name_and_key_for_existing_component_and_main_component() {
-    ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto branch = db.components().insertProjectBranch(project);
-    CeTask task = submit(CeTaskTypes.REPORT, branch);
-
-    Optional<CeTask> peek = underTest.peek(WORKER_UUID_1, true);
-    assertThat(peek).isPresent();
-    assertThat(peek.get().getUuid()).isEqualTo(task.getUuid());
-    assertThat(peek.get().getType()).isEqualTo(CeTaskTypes.REPORT);
-    assertThat(peek.get().getComponent()).contains(new CeTask.Component(branch.uuid(), branch.getKey(), branch.name()));
-    assertThat(peek.get().getMainComponent()).contains(new CeTask.Component(project.uuid(), project.getKey(), project.name()));
-
-    // no more pending tasks
-    peek = underTest.peek(WORKER_UUID_2, true);
-    assertThat(peek).isEmpty();
-  }
-
-  @Test
-  public void peek_is_paused_then_resumed() {
-    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
-    underTest.pauseWorkers();
-
-    Optional<CeTask> peek = underTest.peek(WORKER_UUID_1, true);
-    assertThat(peek).isEmpty();
-
-    underTest.resumeWorkers();
-    peek = underTest.peek(WORKER_UUID_1, true);
-    assertThat(peek).isPresent();
-    assertThat(peek.get().getUuid()).isEqualTo(task.getUuid());
-  }
-
-  @Test
-  public void peek_ignores_in_progress_tasks() {
-    CeQueueDto dto = db.getDbClient().ceQueueDao().insert(session, new CeQueueDto()
-      .setUuid("uuid")
-      .setTaskType("foo")
-      .setStatus(CeQueueDto.Status.PENDING));
-    makeInProgress(dto, "foo");
-    db.commit();
-
-    assertThat(underTest.peek(WORKER_UUID_1, true)).isEmpty();
-  }
-
-  @Test
-  public void peek_nothing_if_application_status_stopping() {
-    submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
-    when(computeEngineStatus.getStatus()).thenReturn(STOPPING);
-
-    Optional<CeTask> peek = underTest.peek(WORKER_UUID_1, true);
-    assertThat(peek).isEmpty();
-  }
-
-  @Test
-  public void peek_peeks_pending_task() {
-    db.getDbClient().ceQueueDao().insert(session, new CeQueueDto()
-      .setUuid("uuid")
-      .setTaskType("foo")
-      .setStatus(CeQueueDto.Status.PENDING));
-    db.commit();
-
-    assertThat(underTest.peek(WORKER_UUID_1, true).get().getUuid()).isEqualTo("uuid");
-  }
-
-  @Test
-  public void peek_resets_to_pending_any_task_in_progress_for_specified_worker_uuid_and_updates_updatedAt() {
-    insertPending("u0"); // add a pending one that will be picked so that u1 isn't peek and status reset is visible in DB
-    CeQueueDto u1 = insertPending("u1");// will be picked-because older than any of the reset ones
-    CeQueueDto u2 = insertInProgress("u2", WORKER_UUID_1);// will be reset
-
-    assertThat(underTest.peek(WORKER_UUID_1, true).get().getUuid()).isEqualTo("u0");
-
-    verifyUnmodifiedTask(u1);
-    verifyResetTask(u2);
-  }
-
-  @Test
-  public void peek_resets_to_pending_any_task_in_progress_for_specified_worker_uuid_and_only_this_uuid() {
-    insertPending("u0"); // add a pending one that will be picked so that u1 isn't peek and status reset is visible in DB
-    CeQueueDto u1 = insertInProgress("u1", WORKER_UUID_1);
-    CeQueueDto u2 = insertInProgress("u2", WORKER_UUID_2);
-    CeQueueDto u3 = insertInProgress("u3", WORKER_UUID_1);
-    CeQueueDto u4 = insertInProgress("u4", WORKER_UUID_2);
-
-    assertThat(underTest.peek(WORKER_UUID_1, true).get().getUuid()).isEqualTo("u0");
-
-    verifyResetTask(u1);
-    verifyUnmodifiedTask(u2);
-    verifyResetTask(u3);
-    verifyUnmodifiedTask(u4);
-  }
-
-  private void verifyResetTask(CeQueueDto originalDto) {
-    CeQueueDto dto = db.getDbClient().ceQueueDao().selectByUuid(session, originalDto.getUuid()).get();
-    assertThat(dto.getStatus()).isEqualTo(CeQueueDto.Status.PENDING);
-    assertThat(dto.getCreatedAt()).isEqualTo(originalDto.getCreatedAt());
-    assertThat(dto.getUpdatedAt()).isGreaterThan(originalDto.getUpdatedAt());
-  }
-
-  private void verifyUnmodifiedTask(CeQueueDto originalDto) {
-    CeQueueDto dto = db.getDbClient().ceQueueDao().selectByUuid(session, originalDto.getUuid()).get();
-    assertThat(dto.getStatus()).isEqualTo(originalDto.getStatus());
-    assertThat(dto.getCreatedAt()).isEqualTo(originalDto.getCreatedAt());
-    assertThat(dto.getUpdatedAt()).isEqualTo(originalDto.getUpdatedAt());
-  }
-
-  private CeQueueDto insertInProgress(String uuid, String workerUuid) {
-    CeQueueDto dto = new CeQueueDto()
-      .setUuid(uuid)
-      .setTaskType("foo")
-      .setStatus(CeQueueDto.Status.PENDING);
-    db.getDbClient().ceQueueDao().insert(session, dto);
-    makeInProgress(dto, workerUuid);
-    db.commit();
-    return db.getDbClient().ceQueueDao().selectByUuid(session, uuid).get();
-  }
-
-  private CeQueueDto insertPending(String uuid) {
-    CeQueueDto dto = new CeQueueDto()
-      .setUuid(uuid)
-      .setTaskType("foo")
-      .setStatus(CeQueueDto.Status.PENDING);
-    db.getDbClient().ceQueueDao().insert(session, dto);
-    db.commit();
-    return dto;
-  }
-
-  @Test
-  public void cancel_pending() {
-    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
-    CeQueueDto queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), task.getUuid()).get();
-
-    underTest.cancel(db.getSession(), queueDto);
-
-    Optional<CeActivityDto> activity = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), task.getUuid());
-    assertThat(activity).isPresent();
-    assertThat(activity.get().getStatus()).isEqualTo(CeActivityDto.Status.CANCELED);
-  }
-
-  @Test
-  public void fail_to_cancel_if_in_progress() {
-    CeTask task = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
-    underTest.peek(WORKER_UUID_2, true);
-    CeQueueDto queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), task.getUuid()).get();
-
-    assertThatThrownBy(() -> underTest.cancel(db.getSession(), queueDto))
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessageContaining("Task is in progress and can't be canceled");
-  }
-
-  @Test
-  public void cancelAll_pendings_but_not_in_progress() {
-    CeTask inProgressTask = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_1"));
-    CeTask pendingTask1 = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_2"));
-    CeTask pendingTask2 = submit(CeTaskTypes.REPORT, newProjectDto("PROJECT_3"));
-    underTest.peek(WORKER_UUID_2, true);
-
-    int canceledCount = underTest.cancelAll();
-    assertThat(canceledCount).isEqualTo(2);
-
-    Optional<CeActivityDto> history = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), pendingTask1.getUuid());
-    assertThat(history.get().getStatus()).isEqualTo(CeActivityDto.Status.CANCELED);
-    history = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), pendingTask2.getUuid());
-    assertThat(history.get().getStatus()).isEqualTo(CeActivityDto.Status.CANCELED);
-    history = db.getDbClient().ceActivityDao().selectByUuid(db.getSession(), inProgressTask.getUuid());
-    assertThat(history).isEmpty();
-  }
-
-  @Test
-  public void resetTasksWithUnknownWorkerUUIDs_reset_only_in_progress_tasks() {
-    CeQueueDto u1 = insertCeQueueDto("u1");
-    CeQueueDto u2 = insertCeQueueDto("u2");
-    CeQueueDto u6 = insertInProgress("u6", "worker1");
-    CeQueueDto u7 = insertInProgress("u7", "worker2");
-    CeQueueDto u8 = insertInProgress("u8", "worker3");
-
-    underTest.resetTasksWithUnknownWorkerUUIDs(ImmutableSet.of("worker2", "worker3"));
-
-    // Pending tasks must not be modified even if a workerUUID is not present
-    verifyUnmodified(u1);
-    verifyUnmodified(u2);
-
-    // Unknown worker : null, "worker1"
-    verifyReset(u6);
-
-    // Known workers : "worker2", "worker3"
-    verifyUnmodified(u7);
-    verifyUnmodified(u8);
-  }
-
-  @Test
-  public void resetTasksWithUnknownWorkerUUIDs_with_empty_set_will_reset_all_in_progress_tasks() {
-    CeQueueDto u1 = insertCeQueueDto("u1");
-    CeQueueDto u2 = insertCeQueueDto("u2");
-    CeQueueDto u6 = insertInProgress("u6", "worker1");
-    CeQueueDto u7 = insertInProgress("u7", "worker2");
-    CeQueueDto u8 = insertInProgress("u8", "worker3");
-
-    underTest.resetTasksWithUnknownWorkerUUIDs(ImmutableSet.of());
-
-    // Pending tasks must not be modified even if a workerUUID is not present
-    verifyUnmodified(u1);
-    verifyUnmodified(u2);
-
-    // Unknown worker : null, "worker1"
-    verifyReset(u6);
-    verifyReset(u7);
-    verifyReset(u8);
-  }
-
-  @Test
-  public void resetTasksWithUnknownWorkerUUIDs_with_worker_without_tasks_will_reset_all_in_progress_tasks() {
-    CeQueueDto u1 = insertCeQueueDto("u1");
-    CeQueueDto u2 = insertCeQueueDto("u2");
-    CeQueueDto u6 = insertInProgress("u6", "worker1");
-    CeQueueDto u7 = insertInProgress("u7", "worker2");
-    CeQueueDto u8 = insertInProgress("u8", "worker3");
-
-    underTest.resetTasksWithUnknownWorkerUUIDs(ImmutableSet.of("worker1000", "worker1001"));
-
-    // Pending tasks must not be modified even if a workerUUID is not present
-    verifyUnmodified(u1);
-    verifyUnmodified(u2);
-
-    // Unknown worker : null, "worker1"
-    verifyReset(u6);
-    verifyReset(u7);
-    verifyReset(u8);
-  }
-
-  private void verifyReset(CeQueueDto original) {
-    CeQueueDto dto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), original.getUuid()).get();
-    // We do not touch CreatedAt
-    assertThat(dto.getCreatedAt()).isEqualTo(original.getCreatedAt());
-
-    // Status must have changed to PENDING and must not be equal to previous status
-    assertThat(dto.getStatus()).isEqualTo(CeQueueDto.Status.PENDING).isNotEqualTo(original.getStatus());
-    // UpdatedAt must have been updated
-    assertThat(dto.getUpdatedAt()).isNotEqualTo(original.getUpdatedAt());
-    assertThat(dto.getStartedAt()).isEqualTo(original.getStartedAt());
-    // WorkerUuid must be null
-    assertThat(dto.getWorkerUuid()).isNull();
-  }
-
-  private void verifyUnmodified(CeQueueDto original) {
-    CeQueueDto dto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), original.getUuid()).get();
-    assertThat(dto.getStatus()).isEqualTo(original.getStatus());
-    assertThat(dto.getCreatedAt()).isEqualTo(original.getCreatedAt());
-    assertThat(dto.getUpdatedAt()).isEqualTo(original.getUpdatedAt());
-  }
-
-  private CeQueueDto insertCeQueueDto(String uuid) {
-    CeQueueDto dto = new CeQueueDto()
-      .setUuid(uuid)
-      .setTaskType("foo")
-      .setStatus(CeQueueDto.Status.PENDING);
-    db.getDbClient().ceQueueDao().insert(db.getSession(), dto);
-    db.commit();
-    return dto;
-  }
-
-  private void verifyCeTask(CeTaskSubmit taskSubmit, CeTask task, @Nullable ComponentDto componentDto, @Nullable UserDto userDto) {
-    assertThat(task.getUuid()).isEqualTo(taskSubmit.getUuid());
-    assertThat(task.getType()).isEqualTo(taskSubmit.getType());
-    if (componentDto != null) {
-      CeTask.Component component = task.getComponent().get();
-      assertThat(component.getUuid()).isEqualTo(componentDto.uuid());
-      assertThat(component.getKey()).contains(componentDto.getKey());
-      assertThat(component.getName()).contains(componentDto.name());
-    } else if (taskSubmit.getComponent().isPresent()) {
-      assertThat(task.getComponent()).contains(new CeTask.Component(taskSubmit.getComponent().get().getUuid(), null, null));
-    } else {
-      assertThat(task.getComponent()).isEmpty();
-    }
-    if (taskSubmit.getSubmitterUuid() != null) {
-      if (userDto == null) {
-        assertThat(task.getSubmitter().uuid()).isEqualTo(taskSubmit.getSubmitterUuid());
-        assertThat(task.getSubmitter().login()).isNull();
-      } else {
-        assertThat(task.getSubmitter().uuid()).isEqualTo(userDto.getUuid()).isEqualTo(taskSubmit.getSubmitterUuid());
-        assertThat(task.getSubmitter().uuid()).isEqualTo(userDto.getLogin());
-      }
-    }
-  }
-
-  private void verifyCeQueueDtoForTaskSubmit(CeTaskSubmit taskSubmit) {
-    Optional<CeQueueDto> queueDto = db.getDbClient().ceQueueDao().selectByUuid(db.getSession(), taskSubmit.getUuid());
-    assertThat(queueDto).isPresent();
-    CeQueueDto dto = queueDto.get();
-    assertThat(dto.getTaskType()).isEqualTo(taskSubmit.getType());
-    Optional<CeTaskSubmit.Component> component = taskSubmit.getComponent();
-    if (component.isPresent()) {
-      assertThat(dto.getMainComponentUuid()).isEqualTo(component.get().getMainComponentUuid());
-      assertThat(dto.getComponentUuid()).isEqualTo(component.get().getUuid());
-    } else {
-      assertThat(dto.getMainComponentUuid()).isNull();
-      assertThat(dto.getComponentUuid()).isNull();
-    }
-    assertThat(dto.getSubmitterUuid()).isEqualTo(taskSubmit.getSubmitterUuid());
-    assertThat(dto.getCreatedAt()).isEqualTo(dto.getUpdatedAt());
-  }
-
-  private ComponentDto newProjectDto(String uuid) {
-    return ComponentTesting.newPublicProjectDto(uuid).setName("name_" + uuid).setKey("key_" + uuid);
-  }
-
-  private CeTask submit(String reportType, ComponentDto componentDto) {
-    return underTest.submit(createTaskSubmit(reportType, componentDto, null));
-  }
-
-  private CeTaskSubmit createTaskSubmit(String type) {
-    return createTaskSubmit(type, null, null);
-  }
-
-  private CeTaskSubmit createTaskSubmit(String type, @Nullable ComponentDto componentDto, @Nullable String submitterUuid) {
-    CeTaskSubmit.Builder builder = underTest.prepareSubmit()
-      .setType(type)
-      .setSubmitterUuid(submitterUuid)
-      .setCharacteristics(emptyMap());
-    if (componentDto != null) {
-      builder.setComponent(CeTaskSubmit.Component.fromDto(componentDto));
-    }
-    return builder.build();
-  }
-
-  private CeTaskResult newTaskResult(@Nullable String analysisUuid) {
-    CeTaskResult taskResult = mock(CeTaskResult.class);
-    when(taskResult.getAnalysisUuid()).thenReturn(java.util.Optional.ofNullable(analysisUuid));
-    return taskResult;
-  }
-
-  private ComponentDto insertComponent(ComponentDto componentDto) {
-    return db.components().insertComponent(componentDto);
-  }
-
-  private CeQueueDto makeInProgress(CeQueueDto ceQueueDto, String workerUuid) {
-    CeQueueTesting.makeInProgress(session, workerUuid, system2.now(), ceQueueDto);
-    return db.getDbClient().ceQueueDao().selectByUuid(session, ceQueueDto.getUuid()).get();
-  }
-
-  private static String stacktraceToString(Throwable error) {
-    ByteArrayOutputStream out = new ByteArrayOutputStream();
-    error.printStackTrace(new PrintStream(out));
-    return out.toString();
-  }
-}
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/queue/NextPendingTaskPickerTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/queue/NextPendingTaskPickerTest.java
deleted file mode 100644 (file)
index 3fd745c..0000000
+++ /dev/null
@@ -1,384 +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.queue;
-
-import java.util.Optional;
-import java.util.UUID;
-import java.util.function.Consumer;
-import javax.annotation.Nullable;
-import org.jetbrains.annotations.NotNull;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
-import org.sonar.api.utils.System2;
-import org.sonar.api.utils.log.LogTester;
-import org.sonar.core.config.ComputeEngineProperties;
-import org.sonar.db.DbTester;
-import org.sonar.db.ce.CeQueueDto;
-import org.sonar.db.ce.CeTaskCharacteristicDto;
-import org.sonar.db.ce.CeTaskTypes;
-import org.sonar.db.component.ComponentDto;
-
-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.ce.CeQueueDto.Status.IN_PROGRESS;
-import static org.sonar.db.ce.CeQueueDto.Status.PENDING;
-import static org.sonar.db.ce.CeTaskCharacteristicDto.BRANCH_KEY;
-import static org.sonar.db.ce.CeTaskCharacteristicDto.PULL_REQUEST;
-
-public class NextPendingTaskPickerTest {
-
-  private final System2 alwaysIncreasingSystem2 = new AlwaysIncreasingSystem2(1L, 1);
-
-  private final Configuration config = mock(Configuration.class);
-
-  @Rule
-  public LogTester logTester = new LogTester();
-
-  private NextPendingTaskPicker underTest;
-
-  @Rule
-  public DbTester db = DbTester.create(alwaysIncreasingSystem2);
-
-  @Before
-  public void before() {
-    underTest = new NextPendingTaskPicker(config, db.getDbClient());
-    when(config.getBoolean(ComputeEngineProperties.CE_PARALLEL_PROJECT_TASKS_ENABLED)).thenReturn(Optional.of(true));
-  }
-
-  @Test
-  public void findPendingTask_whenNoTasksPending_returnsEmpty() {
-    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
-
-    assertThat(ceQueueDto).isEmpty();
-  }
-
-  @Test
-  public void findPendingTask_whenTwoTasksPending_returnsTheOlderOne() {
-    // both the 'eligibleTask' and 'parallelEligibleTask' will point to this one
-    insertPending("1");
-    insertPending("2");
-    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
-
-    assertThat(ceQueueDto).isPresent();
-    assertThat(ceQueueDto.get().getUuid()).isEqualTo("1");
-  }
-
-  @Test
-  public void findPendingTask_whenTwoTasksPendingWithSameCreationDate_returnsLowestUuid() {
-    insertPending("d", c -> c.setCreatedAt(1L).setUpdatedAt(1L));
-    insertPending("c", c -> c.setCreatedAt(1L).setUpdatedAt(1L));
-    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
-
-    assertThat(ceQueueDto).isPresent();
-    assertThat(ceQueueDto.get().getUuid()).isEqualTo("c");
-  }
-
-  @Test
-  public void findPendingTask_givenBranchInProgressAndPropertySet_returnQueuedPR() {
-    insertInProgress("1");
-    insertPendingPullRequest("2");
-    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
-
-    assertThat(ceQueueDto).isPresent();
-    assertThat(ceQueueDto.get().getUuid()).isEqualTo("2");
-
-    assertThat(logTester.logs()).contains("Task [uuid = " + ceQueueDto.get().getUuid() + "] will be run concurrently with other tasks for the same project");
-  }
-
-  @Test
-  public void findPendingTask_givenBranchInProgressAndPropertyNotSet_dontReturnQueuedPR() {
-    when(config.getBoolean(ComputeEngineProperties.CE_PARALLEL_PROJECT_TASKS_ENABLED)).thenReturn(Optional.of(false));
-    insertInProgress("1");
-    insertPendingPullRequest("2");
-    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
-
-    assertThat(ceQueueDto).isEmpty();
-  }
-
-  @Test
-  public void findPendingTask_given2PRsQueued_returnBothQueuedPR() {
-    insertPendingPullRequest("1");
-    insertPendingPullRequest("2");
-    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
-    Optional<CeQueueDto> ceQueueDto2 = underTest.findPendingTask("workerUuid2", db.getSession(), true);
-
-    assertThat(ceQueueDto).isPresent();
-    assertThat(ceQueueDto2).isPresent();
-    assertThat(ceQueueDto.get().getUuid()).isEqualTo("1");
-    assertThat(ceQueueDto.get().getStatus()).isEqualTo(IN_PROGRESS);
-    assertThat(ceQueueDto2.get().getStatus()).isEqualTo(IN_PROGRESS);
-  }
-
-  @Test
-  public void findPendingTask_given1MainBranch_2PRsQueued_returnMainBranchAndPRs() {
-    insertPending("1");
-    insertPendingPullRequest("2");
-    insertPendingPullRequest("3");
-    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
-    Optional<CeQueueDto> ceQueueDto2 = underTest.findPendingTask("workerUuid2", db.getSession(), true);
-    Optional<CeQueueDto> ceQueueDto3 = underTest.findPendingTask("workerUuid3", db.getSession(), true);
-
-    assertThat(ceQueueDto).isPresent();
-    assertThat(ceQueueDto2).isPresent();
-    assertThat(ceQueueDto3).isPresent();
-    assertThat(ceQueueDto.get().getUuid()).isEqualTo("1");
-    assertThat(ceQueueDto.get().getStatus()).isEqualTo(IN_PROGRESS);
-    assertThat(ceQueueDto2.get().getUuid()).isEqualTo("2");
-    assertThat(ceQueueDto2.get().getStatus()).isEqualTo(IN_PROGRESS);
-    assertThat(ceQueueDto3.get().getUuid()).isEqualTo("3");
-    assertThat(ceQueueDto3.get().getStatus()).isEqualTo(IN_PROGRESS);
-  }
-
-  @Test
-  public void findPendingTask_given1MainBranch_2BranchesQueued_returnOnyMainBranch() {
-    insertPending("1", null);
-    insertPendingBranch("2");
-    insertPendingBranch("3");
-    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
-    Optional<CeQueueDto> ceQueueDto2 = underTest.findPendingTask("workerUuid2", db.getSession(), true);
-
-    assertThat(ceQueueDto).isPresent();
-    assertThat(ceQueueDto2).isEmpty();
-    assertThat(ceQueueDto.get().getUuid()).isEqualTo("1");
-    assertThat(ceQueueDto.get().getStatus()).isEqualTo(IN_PROGRESS);
-  }
-
-  @Test
-  public void findPendingTask_given2BranchesQueued_returnOnlyFirstQueuedBranch() {
-    insertPending("1");
-    insertPendingBranch("2");
-    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
-    Optional<CeQueueDto> ceQueueDto2 = underTest.findPendingTask("workerUuid2", db.getSession(), true);
-
-    assertThat(ceQueueDto).isPresent();
-    assertThat(ceQueueDto2).isEmpty();
-    assertThat(ceQueueDto.get().getStatus()).isEqualTo(IN_PROGRESS);
-  }
-
-  @Test
-  public void findPendingTask_given2SamePRsQueued_returnOnlyFirstQueuedPR() {
-    insertPendingPullRequest("1", c -> c.setComponentUuid("pr1"));
-    insertPendingPullRequest("2", c -> c.setComponentUuid("pr1"));
-    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
-    Optional<CeQueueDto> ceQueueDto2 = underTest.findPendingTask("workerUuid2", db.getSession(), true);
-
-    assertThat(ceQueueDto).isPresent();
-    assertThat(ceQueueDto2).isEmpty();
-    assertThat(ceQueueDto.get().getStatus()).isEqualTo(IN_PROGRESS);
-  }
-
-  @Test
-  public void findPendingTask_givenBranchInTheQueueOlderThanPrInTheQueue_dontJumpAheadOfBranch() {
-    // we have branch task in progress. Next branch task needs to wait for this one to finish. We dont allow PRs to jump ahead of this branch
-    insertInProgress("1");
-    insertPending("2");
-    insertPendingPullRequest("3");
-
-    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
-
-    assertThat(ceQueueDto).isEmpty();
-  }
-
-  @Test
-  public void findPendingTask_givenDifferentProjectAndPrInTheQueue_dontJumpAheadOfDifferentProject() {
-    // we have branch task in progress.
-    insertInProgress("1");
-    // The PR can run in parallel, but needs to wait for this other project to finish. We dont allow PRs to jump ahead
-    insertPending("2", c -> c.setMainComponentUuid("different project"));
-    insertPendingPullRequest("3");
-
-    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
-
-    assertThat(ceQueueDto).isPresent();
-    assertThat(ceQueueDto.get().getUuid()).isEqualTo("2");
-  }
-
-  @Test
-  public void findPendingTask_givenDifferentProjectAndPrInTheQueue_prCanRunFirst() {
-    // we have branch task in progress.
-    insertInProgress("1");
-    // The PR can run in parallel and is ahead of the other project
-    insertPendingPullRequest("2");
-    insertPending("3", c -> c.setMainComponentUuid("different project"));
-
-    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
-
-    assertThat(ceQueueDto).isPresent();
-    assertThat(ceQueueDto.get().getUuid()).isEqualTo("2");
-  }
-
-  @Test
-  public void findPendingTask_givenFivePrsInProgress_branchCanBeScheduled() {
-    insertInProgressPullRequest("1");
-    insertInProgressPullRequest("2");
-    insertInProgressPullRequest("3");
-    insertInProgressPullRequest("4");
-    insertInProgressPullRequest("5");
-    insertPending("6");
-
-    Optional<CeQueueDto> ceQueueDto = underTest.findPendingTask("workerUuid", db.getSession(), true);
-
-    assertThat(ceQueueDto).isPresent();
-    assertThat(ceQueueDto.get().getUuid()).isEqualTo("6");
-  }
-
-  @Test
-  public void findPendingTask_excludingViewPickUpOrphanBranches() {
-    insertPending("1", dto -> dto
-      .setComponentUuid("1")
-      .setMainComponentUuid("non-existing-uuid")
-      .setStatus(PENDING)
-      .setTaskType(CeTaskTypes.BRANCH_ISSUE_SYNC)
-      .setCreatedAt(100_000L));
-
-    Optional<CeQueueDto> peek = underTest.findPendingTask("1", db.getSession(), false);
-    assertThat(peek).isPresent();
-    assertThat(peek.get().getUuid()).isEqualTo("1");
-  }
-
-  @Test
-  public void exclude_portfolios_computation_when_indexing_issues() {
-    String taskUuid1 = "1", taskUuid2 = "2";
-    String mainComponentUuid = "1";
-    insertBranch(mainComponentUuid);
-    insertPending(taskUuid1, dto -> dto
-      .setComponentUuid(mainComponentUuid)
-      .setMainComponentUuid(mainComponentUuid)
-      .setStatus(PENDING)
-      .setTaskType(CeTaskTypes.BRANCH_ISSUE_SYNC)
-      .setCreatedAt(100_000L));
-
-    String view_uuid = "view_uuid";
-    insertView(view_uuid);
-    insertPending(taskUuid2, dto -> dto
-      .setComponentUuid(view_uuid)
-      .setMainComponentUuid(view_uuid)
-      .setStatus(PENDING)
-      .setTaskType(CeTaskTypes.REPORT)
-      .setCreatedAt(100_000L));
-
-    Optional<CeQueueDto> peek = underTest.findPendingTask("1", db.getSession(), false);
-    assertThat(peek).isPresent();
-    assertThat(peek.get().getUuid()).isEqualTo(taskUuid1);
-
-    Optional<CeQueueDto> peek2 = underTest.findPendingTask("1", db.getSession(), false);
-    assertThat(peek2).isPresent();
-    assertThat(peek2.get().getUuid()).isEqualTo(taskUuid2);
-  }
-
-  private CeQueueDto insertPending(String uuid) {
-    return insertPending(uuid, null);
-  }
-
-  private CeQueueDto insertPendingBranch(String uuid) {
-    CeQueueDto queue = insertPending(uuid, null);
-    insertCharacteristics(queue.getUuid(), BRANCH_KEY);
-    return queue;
-  }
-
-  private CeQueueDto insertPendingPullRequest(String uuid) {
-    return insertPendingPullRequest(uuid, null);
-  }
-
-  private CeQueueDto insertPendingPullRequest(String uuid, @Nullable Consumer<CeQueueDto> ceQueueDto) {
-    CeQueueDto queue = insertPending(uuid, ceQueueDto);
-    insertCharacteristics(queue.getUuid(), PULL_REQUEST);
-    return queue;
-  }
-
-  private CeQueueDto insertInProgressPullRequest(String uuid) {
-    CeQueueDto queue = insertInProgress(uuid, null);
-    insertCharacteristics(queue.getUuid(), PULL_REQUEST);
-    return queue;
-  }
-
-  private CeQueueDto insertInProgress(String uuid) {
-    return insertInProgress(uuid, null);
-  }
-
-  private CeQueueDto insertInProgress(String uuid, @Nullable Consumer<CeQueueDto> ceQueueDto) {
-    return insertTask(uuid, IN_PROGRESS, ceQueueDto);
-  }
-
-  private CeQueueDto insertPending(String uuid, @Nullable Consumer<CeQueueDto> ceQueueDto) {
-    return insertTask(uuid, PENDING, ceQueueDto);
-  }
-
-  private CeTaskCharacteristicDto insertCharacteristics(String taskUuid, String branchType) {
-    var ctcDto = new CeTaskCharacteristicDto();
-    ctcDto.setUuid(UUID.randomUUID().toString());
-    ctcDto.setTaskUuid(taskUuid);
-    ctcDto.setKey(branchType);
-    ctcDto.setValue("value");
-    db.getDbClient().ceTaskCharacteristicsDao().insert(db.getSession(), ctcDto);
-    db.getSession().commit();
-    return ctcDto;
-  }
-
-  private CeQueueDto insertTask(String uuid, CeQueueDto.Status status, @Nullable Consumer<CeQueueDto> ceQueueDtoConsumer) {
-    CeQueueDto dto = createCeQueue(uuid, status, ceQueueDtoConsumer);
-    db.getDbClient().ceQueueDao().insert(db.getSession(), dto);
-    db.getSession().commit();
-    return dto;
-  }
-
-  @NotNull
-  private static CeQueueDto createCeQueue(String uuid, CeQueueDto.Status status, @Nullable Consumer<CeQueueDto> ceQueueDtoConsumer) {
-    CeQueueDto dto = new CeQueueDto();
-    dto.setUuid(uuid);
-    dto.setTaskType(CeTaskTypes.REPORT);
-    dto.setStatus(status);
-    dto.setSubmitterUuid("henri");
-    dto.setComponentUuid(UUID.randomUUID().toString());
-    dto.setMainComponentUuid("1");
-    if (ceQueueDtoConsumer != null) {
-      ceQueueDtoConsumer.accept(dto);
-    }
-    return dto;
-  }
-
-  private void insertView(String view_uuid) {
-    ComponentDto view = new ComponentDto();
-    view.setQualifier("VW");
-    view.setKey(view_uuid + "_key");
-    view.setUuid(view_uuid);
-    view.setPrivate(false);
-    view.setUuidPath("uuid_path");
-    view.setBranchUuid(view_uuid);
-    db.components().insertPortfolioAndSnapshot(view);
-    db.commit();
-  }
-
-  private void insertBranch(String uuid) {
-    ComponentDto branch = new ComponentDto();
-    branch.setQualifier("TRK");
-    branch.setKey(uuid + "_key");
-    branch.setUuid(uuid);
-    branch.setPrivate(false);
-    branch.setUuidPath("uuid_path");
-    branch.setBranchUuid(uuid);
-    db.components().insertComponent(branch);
-    db.commit();
-  }
-}
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/taskprocessor/CeWorkerImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/taskprocessor/CeWorkerImplTest.java
deleted file mode 100644 (file)
index 9b55622..0000000
+++ /dev/null
@@ -1,788 +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.taskprocessor;
-
-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.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.InOrder;
-import org.mockito.stubbing.Answer;
-import org.sonar.api.impl.utils.TestSystem2;
-import org.sonar.api.utils.MessageException;
-import org.sonar.api.utils.System2;
-import org.sonar.api.utils.log.LogAndArguments;
-import org.sonar.api.utils.log.LogTester;
-import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.ce.queue.InternalCeQueue;
-import org.sonar.ce.task.CeTask;
-import org.sonar.ce.task.CeTaskResult;
-import org.sonar.ce.task.projectanalysis.taskprocessor.ReportTaskProcessor;
-import org.sonar.ce.task.taskprocessor.CeTaskProcessor;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.ce.CeActivityDto;
-import org.sonar.db.ce.CeTaskTypes;
-import org.sonar.db.user.UserDto;
-import org.sonar.db.user.UserTesting;
-
-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.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoInteractions;
-import static org.mockito.Mockito.when;
-import static org.sonar.ce.taskprocessor.CeWorker.Result.DISABLED;
-import static org.sonar.ce.taskprocessor.CeWorker.Result.NO_TASK;
-import static org.sonar.ce.taskprocessor.CeWorker.Result.TASK_PROCESSED;
-
-public class CeWorkerImplTest {
-
-  private System2 system2 = new TestSystem2().setNow(1_450_000_000_000L);
-
-  @Rule
-  public CeTaskProcessorRepositoryRule taskProcessorRepository = new CeTaskProcessorRepositoryRule();
-  @Rule
-  public LogTester logTester = new LogTester();
-  @Rule
-  public DbTester db = DbTester.create(system2);
-
-  private DbSession session = db.getSession();
-
-  private InternalCeQueue queue = mock(InternalCeQueue.class);
-  private ReportTaskProcessor taskProcessor = mock(ReportTaskProcessor.class);
-  private CeWorker.ExecutionListener executionListener1 = mock(CeWorker.ExecutionListener.class);
-  private CeWorker.ExecutionListener executionListener2 = mock(CeWorker.ExecutionListener.class);
-  private CeWorkerController ceWorkerController = mock(CeWorkerController.class);
-  private ArgumentCaptor<String> workerUuidCaptor = ArgumentCaptor.forClass(String.class);
-  private int randomOrdinal = new Random().nextInt(50);
-  private String workerUuid = UUID.randomUUID().toString();
-  private CeWorker underTest = new CeWorkerImpl(randomOrdinal, workerUuid, queue, taskProcessorRepository, ceWorkerController,
-    executionListener1, executionListener2);
-  private CeWorker underTestNoListener = new CeWorkerImpl(randomOrdinal, workerUuid, queue, taskProcessorRepository, ceWorkerController);
-  private InOrder inOrder = inOrder(taskProcessor, queue, executionListener1, executionListener2);
-  private final CeTask.User submitter = new CeTask.User("UUID_USER_1", "LOGIN_1");
-
-  @Before
-  public void setUp() {
-    when(ceWorkerController.isEnabled(any(CeWorker.class))).thenReturn(true);
-  }
-
-  @Test
-  public void constructor_throws_IAE_if_ordinal_is_less_than_zero() {
-    assertThatThrownBy(() -> new CeWorkerImpl(-1 - new Random().nextInt(20), workerUuid, queue, taskProcessorRepository, ceWorkerController))
-      .isInstanceOf(IllegalArgumentException.class)
-      .hasMessage("Ordinal must be >= 0");
-  }
-
-  @Test
-  public void getUUID_must_return_the_uuid_of_constructor() {
-    String uuid = UUID.randomUUID().toString();
-    CeWorker underTest = new CeWorkerImpl(randomOrdinal, uuid, queue, taskProcessorRepository, ceWorkerController);
-    assertThat(underTest.getUUID()).isEqualTo(uuid);
-  }
-
-  @Test
-  public void worker_disabled() throws Exception {
-    reset(ceWorkerController);
-    when(ceWorkerController.isEnabled(underTest)).thenReturn(false);
-
-    assertThat(underTest.call()).isEqualTo(DISABLED);
-
-    verifyNoInteractions(taskProcessor, executionListener1, executionListener2);
-  }
-
-  @Test
-  public void worker_disabled_no_listener() throws Exception {
-    reset(ceWorkerController);
-    when(ceWorkerController.isEnabled(underTest)).thenReturn(false);
-
-    assertThat(underTestNoListener.call()).isEqualTo(DISABLED);
-
-    verifyNoInteractions(taskProcessor, executionListener1, executionListener2);
-  }
-
-  @Test
-  public void no_pending_tasks_in_queue() throws Exception {
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.empty());
-
-    assertThat(underTest.call()).isEqualTo(NO_TASK);
-
-    verifyNoInteractions(taskProcessor, executionListener1, executionListener2);
-  }
-
-  @Test
-  public void no_pending_tasks_in_queue_without_listener() throws Exception {
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.empty());
-
-    assertThat(underTestNoListener.call()).isEqualTo(NO_TASK);
-
-    verifyNoInteractions(taskProcessor, executionListener1, executionListener2);
-  }
-
-  @Test
-  public void fail_when_no_CeTaskProcessor_is_found_in_repository() throws Exception {
-    CeTask task = createCeTask(null);
-    taskProcessorRepository.setNoProcessorForTask(CeTaskTypes.REPORT);
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(task));
-
-    assertThat(underTest.call()).isEqualTo(TASK_PROCESSED);
-
-    verifyWorkerUuid();
-    inOrder.verify(executionListener1).onStart(task);
-    inOrder.verify(executionListener2).onStart(task);
-    inOrder.verify(queue).remove(task, CeActivityDto.Status.FAILED, null, null);
-    inOrder.verify(executionListener1).onEnd(eq(task), eq(CeActivityDto.Status.FAILED), any(), isNull(), isNull());
-    inOrder.verify(executionListener2).onEnd(eq(task), eq(CeActivityDto.Status.FAILED), any(), isNull(), isNull());
-  }
-
-  @Test
-  public void fail_when_no_CeTaskProcessor_is_found_in_repository_without_listener() throws Exception {
-    CeTask task = createCeTask(null);
-    taskProcessorRepository.setNoProcessorForTask(CeTaskTypes.REPORT);
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(task));
-
-    assertThat(underTestNoListener.call()).isEqualTo(TASK_PROCESSED);
-
-    verifyWorkerUuid();
-    inOrder.verify(queue).remove(task, CeActivityDto.Status.FAILED, null, null);
-    inOrder.verifyNoMoreInteractions();
-  }
-
-  @Test
-  public void peek_and_process_task() throws Exception {
-    CeTask task = createCeTask(null);
-    taskProcessorRepository.setProcessorForTask(task.getType(), taskProcessor);
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(task));
-
-    assertThat(underTest.call()).isEqualTo(TASK_PROCESSED);
-
-    verifyWorkerUuid();
-    inOrder.verify(executionListener1).onStart(task);
-    inOrder.verify(executionListener2).onStart(task);
-    inOrder.verify(taskProcessor).process(task);
-    inOrder.verify(queue).remove(task, CeActivityDto.Status.SUCCESS, null, null);
-    inOrder.verify(executionListener1).onEnd(eq(task), eq(CeActivityDto.Status.SUCCESS), any(), isNull(), isNull());
-    inOrder.verify(executionListener2).onEnd(eq(task), eq(CeActivityDto.Status.SUCCESS), any(), isNull(), isNull());
-  }
-
-  @Test
-  public void peek_and_process_task_without_listeners() throws Exception {
-    CeTask task = createCeTask(null);
-    taskProcessorRepository.setProcessorForTask(task.getType(), taskProcessor);
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(task));
-
-    assertThat(underTestNoListener.call()).isEqualTo(TASK_PROCESSED);
-
-    verifyWorkerUuid();
-    inOrder.verify(taskProcessor).process(task);
-    inOrder.verify(queue).remove(task, CeActivityDto.Status.SUCCESS, null, null);
-    inOrder.verifyNoMoreInteractions();
-  }
-
-  @Test
-  public void fail_to_process_task() throws Exception {
-    CeTask task = createCeTask(null);
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(task));
-    taskProcessorRepository.setProcessorForTask(task.getType(), taskProcessor);
-    Throwable error = makeTaskProcessorFail(task);
-
-    assertThat(underTest.call()).isEqualTo(TASK_PROCESSED);
-
-    verifyWorkerUuid();
-    inOrder.verify(executionListener1).onStart(task);
-    inOrder.verify(executionListener2).onStart(task);
-    inOrder.verify(taskProcessor).process(task);
-    inOrder.verify(queue).remove(task, CeActivityDto.Status.FAILED, null, error);
-    inOrder.verify(executionListener1).onEnd(eq(task), eq(CeActivityDto.Status.FAILED), any(), isNull(), eq(error));
-    inOrder.verify(executionListener2).onEnd(eq(task), eq(CeActivityDto.Status.FAILED), any(), isNull(), eq(error));
-  }
-
-  @Test
-  public void fail_to_process_task_without_listeners() throws Exception {
-    CeTask task = createCeTask(null);
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(task));
-    taskProcessorRepository.setProcessorForTask(task.getType(), taskProcessor);
-    Throwable error = makeTaskProcessorFail(task);
-
-    assertThat(underTestNoListener.call()).isEqualTo(TASK_PROCESSED);
-
-    verifyWorkerUuid();
-    inOrder.verify(taskProcessor).process(task);
-    inOrder.verify(queue).remove(task, CeActivityDto.Status.FAILED, null, error);
-    inOrder.verifyNoMoreInteractions();
-  }
-
-  @Test
-  public void log_task_characteristics() throws Exception {
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(createCeTask(null, "pullRequest", "123", "branch", "foo")));
-    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
-
-    underTest.call();
-
-    List<String> logs = logTester.logs(LoggerLevel.INFO);
-    assertThat(logs).hasSize(2);
-    for (int i = 0; i < 2; i++) {
-      assertThat(logs.get(i)).contains("pullRequest=123");
-      assertThat(logs.get(i)).contains("branch=foo");
-    }
-  }
-
-  @Test
-  public void do_not_log_submitter_param_if_anonymous_and_success() throws Exception {
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(createCeTask(null)));
-    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
-
-    underTest.call();
-
-    verifyWorkerUuid();
-    List<String> logs = logTester.logs(LoggerLevel.INFO);
-    assertThat(logs).hasSize(2);
-    for (int i = 0; i < 2; i++) {
-      assertThat(logs.get(i)).doesNotContain("submitter=");
-    }
-  }
-
-  @Test
-  public void do_not_log_submitter_param_if_anonymous_and_error() throws Exception {
-    CeTask ceTask = createCeTask(null);
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(ceTask));
-    taskProcessorRepository.setProcessorForTask(ceTask.getType(), taskProcessor);
-    makeTaskProcessorFail(ceTask);
-
-    underTest.call();
-
-    verifyWorkerUuid();
-    List<String> logs = logTester.logs(LoggerLevel.INFO);
-    assertThat(logs).hasSize(2);
-    assertThat(logs.get(0)).doesNotContain("submitter=");
-    assertThat(logs.get(1)).doesNotContain("submitter=");
-    logs = logTester.logs(LoggerLevel.ERROR);
-    assertThat(logs).hasSize(1);
-    assertThat(logs.iterator().next()).doesNotContain("submitter=");
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).isEmpty();
-  }
-
-  @Test
-  public void log_submitter_login_if_authenticated_and_success() throws Exception {
-    UserDto userDto = insertRandomUser();
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(createCeTask(toTaskSubmitter(userDto))));
-    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
-
-    underTest.call();
-
-    verifyWorkerUuid();
-    List<String> logs = logTester.logs(LoggerLevel.INFO);
-    assertThat(logs).hasSize(2);
-    assertThat(logs.get(0)).contains(String.format("submitter=%s", userDto.getLogin()));
-    assertThat(logs.get(1)).contains(String.format("submitter=%s | status=SUCCESS | time=", userDto.getLogin()));
-    assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).isEmpty();
-  }
-
-  @Test
-  public void log_submitterUuid_if_user_matching_submitterUuid_can_not_be_found() throws Exception {
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(createCeTask(new CeTask.User("UUID_USER", null))));
-    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
-
-    underTest.call();
-
-    verifyWorkerUuid();
-    List<String> logs = logTester.logs(LoggerLevel.INFO);
-    assertThat(logs).hasSize(2);
-    assertThat(logs.get(0)).contains("submitter=UUID_USER");
-    assertThat(logs.get(1)).contains("submitter=UUID_USER | status=SUCCESS | time=");
-    assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).isEmpty();
-  }
-
-  @Test
-  public void display_submitterLogin_in_logs_when_set_in_case_of_error() throws Exception {
-    UserDto userDto = insertRandomUser();
-    CeTask ceTask = createCeTask(toTaskSubmitter(userDto));
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(ceTask));
-    taskProcessorRepository.setProcessorForTask(ceTask.getType(), taskProcessor);
-    makeTaskProcessorFail(ceTask);
-
-    underTest.call();
-
-    verifyWorkerUuid();
-    List<String> logs = logTester.logs(LoggerLevel.INFO);
-    assertThat(logs).hasSize(2);
-    assertThat(logs.get(0)).contains(String.format("submitter=%s", userDto.getLogin()));
-    assertThat(logs.get(1)).contains(String.format("submitter=%s | status=FAILED | time=", userDto.getLogin()));
-    logs = logTester.logs(LoggerLevel.ERROR);
-    assertThat(logs).hasSize(1);
-    assertThat(logs.get(0)).isEqualTo("Failed to execute task " + ceTask.getUuid());
-  }
-
-  @Test
-  public void display_start_stop_at_debug_level_for_console_if_DEBUG_is_enabled_and_task_successful() throws Exception {
-    logTester.setLevel(LoggerLevel.DEBUG);
-
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(createCeTask(submitter)));
-    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
-
-    underTest.call();
-
-    verifyWorkerUuid();
-    List<String> logs = logTester.logs(LoggerLevel.INFO);
-    assertThat(logs).hasSize(2);
-    assertThat(logs.get(0)).contains(" | submitter=" + submitter.login());
-    assertThat(logs.get(1)).contains(String.format(" | submitter=%s | status=SUCCESS | time=", submitter.login()));
-    assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).isEmpty();
-  }
-
-  @Test
-  public void display_start_at_debug_level_stop_at_error_level_for_console_if_DEBUG_is_enabled_and_task_failed() throws Exception {
-    logTester.setLevel(LoggerLevel.DEBUG);
-
-    CeTask ceTask = createCeTask(submitter);
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(ceTask));
-    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
-    makeTaskProcessorFail(ceTask);
-
-    underTest.call();
-
-    verifyWorkerUuid();
-    List<String> logs = logTester.logs(LoggerLevel.INFO);
-    assertThat(logs).hasSize(2);
-    assertThat(logs.get(0)).contains(" | submitter=" + submitter.login());
-    assertThat(logs.get(1)).contains(String.format(" | submitter=%s | status=FAILED | time=", submitter.login()));
-    logs = logTester.logs(LoggerLevel.ERROR);
-    assertThat(logs).hasSize(1);
-    assertThat(logs.iterator().next()).isEqualTo("Failed to execute task " + ceTask.getUuid());
-    assertThat(logTester.logs(LoggerLevel.DEBUG)).isEmpty();
-  }
-
-  @Test
-  public void call_sets_and_restores_thread_name_with_information_of_worker_when_there_is_no_task_to_process() throws Exception {
-    String threadName = randomAlphabetic(3);
-    when(queue.peek(anyString(), anyBoolean())).thenAnswer(invocation -> {
-      assertThat(Thread.currentThread().getName())
-        .isEqualTo("Worker " + randomOrdinal + " (UUID=" + workerUuid + ") on " + threadName);
-      return Optional.empty();
-    });
-    Thread newThread = createThreadNameVerifyingThread(threadName);
-
-    newThread.start();
-    newThread.join();
-  }
-
-  @Test
-  public void call_sets_and_restores_thread_name_with_information_of_worker_when_a_task_is_processed() throws Exception {
-    String threadName = randomAlphabetic(3);
-    when(queue.peek(anyString(), anyBoolean())).thenAnswer(invocation -> {
-      assertThat(Thread.currentThread().getName())
-        .isEqualTo("Worker " + randomOrdinal + " (UUID=" + workerUuid + ") on " + threadName);
-      return Optional.of(createCeTask(submitter));
-    });
-    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
-    Thread newThread = createThreadNameVerifyingThread(threadName);
-
-    newThread.start();
-    newThread.join();
-  }
-
-  @Test
-  public void call_sets_and_restores_thread_name_with_information_of_worker_when_an_error_occurs() throws Exception {
-    String threadName = randomAlphabetic(3);
-    CeTask ceTask = createCeTask(submitter);
-    when(queue.peek(anyString(), anyBoolean())).thenAnswer(invocation -> {
-      assertThat(Thread.currentThread().getName())
-        .isEqualTo("Worker " + randomOrdinal + " (UUID=" + workerUuid + ") on " + threadName);
-      return Optional.of(ceTask);
-    });
-    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
-    makeTaskProcessorFail(ceTask);
-    Thread newThread = createThreadNameVerifyingThread(threadName);
-
-    newThread.start();
-    newThread.join();
-  }
-
-  @Test
-  public void call_sets_and_restores_thread_name_with_information_of_worker_when_worker_is_disabled() throws Exception {
-    reset(ceWorkerController);
-    when(ceWorkerController.isEnabled(underTest)).thenReturn(false);
-
-    String threadName = randomAlphabetic(3);
-    Thread newThread = createThreadNameVerifyingThread(threadName);
-
-    newThread.start();
-    newThread.join();
-  }
-
-  @Test
-  public void log_error_when_task_fails_with_not_MessageException() throws Exception {
-    CeTask ceTask = createCeTask(submitter);
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(ceTask));
-    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
-    makeTaskProcessorFail(ceTask);
-
-    underTest.call();
-
-    List<String> logs = logTester.logs(LoggerLevel.INFO);
-    assertThat(logs).hasSize(2);
-    assertThat(logs.get(0)).contains(" | submitter=" + submitter.login());
-    assertThat(logs.get(1)).contains(String.format(" | submitter=%s | status=FAILED | time=", submitter.login()));
-    logs = logTester.logs(LoggerLevel.ERROR);
-    assertThat(logs).hasSize(1);
-    assertThat(logs.iterator().next()).isEqualTo("Failed to execute task " + ceTask.getUuid());
-  }
-
-  @Test
-  public void do_no_log_error_when_task_fails_with_MessageException() throws Exception {
-    CeTask ceTask = createCeTask(submitter);
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(ceTask));
-    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
-    makeTaskProcessorFail(ceTask, MessageException.of("simulate MessageException thrown by TaskProcessor#process"));
-
-    underTest.call();
-
-    List<String> logs = logTester.logs(LoggerLevel.INFO);
-    assertThat(logs).hasSize(2);
-    assertThat(logs.get(1)).contains(" | submitter=" + submitter.login());
-    assertThat(logs.get(1)).contains(String.format(" | submitter=%s | status=FAILED | time=", submitter.login()));
-    assertThat(logTester.logs(LoggerLevel.ERROR)).isEmpty();
-  }
-
-  @Test
-  public void log_error_when_task_was_successful_but_ending_state_can_not_be_persisted_to_db() throws Exception {
-    CeTask ceTask = createCeTask(submitter);
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(ceTask));
-    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
-    doThrow(new RuntimeException("Simulate queue#remove failing")).when(queue).remove(ceTask, CeActivityDto.Status.SUCCESS, null, null);
-
-    underTest.call();
-
-    assertThat(logTester.logs(LoggerLevel.ERROR)).containsOnly("Failed to finalize task with uuid '" + ceTask.getUuid() + "' and persist its state to db");
-  }
-
-  @Test
-  public void log_error_when_task_failed_and_ending_state_can_not_be_persisted_to_db() throws Exception {
-    CeTask ceTask = createCeTask(submitter);
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(ceTask));
-    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
-    IllegalStateException ex = makeTaskProcessorFail(ceTask);
-    RuntimeException runtimeException = new RuntimeException("Simulate queue#remove failing");
-    doThrow(runtimeException).when(queue).remove(ceTask, CeActivityDto.Status.FAILED, null, ex);
-
-    underTest.call();
-
-    List<String> logs = logTester.logs(LoggerLevel.INFO);
-    assertThat(logs).hasSize(2);
-    assertThat(logs.get(0)).contains(" | submitter=" + submitter.login());
-    assertThat(logs.get(1)).contains(String.format(" | submitter=%s | status=FAILED | time=", submitter.login()));
-    List<LogAndArguments> logAndArguments = logTester.getLogs(LoggerLevel.ERROR);
-    assertThat(logAndArguments).hasSize(2);
-
-    LogAndArguments executionErrorLog = logAndArguments.get(0);
-    assertThat(executionErrorLog.getFormattedMsg()).isEqualTo("Failed to execute task " + ceTask.getUuid());
-    assertThat(executionErrorLog.getArgs().get()).containsOnly(ceTask.getUuid(), ex);
-
-    LogAndArguments finalizingErrorLog = logAndArguments.get(1);
-    assertThat(finalizingErrorLog.getFormattedMsg()).isEqualTo("Failed to finalize task with uuid '" + ceTask.getUuid() + "' and persist its state to db");
-    Object arg1 = finalizingErrorLog.getArgs().get()[0];
-    assertThat(arg1).isSameAs(runtimeException);
-    assertThat(((Exception) arg1).getSuppressed()).containsOnly(ex);
-  }
-
-  @Test
-  public void log_error_as_suppressed_when_task_failed_with_MessageException_and_ending_state_can_not_be_persisted_to_db() throws Exception {
-    CeTask ceTask = createCeTask(submitter);
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(ceTask));
-    taskProcessorRepository.setProcessorForTask(CeTaskTypes.REPORT, taskProcessor);
-    MessageException ex = makeTaskProcessorFail(ceTask, MessageException.of("simulate MessageException thrown by TaskProcessor#process"));
-    RuntimeException runtimeException = new RuntimeException("Simulate queue#remove failing");
-    doThrow(runtimeException).when(queue).remove(ceTask, CeActivityDto.Status.FAILED, null, ex);
-
-    underTest.call();
-
-    List<String> logs = logTester.logs(LoggerLevel.INFO);
-    assertThat(logs).hasSize(2);
-    assertThat(logs.get(0)).contains(" | submitter=" + submitter.login());
-    assertThat(logs.get(1)).contains(String.format(" | submitter=%s | status=FAILED | time=", submitter.login()));
-    List<LogAndArguments> logAndArguments = logTester.getLogs(LoggerLevel.ERROR);
-    assertThat(logAndArguments).hasSize(1);
-    assertThat(logAndArguments.get(0).getFormattedMsg()).isEqualTo("Failed to finalize task with uuid '" + ceTask.getUuid() + "' and persist its state to db");
-    Object arg1 = logAndArguments.get(0).getArgs().get()[0];
-    assertThat(arg1).isSameAs(runtimeException);
-    assertThat(((Exception) arg1).getSuppressed()).containsOnly(ex);
-  }
-
-  @Test
-  public void isExecutedBy_returns_false_when_no_interaction_with_instance() {
-    assertThat(underTest.isExecutedBy(Thread.currentThread())).isFalse();
-    assertThat(underTest.isExecutedBy(new Thread())).isFalse();
-  }
-
-  @Test
-  public void isExecutedBy_returns_false_unless_a_thread_is_currently_calling_call() throws InterruptedException {
-    CountDownLatch inCallLatch = new CountDownLatch(1);
-    CountDownLatch assertionsDoneLatch = new CountDownLatch(1);
-    // mock long running peek(String) call => Thread is executing call() but not running a task
-    when(queue.peek(anyString(), anyBoolean())).thenAnswer((Answer<Optional<CeTask>>) invocation -> {
-      inCallLatch.countDown();
-      try {
-        assertionsDoneLatch.await(10, TimeUnit.SECONDS);
-      } catch (InterruptedException e) {
-        throw new RuntimeException(e);
-      }
-      return Optional.empty();
-    });
-    Thread t = callCallInNewThread(underTest);
-
-    try {
-      t.start();
-
-      inCallLatch.await(10, TimeUnit.SECONDS);
-      assertThat(underTest.isExecutedBy(Thread.currentThread())).isFalse();
-      assertThat(underTest.isExecutedBy(new Thread())).isFalse();
-      assertThat(underTest.isExecutedBy(t)).isTrue();
-    } finally {
-      assertionsDoneLatch.countDown();
-      t.join();
-    }
-
-    assertThat(underTest.isExecutedBy(Thread.currentThread())).isFalse();
-    assertThat(underTest.isExecutedBy(new Thread())).isFalse();
-    assertThat(underTest.isExecutedBy(t)).isFalse();
-  }
-
-  @Test
-  public void isExecutedBy_returns_false_unless_a_thread_is_currently_executing_a_task() throws InterruptedException {
-    CountDownLatch inCallLatch = new CountDownLatch(1);
-    CountDownLatch assertionsDoneLatch = new CountDownLatch(1);
-    String taskType = randomAlphabetic(12);
-    CeTask ceTask = mock(CeTask.class);
-    when(ceTask.getType()).thenReturn(taskType);
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(ceTask));
-    taskProcessorRepository.setProcessorForTask(taskType, new SimpleCeTaskProcessor() {
-      @CheckForNull
-      @Override
-      public CeTaskResult process(CeTask task) {
-        inCallLatch.countDown();
-        try {
-          assertionsDoneLatch.await(10, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-          throw new RuntimeException(e);
-        }
-        return null;
-      }
-    });
-    Thread t = callCallInNewThread(underTest);
-
-    try {
-      t.start();
-
-      inCallLatch.await(10, TimeUnit.SECONDS);
-      assertThat(underTest.isExecutedBy(Thread.currentThread())).isFalse();
-      assertThat(underTest.isExecutedBy(new Thread())).isFalse();
-      assertThat(underTest.isExecutedBy(t)).isTrue();
-    } finally {
-      assertionsDoneLatch.countDown();
-      t.join();
-    }
-
-    assertThat(underTest.isExecutedBy(Thread.currentThread())).isFalse();
-    assertThat(underTest.isExecutedBy(new Thread())).isFalse();
-    assertThat(underTest.isExecutedBy(t)).isFalse();
-  }
-
-  @Test
-  public void getCurrentTask_returns_empty_when_no_interaction_with_instance() {
-    assertThat(underTest.getCurrentTask()).isEmpty();
-  }
-
-  @Test
-  public void do_not_exclude_portfolio_when_indexation_task_lookup_is_disabled() throws Exception {
-    // first call with empty queue to disable indexationTaskLookupEnabled
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.empty());
-    assertThat(underTest.call()).isEqualTo(NO_TASK);
-
-    // following calls should not exclude portfolios
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(createCeTask(submitter)));
-    assertThat(underTest.call()).isEqualTo(TASK_PROCESSED);
-    verify(queue, times(2)).peek(anyString(), anyBoolean());
-  }
-
-  @Test
-  public void getCurrentTask_returns_empty_when_a_thread_is_currently_calling_call_but_not_executing_a_task() throws InterruptedException {
-    CountDownLatch inCallLatch = new CountDownLatch(1);
-    CountDownLatch assertionsDoneLatch = new CountDownLatch(1);
-    // mock long running peek(String) call => Thread is executing call() but not running a task
-    when(queue.peek(anyString(), anyBoolean())).thenAnswer((Answer<Optional<CeTask>>) invocation -> {
-      inCallLatch.countDown();
-      try {
-        assertionsDoneLatch.await(10, TimeUnit.SECONDS);
-      } catch (InterruptedException e) {
-        throw new RuntimeException(e);
-      }
-      return Optional.empty();
-    });
-    Thread t = callCallInNewThread(underTest);
-
-    try {
-      t.start();
-
-      inCallLatch.await(10, TimeUnit.SECONDS);
-      assertThat(underTest.getCurrentTask()).isEmpty();
-    } finally {
-      assertionsDoneLatch.countDown();
-      t.join();
-    }
-
-    assertThat(underTest.getCurrentTask()).isEmpty();
-  }
-
-  @Test
-  public void getCurrentTask_returns_empty_unless_a_thread_is_currently_executing_a_task() throws InterruptedException {
-    CountDownLatch inCallLatch = new CountDownLatch(1);
-    CountDownLatch assertionsDoneLatch = new CountDownLatch(1);
-    String taskType = randomAlphabetic(12);
-    CeTask ceTask = mock(CeTask.class);
-    when(ceTask.getType()).thenReturn(taskType);
-    when(queue.peek(anyString(), anyBoolean())).thenReturn(Optional.of(ceTask));
-    taskProcessorRepository.setProcessorForTask(taskType, new SimpleCeTaskProcessor() {
-
-      @CheckForNull
-      @Override
-      public CeTaskResult process(CeTask task) {
-        inCallLatch.countDown();
-        try {
-          assertionsDoneLatch.await(10, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-          throw new RuntimeException(e);
-        }
-        return null;
-      }
-    });
-    Thread t = callCallInNewThread(underTest);
-
-    try {
-      t.start();
-
-      inCallLatch.await(10, TimeUnit.SECONDS);
-      assertThat(underTest.getCurrentTask()).contains(ceTask);
-    } finally {
-      assertionsDoneLatch.countDown();
-      t.join();
-    }
-
-    assertThat(underTest.getCurrentTask()).isEmpty();
-  }
-
-  private Thread callCallInNewThread(CeWorker underTest) {
-    return new Thread(() -> {
-      try {
-        underTest.call();
-      } catch (Exception e) {
-        throw new RuntimeException("call to call() failed and this is unexpected. Fix the UT.", e);
-      }
-    });
-  }
-
-  private Thread createThreadNameVerifyingThread(String threadName) {
-    return new Thread(() -> {
-      verifyUnchangedThreadName(threadName);
-      try {
-        underTest.call();
-      } catch (Exception e) {
-        throw new RuntimeException(e);
-      }
-      verifyUnchangedThreadName(threadName);
-    }, threadName);
-  }
-
-  private void verifyUnchangedThreadName(String threadName) {
-    assertThat(Thread.currentThread().getName()).isEqualTo(threadName);
-  }
-
-  private void verifyWorkerUuid() {
-    verify(queue, atLeastOnce()).peek(workerUuidCaptor.capture(), anyBoolean());
-    assertThat(workerUuidCaptor.getValue()).isEqualTo(workerUuid);
-  }
-
-  private static CeTask createCeTask(@Nullable CeTask.User submitter, String... characteristics) {
-    Map<String, String> characteristicMap = new HashMap<>();
-    for (int i = 0; i < characteristics.length; i += 2) {
-      characteristicMap.put(characteristics[i], characteristics[i + 1]);
-    }
-    CeTask.Component component = new CeTask.Component("PROJECT_1", null, null);
-    return new CeTask.Builder()
-      .setUuid("TASK_1").setType(CeTaskTypes.REPORT)
-      .setComponent(component)
-      .setMainComponent(component)
-      .setSubmitter(submitter)
-      .setCharacteristics(characteristicMap)
-      .build();
-  }
-
-  private UserDto insertRandomUser() {
-    UserDto userDto = UserTesting.newUserDto();
-    db.getDbClient().userDao().insert(session, userDto);
-    session.commit();
-    return userDto;
-  }
-
-  private CeTask.User toTaskSubmitter(UserDto userDto) {
-    return new CeTask.User(userDto.getUuid(), userDto.getLogin());
-  }
-
-  private IllegalStateException makeTaskProcessorFail(CeTask task) {
-    return makeTaskProcessorFail(task, new IllegalStateException("simulate exception thrown by TaskProcessor#process"));
-  }
-
-  private <T extends Throwable> T makeTaskProcessorFail(CeTask task, T t) {
-    doThrow(t).when(taskProcessor).process(task);
-    return t;
-  }
-
-  private static abstract class SimpleCeTaskProcessor implements CeTaskProcessor {
-    @Override
-    public Set<String> getHandledCeTaskTypes() {
-      throw new UnsupportedOperationException("getHandledCeTaskTypes should not be called");
-    }
-  }
-}