--- /dev/null
+/*
+ * 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.qualitygate;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.sonar.api.measures.Metric;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.metric.MetricDto;
+import org.sonar.db.qualitygate.QualityGateConditionDto;
+
+import static java.util.stream.Collectors.toUnmodifiableMap;
+import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING;
+import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_RATING;
+import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED;
+import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_RATING;
+import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
+
+public class QualityGateCaycChecker {
+ private static final Map<String, Double> CAYC_REQUIREMENTS = Stream.of(
+ NEW_MAINTAINABILITY_RATING,
+ NEW_RELIABILITY_RATING,
+ NEW_SECURITY_HOTSPOTS_REVIEWED,
+ NEW_SECURITY_RATING
+ ).collect(toUnmodifiableMap(Metric::getKey, Metric::getBestValue));
+
+ private final DbClient dbClient;
+
+ public QualityGateCaycChecker(DbClient dbClient) {
+ this.dbClient = dbClient;
+ }
+
+ public boolean checkCaycCompliant(DbSession dbSession, String qualityGateUuid) {
+ var conditionsByMetricId = dbClient.gateConditionDao().selectForQualityGate(dbSession, qualityGateUuid)
+ .stream()
+ .collect(uniqueIndex(QualityGateConditionDto::getMetricUuid));
+ var metrics = dbClient.metricDao().selectByUuids(dbSession, conditionsByMetricId.keySet())
+ .stream()
+ .filter(MetricDto::isEnabled)
+ .collect(Collectors.toSet());
+ long count = metrics.stream()
+ .filter(metric -> CAYC_REQUIREMENTS.containsKey(metric.getKey()))
+ .filter(metric -> checkMetricCaycCompliant(conditionsByMetricId.get(metric.getUuid()), metric))
+ .count();
+ return count == CAYC_REQUIREMENTS.size();
+ }
+
+ public boolean checkCaycCompliantFromProject(DbSession dbSession, String projectUuid) {
+ return Optional.ofNullable(dbClient.qualityGateDao().selectByProjectUuid(dbSession, projectUuid))
+ .or(() -> Optional.ofNullable(dbClient.qualityGateDao().selectDefault(dbSession)))
+ .map(qualityGate -> checkCaycCompliant(dbSession, qualityGate.getUuid()))
+ .orElse(false);
+ }
+
+ private static boolean checkMetricCaycCompliant(QualityGateConditionDto condition, MetricDto metric) {
+ Double errorThreshold = Double.valueOf(condition.getErrorThreshold());
+ Double caycRequiredThreshold = CAYC_REQUIREMENTS.get(metric.getKey());
+ return caycRequiredThreshold.compareTo(errorThreshold) == 0;
+ }
+
+}
protected void configureModule() {
add(
QualityGateUpdater.class,
+ QualityGateCaycChecker.class,
QualityGateConditionsUpdater.class,
QualityGateFinder.class,
QualityGateEvaluatorImpl.class);
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.qualitygate.QualityGateDto;
+import org.sonar.server.qualitygate.QualityGateCaycChecker;
import org.sonar.server.qualitygate.QualityGateFinder;
import org.sonarqube.ws.Qualitygates.ListWsResponse;
import org.sonarqube.ws.Qualitygates.ListWsResponse.QualityGate;
private final DbClient dbClient;
private final QualityGatesWsSupport wsSupport;
private final QualityGateFinder finder;
+ private final QualityGateCaycChecker qualityGateCaycChecker;
- public ListAction(DbClient dbClient, QualityGatesWsSupport wsSupport, QualityGateFinder finder) {
+ public ListAction(DbClient dbClient, QualityGatesWsSupport wsSupport, QualityGateFinder finder, QualityGateCaycChecker qualityGateCaycChecker) {
this.dbClient = dbClient;
this.wsSupport = wsSupport;
this.finder = finder;
+ this.qualityGateCaycChecker = qualityGateCaycChecker;
}
@Override
.setSince("4.3")
.setResponseExample(Resources.getResource(this.getClass(), "list-example.json"))
.setChangelog(
+ new Change("9.9", "'isCaycCompliant' field is added on quality gate"),
new Change("8.4", "Field 'id' in the response is deprecated. Format changes from integer to string."),
new Change("7.0", "'isDefault' field is added on quality gate"),
new Change("7.0", "'default' field on root level is deprecated"),
try (DbSession dbSession = dbClient.openSession(false)) {
QualityGateDto defaultQualityGate = finder.getDefault(dbSession);
Collection<QualityGateDto> qualityGates = dbClient.qualityGateDao().selectAll(dbSession);
+
writeProtobuf(buildResponse(dbSession, qualityGates, defaultQualityGate), request, response);
}
}
.setName(qualityGate.getName())
.setIsDefault(qualityGate.getUuid().equals(defaultUuid))
.setIsBuiltIn(qualityGate.isBuiltIn())
+ .setIsCaycCompliant(qualityGateCaycChecker.checkCaycCompliant(dbSession, qualityGate.getUuid()))
.setActions(wsSupport.getActions(dbSession, qualityGate, defaultQualityGate))
.build())
.collect(toList()));
import org.sonar.db.project.ProjectDto;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.exceptions.BadRequestException;
+import org.sonar.server.qualitygate.QualityGateCaycChecker;
import org.sonar.server.user.UserSession;
import org.sonar.server.ws.KeyExamples;
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse;
private final DbClient dbClient;
private final ComponentFinder componentFinder;
private final UserSession userSession;
+ private final QualityGateCaycChecker qualityGateCaycChecker;
- public ProjectStatusAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession) {
+ public ProjectStatusAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession, QualityGateCaycChecker qualityGateCaycChecker) {
this.dbClient = dbClient;
this.componentFinder = componentFinder;
this.userSession = userSession;
+ this.qualityGateCaycChecker = qualityGateCaycChecker;
}
@Override
"<li>'Administer' rights on the specified project</li>" +
"<li>'Browse' on the specified project</li>" +
"<li>'Execute Analysis' on the specified project</li>" +
- "</ul>",MSG_ONE_PROJECT_PARAMETER_ONLY, QG_STATUSES_ONE_LINE, ProjectStatusResponse.Status.NONE))
+ "</ul>", MSG_ONE_PROJECT_PARAMETER_ONLY, QG_STATUSES_ONE_LINE, ProjectStatusResponse.Status.NONE))
.setResponseExample(getClass().getResource("project_status-example.json"))
.setSince("5.3")
.setHandler(this)
.setChangelog(
+ new Change("9.9", "'isCaycCompliant' field is added to the response"),
new Change("9.5", "The 'Execute Analysis' permission also allows to access the endpoint"),
new Change("8.5", "The field 'periods' in the response is deprecated. Use 'period' instead"),
new Change("7.7", "The parameters 'branch' and 'pullRequest' were added"),
ProjectAndSnapshot projectAndSnapshot = getProjectAndSnapshot(dbSession, analysisId, projectUuid, projectKey, branchKey, pullRequestId);
checkPermission(projectAndSnapshot.project);
Optional<String> measureData = loadQualityGateDetails(dbSession, projectAndSnapshot, analysisId != null);
+ var isCaycCompliant = qualityGateCaycChecker.checkCaycCompliantFromProject(dbSession, projectAndSnapshot.project.getUuid());
return ProjectStatusResponse.newBuilder()
- .setProjectStatus(new QualityGateDetailsFormatter(measureData, projectAndSnapshot.snapshotDto).format())
+ .setProjectStatus(new QualityGateDetailsFormatter(measureData.orElse(null), projectAndSnapshot.snapshotDto.orElse(null), isCaycCompliant).format())
.build();
}
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
-import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
-import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import javax.annotation.Nullable;
-import org.sonar.api.measures.Metric;
import org.sonar.db.component.SnapshotDto;
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse;
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse.NewCodePeriod;
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse.Period;
import static com.google.common.base.Strings.isNullOrEmpty;
-import static java.util.stream.Collectors.toUnmodifiableMap;
-import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING;
-import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_RATING;
-import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED;
-import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_RATING;
import static org.sonar.api.utils.DateUtils.formatDateTime;
public class QualityGateDetailsFormatter {
- public static final String METRIC_KEY = "metric";
- private static final Map<String, Double> CAYC_REQUIREMENTS = Stream.of(
- NEW_MAINTAINABILITY_RATING,
- NEW_RELIABILITY_RATING,
- NEW_SECURITY_HOTSPOTS_REVIEWED,
- NEW_SECURITY_RATING
- ).collect(toUnmodifiableMap(Metric::getKey, Metric::getBestValue));
private final Optional<String> optionalMeasureData;
private final Optional<SnapshotDto> optionalSnapshot;
+ private final boolean isCaycCompliant;
private final ProjectStatusResponse.ProjectStatus.Builder projectStatusBuilder;
- public QualityGateDetailsFormatter(Optional<String> measureData, Optional<SnapshotDto> snapshot) {
- this.optionalMeasureData = measureData;
- this.optionalSnapshot = snapshot;
+ public QualityGateDetailsFormatter(@Nullable String measureData, @Nullable SnapshotDto snapshot, boolean isCaycCompliant) {
+ this.optionalMeasureData = Optional.ofNullable(measureData);
+ this.optionalSnapshot = Optional.ofNullable(snapshot);
+ this.isCaycCompliant = isCaycCompliant;
this.projectStatusBuilder = ProjectStatusResponse.ProjectStatus.newBuilder();
}
ProjectStatusResponse.Status qualityGateStatus = measureLevelToQualityGateStatus(json.get("level").getAsString());
projectStatusBuilder.setStatus(qualityGateStatus);
+ projectStatusBuilder.setIsCaycCompliant(isCaycCompliant);
formatIgnoredConditions(json);
formatConditions(json.getAsJsonArray("conditions"));
- formatCleanAsYouCodeCompliant(json.getAsJsonArray("conditions"));
formatPeriods();
return projectStatusBuilder.build();
}
- private void formatCleanAsYouCodeCompliant(@Nullable JsonArray jsonConditions) {
- if (jsonConditions == null) {
- return;
- }
-
- long matchCount = jsonConditions.asList().stream()
- .map(JsonElement::getAsJsonObject)
- .filter(jsonObject -> CAYC_REQUIREMENTS.containsKey(jsonObject.get(METRIC_KEY).getAsString()))
- .filter(jsonObject -> {
- String metricKey = jsonObject.get(METRIC_KEY).getAsString();
- Double value = jsonObject.get("error").getAsDouble();
- return CAYC_REQUIREMENTS.get(metricKey).compareTo(value) == 0;
- })
- .count();
-
- projectStatusBuilder.setIsCaycCompliant(matchCount == CAYC_REQUIREMENTS.size());
- }
-
private void formatIgnoredConditions(JsonObject json) {
JsonElement ignoredConditions = json.get("ignoredConditions");
if (ignoredConditions != null) {
}
private static void formatConditionMetric(ProjectStatusResponse.Condition.Builder conditionBuilder, JsonObject jsonCondition) {
- JsonElement metric = jsonCondition.get(METRIC_KEY);
+ JsonElement metric = jsonCondition.get("metric");
if (metric != null && !isNullOrEmpty(metric.getAsString())) {
conditionBuilder.setMetricKey(metric.getAsString());
}
throw new IllegalStateException(String.format("Unknown quality gate comparator '%s'", measureOp));
}
- private static ProjectStatusResponse.ProjectStatus newResponseWithoutQualityGateDetails() {
- return ProjectStatusResponse.ProjectStatus.newBuilder().setStatus(ProjectStatusResponse.Status.NONE).build();
+ private ProjectStatusResponse.ProjectStatus newResponseWithoutQualityGateDetails() {
+ return ProjectStatusResponse.ProjectStatus.newBuilder().setStatus(ProjectStatusResponse.Status.NONE).setIsCaycCompliant(isCaycCompliant).build();
}
private static Predicate<JsonObject> isConditionOnValidPeriod() {
import org.sonar.db.metric.MetricDto;
import org.sonar.db.qualitygate.QualityGateConditionDto;
import org.sonar.db.qualitygate.QualityGateDto;
+import org.sonar.server.qualitygate.QualityGateCaycChecker;
import org.sonar.server.qualitygate.QualityGateFinder;
import org.sonarqube.ws.Qualitygates.ShowWsResponse;
private final DbClient dbClient;
private final QualityGateFinder qualityGateFinder;
private final QualityGatesWsSupport wsSupport;
+ private final QualityGateCaycChecker qualityGateCaycChecker;
- public ShowAction(DbClient dbClient, QualityGateFinder qualityGateFinder, QualityGatesWsSupport wsSupport) {
+ public ShowAction(DbClient dbClient, QualityGateFinder qualityGateFinder, QualityGatesWsSupport wsSupport, QualityGateCaycChecker qualityGateCaycChecker) {
this.dbClient = dbClient;
this.qualityGateFinder = qualityGateFinder;
this.wsSupport = wsSupport;
+ this.qualityGateCaycChecker = qualityGateCaycChecker;
}
- @Override
- public void define(WebService.NewController controller) {
- WebService.NewAction action = controller.createAction("show")
- .setDescription("Display the details of a quality gate")
- .setSince("4.3")
+ @Override public void define(WebService.NewController controller) {
+ WebService.NewAction action = controller.createAction("show").setDescription("Display the details of a quality gate").setSince("4.3")
.setResponseExample(Resources.getResource(this.getClass(), "show-example.json"))
.setChangelog(
+ new Change("9.9", "'isCaycCompliant' field is added to the response"),
new Change("8.4", "Parameter 'id' is deprecated. Format changes from integer to string. Use 'name' instead."),
new Change("8.4", "Field 'id' in the response is deprecated."),
new Change("7.6", "'period' and 'warning' fields of conditions are removed from the response"),
.setExampleValue("My Quality Gate");
}
- @Override
- public void handle(Request request, Response response) {
+ @Override public void handle(Request request, Response response) {
String id = request.param(PARAM_ID);
String name = request.param(PARAM_NAME);
checkOneOfIdOrNamePresent(id, name);
Collection<QualityGateConditionDto> conditions = getConditions(dbSession, qualityGate);
Map<String, MetricDto> metricsByUuid = getMetricsByUuid(dbSession, conditions);
QualityGateDto defaultQualityGate = qualityGateFinder.getDefault(dbSession);
- writeProtobuf(buildResponse(dbSession, qualityGate, defaultQualityGate, conditions, metricsByUuid), request, response);
+ boolean isCaycCompliant = qualityGateCaycChecker.checkCaycCompliant(dbSession, qualityGate.getUuid());
+ writeProtobuf(buildResponse(dbSession, qualityGate, defaultQualityGate, conditions, metricsByUuid, isCaycCompliant), request, response);
}
}
private Map<String, MetricDto> getMetricsByUuid(DbSession dbSession, Collection<QualityGateConditionDto> conditions) {
Set<String> metricUuids = conditions.stream().map(QualityGateConditionDto::getMetricUuid).collect(toSet());
- return dbClient.metricDao().selectByUuids(dbSession, metricUuids).stream()
- .filter(MetricDto::isEnabled)
- .collect(uniqueIndex(MetricDto::getUuid));
+ return dbClient.metricDao().selectByUuids(dbSession, metricUuids).stream().filter(MetricDto::isEnabled).collect(uniqueIndex(MetricDto::getUuid));
}
- private ShowWsResponse buildResponse(DbSession dbSession, QualityGateDto qualityGate, QualityGateDto defaultQualityGate,
- Collection<QualityGateConditionDto> conditions, Map<String, MetricDto> metricsByUuid) {
+ private ShowWsResponse buildResponse(DbSession dbSession, QualityGateDto qualityGate, QualityGateDto defaultQualityGate, Collection<QualityGateConditionDto> conditions,
+ Map<String, MetricDto> metricsByUuid, boolean isCaycCompliant) {
return ShowWsResponse.newBuilder()
.setId(qualityGate.getUuid())
.setName(qualityGate.getName())
.setIsBuiltIn(qualityGate.isBuiltIn())
+ .setIsCaycCompliant(isCaycCompliant)
.addAllConditions(conditions.stream()
.map(toWsCondition(metricsByUuid))
.collect(toList()))
"projectStatus": {
"status": "ERROR",
"ignoredConditions": false,
+ "isCaycCompliant": false,
"conditions": [
{
"status": "ERROR",
--- /dev/null
+/*
+ * 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.qualitygate;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.utils.System2;
+import org.sonar.core.util.Uuids;
+import org.sonar.db.DbTester;
+import org.sonar.db.metric.MetricDto;
+import org.sonar.db.qualitygate.QualityGateConditionDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING;
+import static org.sonar.api.measures.CoreMetrics.NEW_RELIABILITY_RATING;
+import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED;
+import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_RATING;
+
+public class QualityGateCaycCheckerTest {
+
+ @Rule
+ public DbTester db = DbTester.create(System2.INSTANCE);
+ QualityGateCaycChecker underTest = new QualityGateCaycChecker(db.getDbClient());
+
+ @Test
+ public void checkCaycCompliant() {
+ String qualityGateUuid = "abcd";
+ List<Metric<Integer>> CAYC_REQUIREMENT_METRICS = List.of(NEW_MAINTAINABILITY_RATING, NEW_RELIABILITY_RATING, NEW_SECURITY_HOTSPOTS_REVIEWED, NEW_SECURITY_RATING);
+ CAYC_REQUIREMENT_METRICS
+ .forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue()));
+ assertThat(underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)).isTrue();
+ }
+
+ @Test
+ public void check_Cayc_NonCompliant_with_lesser_threshold_value() {
+ var metrics = List.of(NEW_MAINTAINABILITY_RATING, NEW_RELIABILITY_RATING, NEW_SECURITY_HOTSPOTS_REVIEWED, NEW_SECURITY_RATING).stream()
+ .map(this::insertMetric)
+ .collect(Collectors.toList());
+
+ IntStream.range(0, metrics.size()).forEach(idx -> {
+ String qualityGateUuid = "abcd" + idx;
+ for (int i = 0; i < metrics.size(); i++) {
+ var metric = metrics.get(i);
+ insertCondition(metric, qualityGateUuid, idx == i ? metric.getWorstValue() : metric.getBestValue());
+ }
+ assertThat(underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)).isFalse();
+ });
+ }
+
+ @Test
+ public void check_Cayc_NonCompliant_with_missing_metric() {
+ String qualityGateUuid = "abcd";
+ List.of(NEW_MAINTAINABILITY_RATING, NEW_RELIABILITY_RATING, NEW_SECURITY_HOTSPOTS_REVIEWED)
+ .forEach(metric -> insertCondition(insertMetric(metric), qualityGateUuid, metric.getBestValue()));
+ assertThat(underTest.checkCaycCompliant(db.getSession(), qualityGateUuid)).isFalse();
+ }
+
+ private void insertCondition(MetricDto metricDto, String qualityGateUuid, Double threshold) {
+ QualityGateConditionDto newCondition = new QualityGateConditionDto().setQualityGateUuid(qualityGateUuid)
+ .setUuid(Uuids.create())
+ .setMetricUuid(metricDto.getUuid())
+ .setOperator("LT")
+ .setErrorThreshold(threshold.toString());
+ db.getDbClient().gateConditionDao().insert(newCondition, db.getSession());
+ db.commit();
+ }
+
+ private MetricDto insertMetric(Metric metric) {
+ return db.measures().insertMetric(m -> m
+ .setKey(metric.key())
+ .setValueType(metric.getType().name())
+ .setHidden(false)
+ .setBestValue(metric.getBestValue())
+ .setBestValue(metric.getWorstValue())
+ .setDirection(metric.getDirection()));
+ }
+}
public void verify_count_of_added_components() {
ListContainer container = new ListContainer();
new QualityGateModule().configure(container);
- assertThat(container.getAddedObjects()).hasSize(4);
+ assertThat(container.getAddedObjects()).hasSize(5);
}
}
import org.junit.Test;
import org.sonar.api.server.ws.WebService;
import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.db.qualitygate.QualityGateDto;
import org.sonar.db.user.UserDto;
import org.sonar.server.component.TestComponentFinder;
+import org.sonar.server.qualitygate.QualityGateCaycChecker;
import org.sonar.server.qualitygate.QualityGateFinder;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.WsActionTester;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.AssertionsForClassTypes.tuple;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_GATES;
import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFILES;
import static org.sonar.test.JsonAssert.assertJson;
private final DbClient dbClient = db.getDbClient();
private final QualityGateFinder qualityGateFinder = new QualityGateFinder(dbClient);
+ private final QualityGateCaycChecker qualityGateCaycChecker = mock(QualityGateCaycChecker.class);
+
private final WsActionTester ws = new WsActionTester(new ListAction(db.getDbClient(),
- new QualityGatesWsSupport(dbClient, userSession, TestComponentFinder.from(db)), qualityGateFinder));
+ new QualityGatesWsSupport(dbClient, userSession, TestComponentFinder.from(db)), qualityGateFinder, qualityGateCaycChecker));
@Test
public void list_quality_gates() {
tuple(qualityGate2.getUuid(), false));
}
+ @Test
+ public void test_isCaycCompliant_flag() {
+ QualityGateDto qualityGate1 = db.qualityGates().insertQualityGate();
+ QualityGateDto qualityGate2 = db.qualityGates().insertQualityGate();
+ when(qualityGateCaycChecker.checkCaycCompliant(any(DbSession.class), eq(qualityGate1.getUuid()))).thenReturn(true);
+ when(qualityGateCaycChecker.checkCaycCompliant(any(DbSession.class), eq(qualityGate2.getUuid()))).thenReturn(false);
+
+ db.qualityGates().setDefaultQualityGate(qualityGate1);
+
+ ListWsResponse response = ws.newRequest()
+ .executeProtobuf(ListWsResponse.class);
+
+ assertThat(response.getQualitygatesList())
+ .extracting(QualityGate::getId, QualityGate::getIsCaycCompliant)
+ .containsExactlyInAnyOrder(
+ tuple(qualityGate1.getUuid(), true),
+ tuple(qualityGate2.getUuid(), false));
+ }
+
@Test
public void test_deprecated_default_field() {
QualityGateDto defaultQualityGate = db.qualityGates().insertQualityGate();
import org.sonar.db.component.SnapshotDto;
import org.sonar.db.metric.MetricDto;
import org.sonar.db.permission.GlobalPermission;
+import org.sonar.db.qualitygate.QualityGateDto;
import org.sonar.server.component.TestComponentFinder;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.qualitygate.QualityGateCaycChecker;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.WsActionTester;
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse;
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.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import static org.sonar.db.component.SnapshotTesting.newAnalysis;
import static org.sonar.db.measure.MeasureTesting.newLiveMeasure;
import static org.sonar.db.measure.MeasureTesting.newMeasureDto;
private final DbClient dbClient = db.getDbClient();
private final DbSession dbSession = db.getSession();
+ private final QualityGateCaycChecker qualityGateCaycChecker = mock(QualityGateCaycChecker.class);
- private final WsActionTester ws = new WsActionTester(new ProjectStatusAction(dbClient, TestComponentFinder.from(db), userSession));
+ private final WsActionTester ws = new WsActionTester(new ProjectStatusAction(dbClient, TestComponentFinder.from(db), userSession, qualityGateCaycChecker));
@Test
public void test_definition() {
.executeProtobuf(ProjectStatusResponse.class);
}
+ @Test
+ public void check_cayc_compliant_flag() {
+ ComponentDto project = db.components().insertPrivateProject();
+ var qg = db.qualityGates().insertBuiltInQualityGate();
+ db.qualityGates().setDefaultQualityGate(qg);
+ when(qualityGateCaycChecker.checkCaycCompliantFromProject(any(DbSession.class), eq(project.uuid()))).thenReturn(true);
+ SnapshotDto snapshot = dbClient.snapshotDao().insert(dbSession, newAnalysis(project));
+ dbSession.commit();
+ userSession.addProjectPermission(UserRole.USER, project);
+
+ ProjectStatusResponse result = ws.newRequest()
+ .setParam(PARAM_ANALYSIS_ID, snapshot.getUuid())
+ .executeProtobuf(ProjectStatusResponse.class);
+
+ assertThat(result.getProjectStatus().getIsCaycCompliant()).isTrue();
+ }
+
@Test
public void user_with_project_scan_permission_is_allowed_to_get_project_status() {
ComponentDto project = db.components().insertPrivateProject();
import java.io.IOException;
import java.util.List;
-import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;
import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang.text.StrSubstitutor;
import org.junit.Test;
import org.sonar.db.component.SnapshotDto;
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse;
ProjectStatus result = underTest.format();
assertThat(result.getStatus()).isEqualTo(ProjectStatusResponse.Status.ERROR);
+ assertThat(result.getIsCaycCompliant()).isFalse();
// check conditions
assertThat(result.getConditionsCount()).isEqualTo(3);
List<ProjectStatusResponse.Condition> conditions = result.getConditionsList();
.hasMessageContaining("Unknown quality gate comparator 'UNKNOWN'");
}
- @Test
- public void verify_cayc_quality_gate_checked() throws IOException {
- String measureDataRaw = IOUtils.toString(getClass().getResource("QualityGateDetailsFormatterTest/cayc_compliant_qg.json"));
-
- String measureDataCompliant = StrSubstitutor.replace(measureDataRaw, Map.of("nmr_error", "1.0"));
- underTest = newQualityGateDetailsFormatter(measureDataCompliant, null);
- ProjectStatus result = underTest.format();
- assertThat(result.getIsCaycCompliant()).isTrue();
-
- String measureDataNonCompliant = StrSubstitutor.replace(measureDataRaw, Map.of("nmr_error", "2.0"));
- underTest = newQualityGateDetailsFormatter(measureDataNonCompliant, null);
- result = underTest.format();
- assertThat(result.getIsCaycCompliant()).isFalse();
- }
-
- @Test
- public void verify_cayc_quality_gate_with_missing_metric() throws IOException {
- String measureData = IOUtils.toString(getClass().getResource("QualityGateDetailsFormatterTest/cayc_missing_metric.json"));
-
- underTest = newQualityGateDetailsFormatter(measureData, null);
- ProjectStatus result = underTest.format();
- assertThat(result.getIsCaycCompliant()).isFalse();
- }
-
private static QualityGateDetailsFormatter newQualityGateDetailsFormatter(@Nullable String measureData, @Nullable SnapshotDto snapshotDto) {
- return new QualityGateDetailsFormatter(Optional.ofNullable(measureData), Optional.ofNullable(snapshotDto));
+ return new QualityGateDetailsFormatter(measureData, snapshotDto, false);
}
}
import org.sonar.api.server.ws.WebService;
import org.sonar.api.server.ws.WebService.Param;
import org.sonar.api.utils.System2;
+import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.db.metric.MetricDto;
import org.sonar.db.qualitygate.QualityGateConditionDto;
import org.sonar.db.user.UserDto;
import org.sonar.server.component.TestComponentFinder;
import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.qualitygate.QualityGateCaycChecker;
import org.sonar.server.qualitygate.QualityGateFinder;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.WsActionTester;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.AssertionsForClassTypes.tuple;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_GATES;
import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFILES;
import static org.sonar.test.JsonAssert.assertJson;
public UserSessionRule userSession = UserSessionRule.standalone();
@Rule
public DbTester db = DbTester.create(System2.INSTANCE);
+ private final QualityGateCaycChecker qualityGateCaycChecker = mock(QualityGateCaycChecker.class);
private final WsActionTester ws = new WsActionTester(
new ShowAction(db.getDbClient(), new QualityGateFinder(db.getDbClient()),
- new QualityGatesWsSupport(db.getDbClient(), userSession, TestComponentFinder.from(db))));
+ new QualityGatesWsSupport(db.getDbClient(), userSession, TestComponentFinder.from(db)), qualityGateCaycChecker));
@Test
public void show() {
assertThat(response.getIsBuiltIn()).isTrue();
}
+ @Test
+ public void show_isCaycCompliant() {
+ QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
+ when(qualityGateCaycChecker.checkCaycCompliant(any(DbSession.class), eq(qualityGate.getUuid()))).thenReturn(true);
+ db.qualityGates().setDefaultQualityGate(qualityGate);
+
+ ShowWsResponse response = ws.newRequest()
+ .setParam("name", qualityGate.getName())
+ .executeProtobuf(ShowWsResponse.class);
+
+ assertThat(response.getIsCaycCompliant()).isTrue();
+ }
+
@Test
public void show_by_id() {
QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
+++ /dev/null
-{
- "level": "ERROR",
- "conditions": [
- {
- "metric": "new_maintainability_rating",
- "op": "LT",
- "period": 1,
- "warning": "",
- "error": "${nmr_error}",
- "actual": "2",
- "level": "ERROR"
- },
- {
- "metric": "new_reliability_rating",
- "op": "LT",
- "period": 1,
- "warning": "",
- "error": "1.0",
- "actual": "1",
- "level": "OK"
- },
- {
- "metric": "new_security_hotspots_reviewed",
- "op": "GT",
- "period": 1,
- "warning": "",
- "error": "100.0",
- "actual": "100.0",
- "level": "OK"
- },
- {
- "metric": "new_security_rating",
- "op": "LT",
- "period": 1,
- "warning": "",
- "error": "1.0",
- "actual": "2",
- "level": "ERROR"
- }
- ]
-}
+++ /dev/null
-{
- "level": "ERROR",
- "conditions": [
- {
- "metric": "new_reliability_rating",
- "op": "LT",
- "period": 1,
- "warning": "",
- "error": "1.0",
- "actual": "1",
- "level": "OK"
- },
- {
- "metric": "new_security_hotspots_reviewed",
- "op": "GT",
- "period": 1,
- "warning": "",
- "error": "100.0",
- "actual": "100.0",
- "level": "OK"
- },
- {
- "metric": "new_security_rating",
- "op": "LT",
- "period": 1,
- "warning": "",
- "error": "1.0",
- "actual": "2",
- "level": "ERROR"
- }
- ]
-}
repeated Condition conditions = 3;
optional bool isBuiltIn = 4;
optional Actions actions = 5;
+ optional bool isCaycCompliant = 6;
message Condition {
optional string id = 1;
optional bool isDefault = 3;
optional bool isBuiltIn = 4;
optional Actions actions = 5;
+ optional bool isCaycCompliant = 6;
}
message RootActions {