aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStepIT.java2
-rw-r--r--server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistReferenceBranchPeriodStepIT.java112
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/PeriodHolder.java6
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/PeriodHolderImpl.java10
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/PeriodOrigin.java25
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStep.java8
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistReferenceBranchPeriodStep.java113
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java1
-rw-r--r--server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/period/PeriodHolderRule.java10
-rw-r--r--server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistReferenceBranchPeriodStepTest.java194
10 files changed, 475 insertions, 6 deletions
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
index b0aed6b3f10..d0c0c7c8c3d 100644
--- 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
@@ -62,7 +62,6 @@ import static org.apache.commons.lang3.RandomStringUtils.secure;
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;
@@ -198,7 +197,6 @@ public class LoadPeriodsStepIT extends BaseStepTest {
underTest.execute(new TestComputationStepContext());
assertPeriod(NewCodePeriodType.REFERENCE_BRANCH, newCodeReferenceBranch, null);
- verify(ceTaskMessages).add(any(CeTaskMessages.Message.class));
}
@Test
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistReferenceBranchPeriodStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistReferenceBranchPeriodStepIT.java
new file mode 100644
index 00000000000..0ede18e3ccf
--- /dev/null
+++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistReferenceBranchPeriodStepIT.java
@@ -0,0 +1,112 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2025 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.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.ce.task.projectanalysis.analysis.TestBranch;
+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.projectanalysis.period.PeriodOrigin;
+import org.sonar.ce.task.step.ComputationStep.Context;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchDto;
+import org.sonar.db.component.ProjectData;
+import org.sonar.db.newcodeperiod.NewCodePeriodDto;
+import org.sonar.server.project.Project;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+import static org.mockito.Mockito.mock;
+import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH;
+
+class PersistReferenceBranchPeriodStepIT {
+
+ private static final String BRANCH_NAME = "feature";
+
+ @RegisterExtension
+ private final PeriodHolderRule periodHolder = new PeriodHolderRule();
+
+ @RegisterExtension
+ private final AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
+
+ @RegisterExtension
+ private final TreeRootHolderRule treeRootHolderRule = new TreeRootHolderRule();
+
+ @RegisterExtension
+ private final DbTester db = DbTester.create();
+
+ private final PersistReferenceBranchPeriodStep persistReferenceBranchPeriodStep = new PersistReferenceBranchPeriodStep(
+ periodHolder, analysisMetadataHolder, db.getDbClient(), treeRootHolderRule);
+
+ private ProjectData projectData;
+ private String branchUuid;
+
+ @BeforeEach
+ void setUp() {
+ projectData = db.components().insertPrivateProject();
+ BranchDto branchDto = db.components().insertProjectBranch(projectData.getProjectDto(), branch -> branch.setKey(BRANCH_NAME));
+ branchUuid = branchDto.getUuid();
+
+ analysisMetadataHolder.setProject(new Project(projectData.projectUuid(), projectData.projectKey(), projectData.projectKey(), null, List.of()));
+ analysisMetadataHolder.setBranch(new TestBranch(BRANCH_NAME));
+ periodHolder.setPeriod(new Period(REFERENCE_BRANCH.name(), "main", null));
+ periodHolder.setPeriodOrigin(PeriodOrigin.SCANNER);
+
+ ReportComponent reportComponent = ReportComponent
+ .builder(Component.Type.PROJECT, 1)
+ .setUuid(branchUuid)
+ .setKey(branchDto.getKey())
+ .build();
+ treeRootHolderRule.setRoot(reportComponent);
+ }
+
+ @Test
+ void execute_shouldPersistReferenceBranchPeriod() {
+
+ persistReferenceBranchPeriodStep.execute(mock(Context.class));
+
+ NewCodePeriodDto newCodePeriodDto = db.getDbClient().newCodePeriodDao().selectByBranch(db.getSession(), projectData.projectUuid(), branchUuid)
+ .orElseGet(() -> fail("No new code period found for branch"));
+ assertThat(newCodePeriodDto.getBranchUuid()).isEqualTo(branchUuid);
+ assertThat(newCodePeriodDto.getType()).isEqualTo(REFERENCE_BRANCH);
+ assertThat(newCodePeriodDto.getValue()).isEqualTo("main");
+ }
+
+ @Test
+ void execute_shouldUpdateReferenceBranchPeriod() {
+ db.newCodePeriods().insert(projectData.projectUuid(), branchUuid, REFERENCE_BRANCH, "old_value");
+
+ persistReferenceBranchPeriodStep.execute(mock(Context.class));
+
+ NewCodePeriodDto newCodePeriodDto = db.getDbClient().newCodePeriodDao().selectByBranch(db.getSession(), projectData.projectUuid(), branchUuid)
+ .orElseGet(() -> fail("No new code period found for branch"));
+ assertThat(newCodePeriodDto.getBranchUuid()).isEqualTo(branchUuid);
+ assertThat(newCodePeriodDto.getType()).isEqualTo(REFERENCE_BRANCH);
+ assertThat(newCodePeriodDto.getValue()).isEqualTo("main");
+ }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/PeriodHolder.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/PeriodHolder.java
index 1705c9fb3bb..9fe7dadb9f5 100644
--- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/PeriodHolder.java
+++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/PeriodHolder.java
@@ -51,4 +51,10 @@ public interface PeriodHolder {
*/
Period getPeriod();
+ /**
+ * Retrieve the context from which this period is coming from. For example, it can be coming from a scanner parameter, a global setting, etc.
+ * See {@link PeriodOrigin} for the possible values.
+ */
+ PeriodOrigin getPeriodOrigin();
+
}
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/PeriodHolderImpl.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/PeriodHolderImpl.java
index 86ea07a18e2..2150f3f5539 100644
--- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/PeriodHolderImpl.java
+++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/PeriodHolderImpl.java
@@ -29,6 +29,7 @@ public class PeriodHolderImpl implements PeriodHolder {
@CheckForNull
private Period period = null;
private boolean initialized = false;
+ private PeriodOrigin periodOrigin = null;
/**
* Initializes the periods in the holder.
@@ -41,6 +42,10 @@ public class PeriodHolderImpl implements PeriodHolder {
this.initialized = true;
}
+ public void setPeriodOrigin(PeriodOrigin periodOrigin) {
+ this.periodOrigin = periodOrigin;
+ }
+
@Override
public boolean hasPeriod() {
checkHolderIsInitialized();
@@ -60,6 +65,11 @@ public class PeriodHolderImpl implements PeriodHolder {
return period;
}
+ @Override
+ public PeriodOrigin getPeriodOrigin() {
+ return periodOrigin;
+ }
+
private void checkHolderIsInitialized() {
checkState(initialized, "Period have not been initialized yet");
}
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/PeriodOrigin.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/PeriodOrigin.java
new file mode 100644
index 00000000000..6e2847fe480
--- /dev/null
+++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/period/PeriodOrigin.java
@@ -0,0 +1,25 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2025 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;
+
+public enum PeriodOrigin {
+ SCANNER,
+ SETTINGS
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStep.java
index 8d9026c42af..45831d3bc6f 100644
--- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStep.java
+++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadPeriodsStep.java
@@ -22,13 +22,13 @@ package org.sonar.ce.task.projectanalysis.step;
import java.util.Optional;
import org.sonar.api.utils.System2;
import org.sonar.ce.task.log.CeTaskMessages;
-import org.sonar.ce.task.log.CeTaskMessages.Message;
import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
import org.sonar.ce.task.projectanalysis.period.NewCodePeriodResolver;
import org.sonar.ce.task.projectanalysis.period.Period;
import org.sonar.ce.task.projectanalysis.period.PeriodHolder;
import org.sonar.ce.task.projectanalysis.period.PeriodHolderImpl;
+import org.sonar.ce.task.projectanalysis.period.PeriodOrigin;
import org.sonar.ce.task.step.ComputationStep;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
@@ -89,6 +89,8 @@ public class LoadPeriodsStep implements ComputationStep {
.map(b -> new NewCodePeriodDto().setType(REFERENCE_BRANCH).setValue(b))
.orElse(null);
+ PeriodOrigin periodOrigin = newCodePeriod == null ? PeriodOrigin.SETTINGS : PeriodOrigin.SCANNER;
+
try (DbSession dbSession = dbClient.openSession(false)) {
Optional<NewCodePeriodDto> branchSpecificSetting = getBranchSetting(dbSession, projectUuid, branchUuid);
@@ -102,13 +104,11 @@ public class LoadPeriodsStep implements ComputationStep {
periodsHolder.setPeriod(null);
return;
}
- } else if (branchSpecificSetting.isPresent()) {
- ceTaskMessages.add(new Message("A scanner parameter is defining a new code reference branch, but this conflicts with the New Code Period"
- + " setting of your branch. Please check your project configuration. You should use either one or the other but not both.", system2.now()));
}
Period period = resolver.resolve(dbSession, branchUuid, newCodePeriod, projectVersion);
periodsHolder.setPeriod(period);
+ periodsHolder.setPeriodOrigin(periodOrigin);
}
}
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistReferenceBranchPeriodStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistReferenceBranchPeriodStep.java
new file mode 100644
index 00000000000..96f471946ae
--- /dev/null
+++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/PersistReferenceBranchPeriodStep.java
@@ -0,0 +1,113 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2025 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.Objects;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
+import org.sonar.ce.task.projectanalysis.period.PeriodHolder;
+import org.sonar.ce.task.projectanalysis.period.PeriodOrigin;
+import org.sonar.ce.task.step.ComputationStep;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.newcodeperiod.NewCodePeriodDto;
+import org.sonar.db.newcodeperiod.NewCodePeriodType;
+
+public class PersistReferenceBranchPeriodStep implements ComputationStep {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(PersistReferenceBranchPeriodStep.class);
+
+ private final PeriodHolder periodHolder;
+ private final AnalysisMetadataHolder analysisMetadataHolder;
+ private final DbClient dbClient;
+ private final TreeRootHolder treeRootHolder;
+
+ public PersistReferenceBranchPeriodStep(PeriodHolder periodHolder, AnalysisMetadataHolder analysisMetadataHolder, DbClient dbClient, TreeRootHolder treeRootHolder) {
+ this.periodHolder = periodHolder;
+ this.analysisMetadataHolder = analysisMetadataHolder;
+ this.dbClient = dbClient;
+ this.treeRootHolder = treeRootHolder;
+ }
+
+ @Override
+ public String getDescription() {
+ return "Persist or update reference branch new code period";
+ }
+
+ @Override
+ public void execute(Context context) {
+ if (shouldExecute()) {
+ executePersistPeriodStep();
+ }
+ }
+
+ private boolean shouldExecute() {
+ return analysisMetadataHolder.isBranch() && periodHolder.hasPeriod()
+ && periodHolder.getPeriodOrigin() == PeriodOrigin.SCANNER;
+ }
+
+ void executePersistPeriodStep() {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ String projectUuid = analysisMetadataHolder.getProject().getUuid();
+ String branchUuid = treeRootHolder.getRoot().getUuid();
+
+ dbClient.newCodePeriodDao()
+ .selectByBranch(dbSession, projectUuid, branchUuid)
+ .ifPresentOrElse(
+ existingNewCodePeriod -> updateNewCodePeriodIfNeeded(dbSession, existingNewCodePeriod),
+ () -> createNewCodePeriod(dbSession, branchUuid)
+ );
+ }
+ }
+
+ private void updateNewCodePeriodIfNeeded(DbSession dbSession, NewCodePeriodDto newCodePeriodDto) {
+ if (shouldUpdateNewCodePeriod(newCodePeriodDto)) {
+ LOGGER.debug("Updating reference branch new code period '{}' for project '{}' and branch '{}'",
+ periodHolder.getPeriod().getModeParameter(), analysisMetadataHolder.getProject().getName(), analysisMetadataHolder.getBranch().getName());
+ newCodePeriodDto.setValue(periodHolder.getPeriod().getModeParameter());
+ newCodePeriodDto.setType(NewCodePeriodType.REFERENCE_BRANCH);
+ dbClient.newCodePeriodDao().update(dbSession, newCodePeriodDto);
+ dbSession.commit();
+ }
+ }
+
+ private boolean shouldUpdateNewCodePeriod(NewCodePeriodDto existingNewCodePeriod) {
+ return existingNewCodePeriod.getType() != NewCodePeriodType.REFERENCE_BRANCH
+ || !Objects.equals(existingNewCodePeriod.getValue(), periodHolder.getPeriod().getModeParameter());
+ }
+
+ private void createNewCodePeriod(DbSession dbSession, String branchUuid) {
+ LOGGER.debug("Persisting reference branch new code period '{}' for project '{}' and branch '{}'",
+ periodHolder.getPeriod().getModeParameter(), analysisMetadataHolder.getProject().getName(), analysisMetadataHolder.getBranch().getName());
+ dbClient.newCodePeriodDao().insert(dbSession, buildNewCodePeriodDto(branchUuid));
+ dbSession.commit();
+ }
+
+ private NewCodePeriodDto buildNewCodePeriodDto(String branchUuid) {
+ return new NewCodePeriodDto()
+ .setProjectUuid(analysisMetadataHolder.getProject().getUuid())
+ .setBranchUuid(branchUuid)
+ .setType(NewCodePeriodType.REFERENCE_BRANCH)
+ .setValue(periodHolder.getPeriod().getModeParameter());
+ }
+
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java
index 8cbcbbf421a..b121df6e657 100644
--- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java
+++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/ReportComputationSteps.java
@@ -106,6 +106,7 @@ public class ReportComputationSteps extends AbstractComputationSteps {
// Persist data
PersistScannerAnalysisCacheStep.class,
PersistComponentsStep.class,
+ PersistReferenceBranchPeriodStep.class,
PersistAnalysisStep.class,
PersistAnalysisPropertiesStep.class,
PersistProjectMeasuresStep.class,
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/period/PeriodHolderRule.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/period/PeriodHolderRule.java
index f47424267a9..7c9b454e8c2 100644
--- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/period/PeriodHolderRule.java
+++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/period/PeriodHolderRule.java
@@ -73,4 +73,14 @@ public class PeriodHolderRule implements TestRule, PeriodHolder, AfterEachCallba
return delegate.getPeriod();
}
+ @Override
+ public PeriodOrigin getPeriodOrigin() {
+ return delegate.getPeriodOrigin();
+ }
+
+ public PeriodHolderRule setPeriodOrigin(PeriodOrigin periodOrigin) {
+ delegate.setPeriodOrigin(periodOrigin);
+ return this;
+ }
+
}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistReferenceBranchPeriodStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistReferenceBranchPeriodStepTest.java
new file mode 100644
index 00000000000..84c3cc6d42d
--- /dev/null
+++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistReferenceBranchPeriodStepTest.java
@@ -0,0 +1,194 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2025 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.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.mockito.ArgumentCaptor;
+import org.slf4j.event.Level;
+import org.sonar.api.testfixtures.log.LogTesterJUnit5;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
+import org.sonar.ce.task.projectanalysis.analysis.TestBranch;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
+import org.sonar.ce.task.projectanalysis.period.Period;
+import org.sonar.ce.task.projectanalysis.period.PeriodHolder;
+import org.sonar.ce.task.projectanalysis.period.PeriodOrigin;
+import org.sonar.ce.task.step.ComputationStep;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.newcodeperiod.NewCodePeriodDao;
+import org.sonar.db.newcodeperiod.NewCodePeriodDto;
+import org.sonar.db.newcodeperiod.NewCodePeriodType;
+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.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.slf4j.event.Level.DEBUG;
+
+class PersistReferenceBranchPeriodStepTest {
+
+ private static final String MAIN_BRANCH = "main";
+ private static final String FEATURE_BRANCH = "feature";
+ private static final String BRANCH_UUID = "branch-uuid";
+ private static final String PROJECT_NAME = "project-name";
+ private static final String PROJECT_UUID = "project-uuid";
+
+ private final PeriodHolder periodHolder = mock(PeriodHolder.class);
+
+ private final AnalysisMetadataHolder analysisMetadataHolder = mock(AnalysisMetadataHolder.class);
+
+ private final DbClient dbClient = mock(DbClient.class);
+
+ private final TreeRootHolder treeRootHolder = mock(TreeRootHolder.class);
+
+ @RegisterExtension
+ private final LogTesterJUnit5 logs = new LogTesterJUnit5().setLevel(Level.DEBUG);
+
+ private final PersistReferenceBranchPeriodStep persistReferenceBranchPeriodStep = new PersistReferenceBranchPeriodStep(
+ periodHolder, analysisMetadataHolder, dbClient, treeRootHolder);
+
+ private final DbSession dbSession = mock(DbSession.class);
+ private final NewCodePeriodDao newCodePeriodeDao = mock(NewCodePeriodDao.class);
+
+ private final ComputationStep.Context context = mock(ComputationStep.Context.class);
+
+ @BeforeEach
+ void setUp() {
+ Project project = new Project(PROJECT_UUID, "project-key", PROJECT_NAME, "project-description", emptyList());
+ when(analysisMetadataHolder.isBranch()).thenReturn(true);
+ when(analysisMetadataHolder.getProject()).thenReturn(project);
+ when(analysisMetadataHolder.getBranch()).thenReturn(new TestBranch(FEATURE_BRANCH));
+
+ when(periodHolder.hasPeriod()).thenReturn(true);
+ Period period = new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), MAIN_BRANCH, null);
+ when(periodHolder.getPeriod()).thenReturn(period);
+ when(periodHolder.getPeriodOrigin()).thenReturn(PeriodOrigin.SCANNER);
+
+ when(dbClient.openSession(false)).thenReturn(dbSession);
+ when(dbClient.newCodePeriodDao()).thenReturn(newCodePeriodeDao);
+
+ Component root = mock(Component.class);
+ when(treeRootHolder.getRoot()).thenReturn(root);
+ when(root.getUuid()).thenReturn(BRANCH_UUID);
+
+ }
+
+ @Test
+ void getDescription() {
+ assertThat(persistReferenceBranchPeriodStep.getDescription()).isEqualTo("Persist or update reference branch new code period");
+ }
+
+ @Test
+ void execute_shouldDoNothing_whenNotABranch() {
+ when(analysisMetadataHolder.isBranch()).thenReturn(false);
+ verifyExecuteNotCalled();
+ }
+
+ @Test
+ void execute_shouldDoNothing_whenNoPeriods() {
+ when(periodHolder.hasPeriod()).thenReturn(false);
+ verifyExecuteNotCalled();
+ }
+
+ @Test
+ void execute_shouldDoNothing_whenNotReferenceBranchPeriod() {
+ Period period = new Period("not-ref-branch", MAIN_BRANCH, null);
+ when(periodHolder.getPeriod()).thenReturn(period);
+ when(periodHolder.getPeriodOrigin()).thenReturn(PeriodOrigin.SETTINGS);
+ verifyExecuteNotCalled();
+ }
+
+ private void verifyExecuteNotCalled() {
+ PersistReferenceBranchPeriodStep spyStep = spy(persistReferenceBranchPeriodStep);
+
+ spyStep.execute(context);
+
+ verify(spyStep, never()).executePersistPeriodStep();
+ }
+
+ @Test
+ void execute_shouldCreateNewCodePeriod_whenItDoesNotExists() {
+ NewCodePeriodDto expectedNewCodePeriod = new NewCodePeriodDto()
+ .setBranchUuid(BRANCH_UUID)
+ .setProjectUuid(PROJECT_UUID)
+ .setType(NewCodePeriodType.REFERENCE_BRANCH)
+ .setValue(MAIN_BRANCH);
+ when(newCodePeriodeDao.selectByBranch(dbSession, PROJECT_UUID, BRANCH_UUID)).thenReturn(Optional.empty());
+
+ persistReferenceBranchPeriodStep.execute(context);
+
+ assertThat(logs.logs(DEBUG)).contains(
+ String.format("Persisting reference branch new code period '%s' for project '%s' and branch '%s'",MAIN_BRANCH, PROJECT_NAME, FEATURE_BRANCH));
+ ArgumentCaptor<NewCodePeriodDto> newCodePeriodCaptor = ArgumentCaptor.forClass(NewCodePeriodDto.class);
+ verify(newCodePeriodeDao).insert(eq(dbSession), newCodePeriodCaptor.capture());
+ assertThat(newCodePeriodCaptor.getValue()).usingRecursiveComparison().isEqualTo(expectedNewCodePeriod);
+ }
+
+ @Test
+ void execute_shouldUpdateNewCodePeriod_whenItExistsAndItChanged() {
+ NewCodePeriodDto expectedNewCodePeriod = new NewCodePeriodDto()
+ .setBranchUuid(BRANCH_UUID)
+ .setProjectUuid(PROJECT_UUID)
+ .setType(NewCodePeriodType.REFERENCE_BRANCH)
+ .setValue(MAIN_BRANCH);
+ var newCodePeriodInBase = new NewCodePeriodDto()
+ .setBranchUuid(BRANCH_UUID)
+ .setProjectUuid(PROJECT_UUID)
+ .setType(NewCodePeriodType.REFERENCE_BRANCH)
+ .setValue("old-value");
+
+ when(newCodePeriodeDao.selectByBranch(dbSession, PROJECT_UUID, BRANCH_UUID)).thenReturn(Optional.of(newCodePeriodInBase));
+
+ persistReferenceBranchPeriodStep.execute(context);
+
+ assertThat(logs.logs(DEBUG)).contains(
+ String.format("Updating reference branch new code period '%s' for project '%s' and branch '%s'", MAIN_BRANCH ,PROJECT_NAME, FEATURE_BRANCH));
+ ArgumentCaptor<NewCodePeriodDto> newCodePeriodCaptor = ArgumentCaptor.forClass(NewCodePeriodDto.class);
+ verify(newCodePeriodeDao).update(eq(dbSession), newCodePeriodCaptor.capture());
+ assertThat(newCodePeriodCaptor.getValue()).usingRecursiveComparison().isEqualTo(expectedNewCodePeriod);
+ }
+
+ @Test
+ void execute_shouldDoNothing_whenItExistsAndItDidNotChanged() {
+ NewCodePeriodDto expectedNewCodePeriod = new NewCodePeriodDto()
+ .setBranchUuid(BRANCH_UUID)
+ .setProjectUuid(PROJECT_UUID)
+ .setType(NewCodePeriodType.REFERENCE_BRANCH)
+ .setValue(MAIN_BRANCH);
+
+ when(newCodePeriodeDao.selectByBranch(dbSession, PROJECT_UUID, BRANCH_UUID)).thenReturn(Optional.of(expectedNewCodePeriod));
+
+ persistReferenceBranchPeriodStep.execute(context);
+
+ verify(newCodePeriodeDao, never()).update(any(), any());
+ verify(newCodePeriodeDao, never()).insert(any(), any());
+ }
+
+}