From 01363ea39feba1f5bfec0d57ef7ee24d3f3a6d82 Mon Sep 17 00:00:00 2001
From: Nolwenn Cadic
<98824442+Nolwenn-cadic-sonarsource@users.noreply.github.com>
Date: Wed, 17 May 2023 14:56:50 +0200
Subject: [PATCH] SONAR-19294 Fail to unset NCD when parent NCD not CaYC
compliant (#8314)
---
.../server/newcodeperiod/CaycUtilsTest.java | 85 +++++++++
.../newcodeperiod/ws/UnsetActionIT.java | 169 +++++++++++++++++-
.../sonar/server/newcodeperiod/CaycUtils.java | 39 ++++
.../server/newcodeperiod/ws/UnsetAction.java | 58 +++++-
4 files changed, 337 insertions(+), 14 deletions(-)
create mode 100644 server/sonar-webserver-webapi/src/it/java/org/sonar/server/newcodeperiod/CaycUtilsTest.java
create mode 100644 server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/CaycUtils.java
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/newcodeperiod/CaycUtilsTest.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/newcodeperiod/CaycUtilsTest.java
new file mode 100644
index 00000000000..4dccd0a7a1f
--- /dev/null
+++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/newcodeperiod/CaycUtilsTest.java
@@ -0,0 +1,85 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.newcodeperiod;
+
+import org.junit.Test;
+import org.sonar.db.newcodeperiod.NewCodePeriodDto;
+import org.sonar.db.newcodeperiod.NewCodePeriodType;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+public class CaycUtilsTest {
+
+ @Test
+ public void reference_branch_is_compliant() {
+ var newCodePeriod = new NewCodePeriodDto()
+ .setType(NewCodePeriodType.REFERENCE_BRANCH)
+ .setValue("master");
+ assertThat(CaycUtils.isNewCodePeriodCompliant(newCodePeriod.getType(), newCodePeriod.getValue())).isTrue();
+ }
+
+ @Test
+ public void previous_version_is_compliant() {
+ var newCodePeriod = new NewCodePeriodDto()
+ .setType(NewCodePeriodType.PREVIOUS_VERSION)
+ .setValue("1.0");
+ assertThat(CaycUtils.isNewCodePeriodCompliant(newCodePeriod.getType(), newCodePeriod.getValue())).isTrue();
+ }
+
+ @Test
+ public void number_of_days_smaller_than_90_is_compliant() {
+ var newCodePeriod = new NewCodePeriodDto()
+ .setType(NewCodePeriodType.NUMBER_OF_DAYS)
+ .setValue("30");
+ assertThat(CaycUtils.isNewCodePeriodCompliant(newCodePeriod.getType(), newCodePeriod.getValue())).isTrue();
+ }
+
+ @Test
+ public void number_of_days_smaller_than_1_is_not_compliant() {
+ var newCodePeriod = new NewCodePeriodDto()
+ .setType(NewCodePeriodType.NUMBER_OF_DAYS)
+ .setValue("0");
+ assertThat(CaycUtils.isNewCodePeriodCompliant(newCodePeriod.getType(), newCodePeriod.getValue())).isFalse();
+ }
+
+ @Test
+ public void number_of_days_bigger_than_90_is_not_compliant() {
+ var newCodePeriod = new NewCodePeriodDto()
+ .setType(NewCodePeriodType.NUMBER_OF_DAYS)
+ .setValue("91");
+ assertThat(CaycUtils.isNewCodePeriodCompliant(newCodePeriod.getType(), newCodePeriod.getValue())).isFalse();
+ }
+
+ @Test
+ public void specific_analysis_is_compliant() {
+ var newCodePeriod = new NewCodePeriodDto()
+ .setType(NewCodePeriodType.SPECIFIC_ANALYSIS)
+ .setValue("sdfsafsdf");
+ assertThat(CaycUtils.isNewCodePeriodCompliant(newCodePeriod.getType(), newCodePeriod.getValue())).isTrue();
+ }
+
+ @Test
+ public void wrong_number_of_days_format_should_throw_exception() {
+ assertThatThrownBy(() -> CaycUtils.isNewCodePeriodCompliant(NewCodePeriodType.NUMBER_OF_DAYS, "abc"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Failed to parse number of days: abc");
+ }
+}
\ No newline at end of file
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/newcodeperiod/ws/UnsetActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/newcodeperiod/ws/UnsetActionIT.java
index 934d6f2ec0c..543ff39515c 100644
--- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/newcodeperiod/ws/UnsetActionIT.java
+++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/newcodeperiod/ws/UnsetActionIT.java
@@ -43,6 +43,7 @@ import org.sonar.server.component.TestComponentFinder;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.WsActionTester;
import static org.assertj.core.api.Assertions.assertThat;
@@ -92,9 +93,10 @@ public class UnsetActionIT {
// validation of project/branch
@Test
public void throw_IAE_if_branch_is_specified_without_project() {
- assertThatThrownBy(() -> ws.newRequest()
- .setParam("branch", "branch")
- .execute())
+
+ TestRequest request = ws.newRequest()
+ .setParam("branch", "branch");
+ assertThatThrownBy(() -> request.execute())
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("If branch key is specified, project key needs to be specified too");
}
@@ -191,7 +193,7 @@ public class UnsetActionIT {
ComponentDto project = componentDb.insertPublicProject().getMainBranchComponent();
ComponentDto branch = componentDb.insertProjectBranch(project, b -> b.setKey("branch"));
- db.newCodePeriods().insert(project.uuid(), null, NewCodePeriodType.SPECIFIC_ANALYSIS, "uuid1");
+ db.newCodePeriods().insert(project.uuid(), null, NewCodePeriodType.NUMBER_OF_DAYS, "20");
db.newCodePeriods().insert(project.uuid(), branch.uuid(), NewCodePeriodType.SPECIFIC_ANALYSIS, "uuid2");
logInAsProjectAdministrator(project);
@@ -201,7 +203,7 @@ public class UnsetActionIT {
.setParam("branch", "branch")
.execute();
- assertTableContainsOnly(project.uuid(), null, NewCodePeriodType.SPECIFIC_ANALYSIS, "uuid1");
+ assertTableContainsOnly(project.uuid(), null, NewCodePeriodType.NUMBER_OF_DAYS, "20");
}
@Test
@@ -222,6 +224,163 @@ public class UnsetActionIT {
assertTableEmpty();
}
+ @Test
+ public void throw_IAE_if_unset_branch_NCD_and_project_NCD_not_compliant() {
+ ComponentDto project = componentDb.insertPublicProject().getMainBranchComponent();
+ ComponentDto branch = componentDb.insertProjectBranch(project, b -> b.setKey("branch"));
+ db.newCodePeriods().insert(project.uuid(), null, NewCodePeriodType.NUMBER_OF_DAYS, "97");
+ db.newCodePeriods().insert(project.uuid(), branch.uuid(), NewCodePeriodType.SPECIFIC_ANALYSIS, "uuid");
+
+ TestRequest request = ws.newRequest()
+ .setParam("project", project.getKey())
+ .setParam("branch", "branch");
+
+ logInAsProjectAdministrator(project);
+ assertThatThrownBy(() -> request.execute())
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Failed to unset the New Code Definition. Your project " +
+ "New Code Definition is not compatible with the Clean as You Code methodology. Please update your project New Code Definition");
+
+ }
+
+ @Test
+ public void throw_IAE_if_unset_branch_NCD_and_no_project_NCD_and_instance_NCD_not_compliant() {
+ ComponentDto project = componentDb.insertPublicProject().getMainBranchComponent();
+ ComponentDto branch = componentDb.insertProjectBranch(project, b -> b.setKey("branch"));
+ db.newCodePeriods().insert(null, null, NewCodePeriodType.NUMBER_OF_DAYS, "97");
+ db.newCodePeriods().insert(project.uuid(), branch.uuid(), NewCodePeriodType.SPECIFIC_ANALYSIS, "uuid");
+
+ TestRequest request = ws.newRequest()
+ .setParam("project", project.getKey())
+ .setParam("branch", "branch");
+
+ logInAsProjectAdministrator(project);
+ assertThatThrownBy(() -> request.execute())
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Failed to unset the New Code Definition. Your instance " +
+ "New Code Definition is not compatible with the Clean as You Code methodology. Please update your instance New Code Definition");
+ }
+
+ @Test
+ public void throw_IAE_if_unset_project_NCD_and_instance_NCD_not_compliant() {
+ ComponentDto project = componentDb.insertPublicProject().getMainBranchComponent();
+ db.newCodePeriods().insert(null, null, NewCodePeriodType.NUMBER_OF_DAYS, "97");
+ db.newCodePeriods().insert(project.uuid(), null, NewCodePeriodType.SPECIFIC_ANALYSIS, "uuid");
+
+ logInAsProjectAdministrator(project);
+
+ TestRequest request = ws.newRequest()
+ .setParam("project", project.getKey());
+ assertThatThrownBy(() -> request.execute())
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Failed to unset the New Code Definition. Your instance " +
+ "New Code Definition is not compatible with the Clean as You Code methodology. Please update your instance New Code Definition");
+ }
+
+ @Test
+ public void do_not_throw_IAE_if_unset_project_NCD_and_no_instance_NCD() {
+ ComponentDto project = componentDb.insertPublicProject().getMainBranchComponent();
+ db.newCodePeriods().insert(project.uuid(), null, NewCodePeriodType.SPECIFIC_ANALYSIS, "uuid");
+
+ logInAsProjectAdministrator(project);
+ ws.newRequest()
+ .setParam("project", project.getKey())
+ .execute();
+
+ assertTableEmpty();
+ }
+
+ @Test
+ public void do_not_throw_IAE_if_unset_branch_NCD_and_project_NCD_compliant() {
+ ComponentDto project = componentDb.insertPublicProject().getMainBranchComponent();
+ ComponentDto branch = componentDb.insertProjectBranch(project, b -> b.setKey("branch"));
+ db.newCodePeriods().insert(project.uuid(), branch.uuid(), NewCodePeriodType.SPECIFIC_ANALYSIS, "uuid");
+ db.newCodePeriods().insert(project.uuid(), null, NewCodePeriodType.PREVIOUS_VERSION, null);
+
+ logInAsProjectAdministrator(project);
+ ws.newRequest()
+ .setParam("project", project.getKey())
+ .setParam("branch", "branch")
+ .execute();
+
+ assertTableContainsOnly(project.uuid(), null, NewCodePeriodType.PREVIOUS_VERSION, null);
+ }
+
+ @Test
+ public void do_not_throw_IAE_if_unset_branch_NCD_and_project_NCD_not_compliant_and_no_branch_NCD() {
+ ComponentDto project = componentDb.insertPublicProject().getMainBranchComponent();
+ componentDb.insertProjectBranch(project, b -> b.setKey("branch"));
+ db.newCodePeriods().insert(project.uuid(), null, NewCodePeriodType.NUMBER_OF_DAYS, "93");
+
+ logInAsProjectAdministrator(project);
+ ws.newRequest()
+ .setParam("project", project.getKey())
+ .setParam("branch", "branch")
+ .execute();
+
+ assertTableContainsOnly(project.uuid(), null, NewCodePeriodType.NUMBER_OF_DAYS, "93");
+ }
+
+ @Test
+ public void do_not_throw_IAE_if_unset_branch_NCD_and_no_project_NCD_and_instance_NCD_compliant() {
+ ComponentDto project = componentDb.insertPublicProject().getMainBranchComponent();
+ ComponentDto branch = componentDb.insertProjectBranch(project, b -> b.setKey("branch"));
+ db.newCodePeriods().insert(project.uuid(), branch.uuid(), NewCodePeriodType.SPECIFIC_ANALYSIS, "uuid");
+ db.newCodePeriods().insert(null, null, NewCodePeriodType.PREVIOUS_VERSION, null);
+
+ logInAsProjectAdministrator(project);
+ ws.newRequest()
+ .setParam("project", project.getKey())
+ .setParam("branch", "branch")
+ .execute();
+
+ assertTableContainsOnly(null, null, NewCodePeriodType.PREVIOUS_VERSION, null);
+ }
+
+ @Test
+ public void do_not_throw_IAE_if_unset_branch_NCD_and_no_project_NCD_and_no_instance() {
+ ComponentDto project = componentDb.insertPublicProject().getMainBranchComponent();
+ ComponentDto branch = componentDb.insertProjectBranch(project, b -> b.setKey("branch"));
+ db.newCodePeriods().insert(project.uuid(), branch.uuid(), NewCodePeriodType.SPECIFIC_ANALYSIS, "uuid");
+
+ logInAsProjectAdministrator(project);
+ ws.newRequest()
+ .setParam("project", project.getKey())
+ .setParam("branch", "branch")
+ .execute();
+
+ assertTableEmpty();
+ }
+
+ @Test
+ public void do_not_throw_IAE_if_unset_project_NCD_and_instance_NCD_compliant() {
+ ComponentDto project = componentDb.insertPublicProject().getMainBranchComponent();
+ ComponentDto branch = componentDb.insertProjectBranch(project, b -> b.setKey("branch"));
+ db.newCodePeriods().insert(null, null, NewCodePeriodType.PREVIOUS_VERSION, null);
+ db.newCodePeriods().insert(project.uuid(), null, NewCodePeriodType.PREVIOUS_VERSION, null);
+
+ logInAsProjectAdministrator(project);
+ ws.newRequest()
+ .setParam("project", project.getKey())
+ .execute();
+
+ assertTableContainsOnly(null, null, NewCodePeriodType.PREVIOUS_VERSION, null);
+ }
+
+ @Test
+ public void do_not_throw_IAE_if_unset_project_NCD_and_instance_NCD_not_compliant_and_no_project_NCD() {
+ ComponentDto project = componentDb.insertPublicProject().getMainBranchComponent();
+ ComponentDto branch = componentDb.insertProjectBranch(project, b -> b.setKey("branch"));
+ db.newCodePeriods().insert(null, null, NewCodePeriodType.NUMBER_OF_DAYS, "93");
+
+ logInAsProjectAdministrator(project);
+ ws.newRequest()
+ .setParam("project", project.getKey())
+ .execute();
+
+ assertTableContainsOnly(null, null, NewCodePeriodType.NUMBER_OF_DAYS, "93");
+ }
+
private void assertTableEmpty() {
assertThat(db.countRowsOfTable(dbSession, "new_code_periods")).isZero();
}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/CaycUtils.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/CaycUtils.java
new file mode 100644
index 00000000000..5a371add3be
--- /dev/null
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/CaycUtils.java
@@ -0,0 +1,39 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.newcodeperiod;
+
+import org.sonar.db.newcodeperiod.NewCodePeriodType;
+
+public interface CaycUtils {
+ static boolean isNewCodePeriodCompliant(NewCodePeriodType type, String value) {
+ if (type == NewCodePeriodType.NUMBER_OF_DAYS) {
+ return parseDays(value) > 0 && parseDays(value) <= 90;
+ }
+ return true;
+ }
+
+ static int parseDays(String value) {
+ try {
+ return Integer.parseInt(value);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Failed to parse number of days: " + value);
+ }
+ }
+}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/UnsetAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/UnsetAction.java
index 0b313178a37..16dba5aa6a0 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/UnsetAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/UnsetAction.java
@@ -19,6 +19,7 @@
*/
package org.sonar.server.newcodeperiod.ws;
+import javax.annotation.Nullable;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
@@ -33,14 +34,18 @@ import org.sonar.db.newcodeperiod.NewCodePeriodDao;
import org.sonar.db.project.ProjectDto;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.newcodeperiod.CaycUtils;
import org.sonar.server.user.UserSession;
import static java.lang.String.format;
import static org.sonar.server.ws.WsUtils.createHtmlExternalLink;
public class UnsetAction implements NewCodePeriodsWsAction {
- private static final String PARAM_BRANCH = "branch";
- private static final String PARAM_PROJECT = "project";
+ private static final String BRANCH = "branch";
+ private static final String PROJECT = "project";
+ private static final String INSTANCE = "instance";
+ private static final String NON_COMPLIANT_CAYC_ERROR_MESSAGE = "Failed to unset the New Code Definition. Your %s " +
+ "New Code Definition is not compatible with the Clean as You Code methodology. Please update your %s New Code Definition";
private final DbClient dbClient;
private final UserSession userSession;
@@ -64,8 +69,8 @@ public class UnsetAction implements NewCodePeriodsWsAction {
WebService.NewAction action = context.createAction("unset")
.setPost(true)
.setDescription("Unsets the " + createHtmlExternalLink(newCodeDefinitionDocumentationUrl, "new code definition") +
- " for a branch, project or global.
" +
- "Requires one of the following permissions: " +
+ " for a branch, project or global. It requires the inherited New Code Definition to be compatible with the Clean as You Code methodology, " +
+ "and one of the following permissions: " +
"