Browse Source

SONAR-5182 Add input validation on quality gate conditions

tags/7.5
Julien Lancelot 6 years ago
parent
commit
ed707f3ee7

+ 36
- 0
server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateConditionsUpdater.java View File

@@ -38,7 +38,9 @@ import org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating;
import org.sonar.server.exceptions.NotFoundException;

import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.Double.parseDouble;
import static java.lang.Integer.parseInt;
import static java.lang.Long.parseLong;
import static java.lang.String.format;
import static java.util.Arrays.stream;
import static org.sonar.api.measures.Metric.ValueType.RATING;
@@ -115,6 +117,9 @@ public class QualityGateConditionsUpdater {
checkOperator(metric, operator, errors);
checkThresholds(warningThreshold, errorThreshold, errors);
checkPeriod(metric, period, errors);

validateThresholdValues(metric, warningThreshold, errors);
validateThresholdValues(metric, errorThreshold, errors);
checkRatingMetric(metric, warningThreshold, errorThreshold, period, errors);
checkRequest(errors.isEmpty(), errors);
}
@@ -159,6 +164,37 @@ public class QualityGateConditionsUpdater {
: format("Condition on metric '%s' over leak period already exists.", metric.getShortName()));
}

private static void validateThresholdValues(MetricDto metric, @Nullable String value, List<String> errors) {
if (value == null) {
return;
}
try {
ValueType valueType = ValueType.valueOf(metric.getValueType());
switch (valueType) {
case BOOL:
case INT:
case RATING:
parseInt(value);
return;
case MILLISEC:
case WORK_DUR:
parseLong(value);
return;
case FLOAT:
case PERCENT:
parseDouble(value);
return;
case STRING:
case LEVEL:
return;
default:
throw new IllegalArgumentException(format("Unsupported value type %s. Cannot convert condition value", valueType));
}
} catch (Exception e) {
errors.add(format("Invalid value '%s' for metric '%s'", value, metric.getShortName()));
}
}

private static void checkRatingMetric(MetricDto metric, @Nullable String warningThreshold, @Nullable String errorThreshold, @Nullable Integer period, List<String> errors) {
if (!metric.getValueType().equals(RATING.name())) {
return;

+ 2
- 1
server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/CreateConditionAction.java View File

@@ -31,6 +31,7 @@ import org.sonar.server.qualitygate.QualityGateConditionsUpdater;
import org.sonar.server.qualitygate.QualityGateFinder;
import org.sonarqube.ws.Qualitygates.CreateConditionResponse;

import static com.google.common.base.Strings.emptyToNull;
import static org.sonar.core.util.Protobuf.setNullable;
import static org.sonar.server.qualitygate.ws.QualityGatesWs.addConditionParams;
import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.ACTION_CREATE_CONDITION;
@@ -89,7 +90,7 @@ public class CreateConditionAction implements QualityGatesWsAction {
OrganizationDto organization = wsSupport.getOrganization(dbSession, request);
QGateWithOrgDto qualityGate = qualityGateFinder.getByOrganizationAndId(dbSession, organization, gateId);
wsSupport.checkCanEdit(qualityGate);
QualityGateConditionDto condition = qualityGateConditionsUpdater.createCondition(dbSession, qualityGate, metric, operator, warning, error, period);
QualityGateConditionDto condition = qualityGateConditionsUpdater.createCondition(dbSession, qualityGate, metric, operator, emptyToNull(warning), emptyToNull(error), period);
CreateConditionResponse.Builder createConditionResponse = CreateConditionResponse.newBuilder()
.setId(condition.getId())
.setMetric(condition.getMetricKey())

+ 2
- 1
server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/UpdateConditionAction.java View File

@@ -31,6 +31,7 @@ import org.sonar.server.qualitygate.QualityGateConditionsUpdater;
import org.sonarqube.ws.Qualitygates.UpdateConditionResponse;

import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.emptyToNull;
import static org.sonar.core.util.Protobuf.setNullable;
import static org.sonar.server.qualitygate.ws.QualityGatesWs.addConditionParams;
import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.ACTION_UPDATE_CONDITION;
@@ -88,7 +89,7 @@ public class UpdateConditionAction implements QualityGatesWsAction {
QGateWithOrgDto qualityGateDto = dbClient.qualityGateDao().selectByOrganizationAndId(dbSession, organization, condition.getQualityGateId());
checkState(qualityGateDto != null, "Condition '%s' is linked to an unknown quality gate '%s'", id, condition.getQualityGateId());
wsSupport.checkCanEdit(qualityGateDto);
QualityGateConditionDto updatedCondition = qualityGateConditionsUpdater.updateCondition(dbSession, condition, metric, operator, warning, error, period);
QualityGateConditionDto updatedCondition = qualityGateConditionsUpdater.updateCondition(dbSession, condition, metric, operator, emptyToNull(warning), emptyToNull(error), period);
UpdateConditionResponse.Builder updateConditionResponse = UpdateConditionResponse.newBuilder()
.setId(updatedCondition.getId())
.setMetric(updatedCondition.getMetricKey())

+ 257
- 127
server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateConditionsUpdaterTest.java View File

@@ -23,30 +23,31 @@ import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import javax.annotation.Nullable;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.sonar.api.measures.Metric;
import org.sonar.api.utils.System2;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.db.metric.MetricDto;
import org.sonar.db.qualitygate.QualityGateConditionDto;
import org.sonar.db.qualitygate.QualityGateDbTester;
import org.sonar.db.qualitygate.QualityGateDto;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.NotFoundException;

import static java.lang.String.format;
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
import static org.sonar.api.measures.CoreMetrics.SQALE_RATING_KEY;
import static org.sonar.api.measures.Metric.ValueType.BOOL;
import static org.sonar.api.measures.Metric.ValueType.DATA;
import static org.sonar.api.measures.Metric.ValueType.FLOAT;
import static org.sonar.api.measures.Metric.ValueType.INT;
import static org.sonar.api.measures.Metric.ValueType.MILLISEC;
import static org.sonar.api.measures.Metric.ValueType.PERCENT;
import static org.sonar.api.measures.Metric.ValueType.RATING;
import static org.sonar.db.metric.MetricTesting.newMetricDto;
import static org.sonar.server.computation.task.projectanalysis.metric.Metric.MetricType.PERCENT;
import static org.sonar.api.measures.Metric.ValueType.WORK_DUR;

@RunWith(DataProviderRunner.class)
public class QualityGateConditionsUpdaterTest {
@@ -57,234 +58,351 @@ public class QualityGateConditionsUpdaterTest {
@Rule
public DbTester db = DbTester.create(System2.INSTANCE);

DbClient dbClient = db.getDbClient();
DbSession dbSession = db.getSession();
QualityGateDbTester qualityGateDbTester = new QualityGateDbTester(db);

QualityGateDto qualityGateDto;
MetricDto coverageMetricDto = newMetricDto()
.setKey("coverage")
.setShortName("Coverage")
.setValueType(PERCENT.name())
.setHidden(false);

MetricDto ratingMetricDto = newMetricDto()
.setKey("reliability_rating")
.setShortName("Reliability Rating")
.setValueType(RATING.name())
.setHidden(false);

QualityGateConditionsUpdater underTest = new QualityGateConditionsUpdater(dbClient);

@Before
public void setUp() throws Exception {
qualityGateDto = qualityGateDbTester.insertQualityGate(db.getDefaultOrganization());
dbClient.metricDao().insert(dbSession, coverageMetricDto, ratingMetricDto);
dbSession.commit();
}
private QualityGateConditionsUpdater underTest = new QualityGateConditionsUpdater(db.getDbClient());

@Test
public void create_warning_condition_without_period() {
QualityGateConditionDto result = underTest.createCondition(dbSession, qualityGateDto, "coverage", "LT", "90", null, null);
MetricDto metric = db.measures().insertMetric(m -> m.setValueType(PERCENT.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());

QualityGateConditionDto result = underTest.createCondition(db.getSession(), qualityGate, metric.getKey(), "LT", "90", null, null);

verifyCondition(result, coverageMetricDto.getId(), "LT", "90", null, null);
verifyCondition(result, qualityGate, metric, "LT", "90", null, null);
}

@Test
public void create_error_condition_with_period() {
MetricDto metricDto = dbClient.metricDao().insert(dbSession, newMetricDto()
.setKey("new_coverage")
.setValueType(INT.name())
.setHidden(false));
dbSession.commit();
public void create_error_condition_on_leak_period() {
MetricDto metric = db.measures().insertMetric(m -> m.setKey("new_coverage").setValueType(INT.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());

QualityGateConditionDto result = underTest.createCondition(dbSession, qualityGateDto, "new_coverage", "LT", null, "80", 1);
QualityGateConditionDto result = underTest.createCondition(db.getSession(), qualityGate, metric.getKey(), "LT", null, "80", 1);

verifyCondition(result, metricDto.getId(), "LT", null, "80", 1);
verifyCondition(result, qualityGate, metric, "LT", null, "80", 1);
}

@Test
public void fail_to_create_condition_when_condition_on_same_metric_already_exist() {
dbClient.gateConditionDao().insert(new QualityGateConditionDto()
.setQualityGateId(qualityGateDto.getId())
.setMetricId(coverageMetricDto.getId())
.setPeriod(null),
dbSession);
MetricDto metric = db.measures().insertMetric(m -> m.setValueType(PERCENT.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());
db.qualityGates().addCondition(qualityGate, metric);

expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Condition on metric 'Coverage' already exists.");
underTest.createCondition(dbSession, qualityGateDto, coverageMetricDto.getKey(), "LT", "90", null, null);
expectedException.expectMessage(format("Condition on metric '%s' already exists.", metric.getShortName()));

underTest.createCondition(db.getSession(), qualityGate, metric.getKey(), "LT", "90", null, null);
}

@Test
public void fail_to_create_condition_when_condition_on_same_metric_and_on_leak_period_already_exist() {
dbClient.gateConditionDao().insert(new QualityGateConditionDto()
.setQualityGateId(qualityGateDto.getId())
.setMetricId(coverageMetricDto.getId())
.setPeriod(1),
dbSession);
MetricDto metric = db.measures().insertMetric(m -> m.setValueType(PERCENT.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());
db.qualityGates().addCondition(qualityGate, metric, c -> c.setPeriod(1));

expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Condition on metric 'Coverage' over leak period already exists.");
underTest.createCondition(dbSession, qualityGateDto, coverageMetricDto.getKey(), "LT", "90", null, 1);
expectedException.expectMessage(format("Condition on metric '%s' over leak period already exists.", metric.getShortName()));

underTest.createCondition(db.getSession(), qualityGate, metric.getKey(), "LT", "90", null, 1);
}

@Test
public void fail_to_create_condition_on_missing_metric() {
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());

expectedException.expect(NotFoundException.class);
expectedException.expectMessage("There is no metric with key=new_coverage");
underTest.createCondition(dbSession, qualityGateDto, "new_coverage", "LT", null, "80", 2);

underTest.createCondition(db.getSession(), qualityGate, "new_coverage", "LT", null, "80", 2);
}

@Test
@UseDataProvider("invalid_metrics")
public void fail_to_create_condition_on_invalid_metric(String metricKey, Metric.ValueType valueType, boolean hidden) {
dbClient.metricDao().insert(dbSession, newMetricDto()
.setKey(metricKey)
.setValueType(valueType.name())
.setHidden(hidden));
dbSession.commit();
MetricDto metric = db.measures().insertMetric(m -> m.setKey(metricKey).setValueType(valueType.name()).setHidden(hidden));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());

expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Metric '" + metricKey + "' cannot be used to define a condition.");
underTest.createCondition(dbSession, qualityGateDto, metricKey, "EQ", null, "80", null);
expectedException.expectMessage(format("Metric '%s' cannot be used to define a condition.", metric.getKey()));

underTest.createCondition(db.getSession(), qualityGate, metric.getKey(), "EQ", null, "80", null);
}

@Test
public void fail_to_create_condition_on_not_allowed_operator() {
MetricDto metric = db.measures().insertMetric(m -> m.setValueType(PERCENT.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());

expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Operator UNKNOWN is not allowed for metric type PERCENT.");
underTest.createCondition(dbSession, qualityGateDto, "coverage", "UNKNOWN", null, "80", 2);

underTest.createCondition(db.getSession(), qualityGate, metric.getKey(), "UNKNOWN", null, "80", 2);
}

@Test
public void fail_to_create_condition_on_missing_period() {
dbClient.metricDao().insert(dbSession, newMetricDto()
.setKey("new_coverage")
.setValueType(INT.name())
.setHidden(false));
dbSession.commit();
MetricDto metric = db.measures().insertMetric(m -> m.setKey("new_coverage").setValueType(INT.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());

expectedException.expect(BadRequestException.class);
expectedException.expectMessage("A period must be selected for differential metrics.");
underTest.createCondition(dbSession, qualityGateDto, "new_coverage", "EQ", null, "90", null);

underTest.createCondition(db.getSession(), qualityGate, metric.getKey(), "EQ", null, "90", null);
}

@Test
public void fail_to_create_condition_on_invalid_period() {
MetricDto metric = db.measures().insertMetric(m -> m.setKey("new_coverage").setValueType(INT.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());

expectedException.expect(BadRequestException.class);
expectedException.expectMessage("The only valid quality gate period is 1, the leak period.");
underTest.createCondition(dbSession, qualityGateDto, "coverage", "EQ", null, "90", 6);

underTest.createCondition(db.getSession(), qualityGate, metric.getKey(), "EQ", null, "90", 6);
}

@Test
public void create_condition_on_rating_metric() {
QualityGateConditionDto result = underTest.createCondition(dbSession, qualityGateDto, ratingMetricDto.getKey(), "GT", null, "3", null);
MetricDto metric = db.measures().insertMetric(m -> m.setKey(SQALE_RATING_KEY).setValueType(RATING.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());

verifyCondition(result, ratingMetricDto.getId(), "GT", null, "3", null);
QualityGateConditionDto result = underTest.createCondition(db.getSession(), qualityGate, metric.getKey(), "GT", null, "3", null);

verifyCondition(result, qualityGate, metric, "GT", null, "3", null);
}

@Test
public void fail_to_create_condition_on_rating_metric_on_leak_period() {
MetricDto metric = db.measures().insertMetric(m -> m.setKey(SQALE_RATING_KEY).setValueType(RATING.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());

expectedException.expect(BadRequestException.class);
expectedException.expectMessage("The metric 'Reliability Rating' cannot be used on the leak period");
underTest.createCondition(dbSession, qualityGateDto, ratingMetricDto.getKey(), "GT", null, "3", 1);
expectedException.expectMessage(format("The metric '%s' cannot be used on the leak period", metric.getShortName()));

underTest.createCondition(db.getSession(), qualityGate, metric.getKey(), "GT", null, "3", 1);
}

@Test
public void fail_to_create_warning_condition_on_invalid_rating_metric() {
MetricDto metric = db.measures().insertMetric(m -> m.setKey(SQALE_RATING_KEY).setValueType(RATING.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());

expectedException.expect(BadRequestException.class);
expectedException.expectMessage("'6' is not a valid rating");
underTest.createCondition(dbSession, qualityGateDto, ratingMetricDto.getKey(), "GT", "6", null, null);

underTest.createCondition(db.getSession(), qualityGate, metric.getKey(), "GT", "6", null, null);
}

@Test
public void fail_to_create_error_condition_on_invalid_rating_metric() {
MetricDto metric = db.measures().insertMetric(m -> m.setKey(SQALE_RATING_KEY).setValueType(RATING.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());

expectedException.expect(BadRequestException.class);
expectedException.expectMessage("'80' is not a valid rating");
underTest.createCondition(dbSession, qualityGateDto, ratingMetricDto.getKey(), "GT", null, "80", null);

underTest.createCondition(db.getSession(), qualityGate, metric.getKey(), "GT", null, "80", null);
}

@Test
public void fail_to_create_condition_on_greater_than_E() {
MetricDto metric = db.measures().insertMetric(m -> m.setKey(SQALE_RATING_KEY).setValueType(RATING.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());

expectedException.expect(BadRequestException.class);
expectedException.expectMessage("There's no worse rating than E (5)");
underTest.createCondition(dbSession, qualityGateDto, ratingMetricDto.getKey(), "GT", "5", null, null);

underTest.createCondition(db.getSession(), qualityGate, metric.getKey(), "GT", "5", null, null);
}

@Test
@UseDataProvider("valid_values")
public void create_warning_condition(Metric.ValueType valueType, String value) {
MetricDto metric = db.measures().insertMetric(m -> m.setValueType(valueType.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());

QualityGateConditionDto result = underTest.createCondition(db.getSession(), qualityGate, metric.getKey(), "EQ", value, null, null);

verifyCondition(result, qualityGate, metric, "EQ", value, null, null);
}

@Test
@UseDataProvider("valid_values")
public void create_error_condition(Metric.ValueType valueType, String value) {
MetricDto metric = db.measures().insertMetric(m -> m.setValueType(valueType.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());

QualityGateConditionDto result = underTest.createCondition(db.getSession(), qualityGate, metric.getKey(), "EQ", null, value, null);

verifyCondition(result, qualityGate, metric, "EQ", null, value, null);
}

@Test
@UseDataProvider("invalid_values")
public void fail_to_create_warning_INT_condition_when_value_is_not_an_integer(Metric.ValueType valueType, String value) {
MetricDto metric = db.measures().insertMetric(m -> m.setValueType(valueType.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());

expectedException.expect(BadRequestException.class);
expectedException.expectMessage(format("Invalid value '%s' for metric '%s'", value, metric.getShortName()));

underTest.createCondition(db.getSession(), qualityGate, metric.getKey(), "EQ", value, null, null);
}

@Test
@UseDataProvider("invalid_values")
public void fail_to_create_error_INT_condition_when_value_is_not_an_integer(Metric.ValueType valueType, String value) {
MetricDto metric = db.measures().insertMetric(m -> m.setValueType(valueType.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());

expectedException.expect(BadRequestException.class);
expectedException.expectMessage(format("Invalid value '%s' for metric '%s'", value, metric.getShortName()));

underTest.createCondition(db.getSession(), qualityGate, metric.getKey(), "EQ", null, value, null);
}

@Test
public void update_condition() {
QualityGateConditionDto condition = insertCondition(coverageMetricDto.getId(), "LT", null, "80", null);
MetricDto metric = db.measures().insertMetric(m -> m.setValueType(PERCENT.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());
QualityGateConditionDto condition = db.qualityGates().addCondition(qualityGate, metric,
c -> c.setOperator("LT").setWarningThreshold(null).setErrorThreshold("80").setPeriod(null));

QualityGateConditionDto result = underTest.updateCondition(dbSession, condition, "coverage", "GT", "60", null, 1);
QualityGateConditionDto result = underTest.updateCondition(db.getSession(), condition, metric.getKey(), "GT", "60", null, 1);

verifyCondition(result, coverageMetricDto.getId(), "GT", "60", null, 1);
verifyCondition(result, qualityGate, metric, "GT", "60", null, 1);
}

@Test
public void update_condition_over_leak_period() {
QualityGateConditionDto condition = insertCondition(coverageMetricDto.getId(), "GT", "80", null, 1);
MetricDto metric = db.measures().insertMetric(m -> m.setValueType(PERCENT.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());
QualityGateConditionDto condition = db.qualityGates().addCondition(qualityGate, metric,
c -> c.setOperator("GT").setWarningThreshold("80").setErrorThreshold(null).setPeriod(1));

QualityGateConditionDto result = underTest.updateCondition(dbSession, condition, "coverage", "LT", null, "80", null);
QualityGateConditionDto result = underTest.updateCondition(db.getSession(), condition, metric.getKey(), "LT", null, "80", null);

verifyCondition(result, coverageMetricDto.getId(), "LT", null, "80", null);
verifyCondition(result, qualityGate, metric, "LT", null, "80", null);
}

@Test
public void update_condition_on_rating_metric() {
QualityGateConditionDto condition = insertCondition(ratingMetricDto.getId(), "LT", null, "3", null);
MetricDto metric = db.measures().insertMetric(m -> m.setKey(SQALE_RATING_KEY).setValueType(RATING.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());
QualityGateConditionDto condition = db.qualityGates().addCondition(qualityGate, metric,
c -> c.setOperator("LT").setWarningThreshold("80").setErrorThreshold(null).setPeriod(1));

QualityGateConditionDto result = underTest.updateCondition(dbSession, condition, ratingMetricDto.getKey(), "GT", "4", null, null);
QualityGateConditionDto result = underTest.updateCondition(db.getSession(), condition, metric.getKey(), "GT", "4", null, null);

verifyCondition(result, ratingMetricDto.getId(), "GT", "4", null, null);
verifyCondition(result, qualityGate, metric, "GT", "4", null, null);
}

@Test
public void fail_to_update_condition_on_rating_metric_on_leak_period() {
QualityGateConditionDto condition = insertCondition(ratingMetricDto.getId(), "LT", null, "3", null);
MetricDto metric = db.measures().insertMetric(m -> m.setKey(SQALE_RATING_KEY).setValueType(RATING.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());
QualityGateConditionDto condition = db.qualityGates().addCondition(qualityGate, metric,
c -> c.setOperator("LT").setWarningThreshold(null).setErrorThreshold("3").setPeriod(null));

expectedException.expect(BadRequestException.class);
expectedException.expectMessage("The metric 'Reliability Rating' cannot be used on the leak period");
underTest.updateCondition(dbSession, condition, ratingMetricDto.getKey(), "GT", "4", null, 1);
expectedException.expectMessage(format("The metric '%s' cannot be used on the leak period", metric.getShortName()));

underTest.updateCondition(db.getSession(), condition, metric.getKey(), "GT", "4", null, 1);
}

@Test
public void fail_to_update_condition_on_rating_metric_on_not_core_rating_metric() {
MetricDto metricDto = dbClient.metricDao().insert(dbSession, newMetricDto().setKey("rating_metric")
.setShortName("Not core rating")
.setValueType(RATING.name()).setHidden(false));
QualityGateConditionDto condition = insertCondition(metricDto.getId(), "LT", null, "3", null);
dbSession.commit();
MetricDto metric = db.measures().insertMetric(m -> m.setKey("not_core_rating_metric").setValueType(RATING.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());
QualityGateConditionDto condition = db.qualityGates().addCondition(qualityGate, metric,
c -> c.setOperator("LT").setWarningThreshold(null).setErrorThreshold("3").setPeriod(null));

expectedException.expect(BadRequestException.class);
expectedException.expectMessage("The metric 'Not core rating' cannot be used");
underTest.updateCondition(dbSession, condition, metricDto.getKey(), "GT", "4", null, 1);
expectedException.expectMessage(format("The metric '%s' cannot be used", metric.getShortName()));

underTest.updateCondition(db.getSession(), condition, metric.getKey(), "GT", "4", null, 1);
}

@Test
@UseDataProvider("invalid_metrics")
public void fail_to_update_condition_on_invalid_metric(String metricKey, Metric.ValueType valueType, boolean hidden) {
MetricDto metricDto = dbClient.metricDao().insert(dbSession, newMetricDto()
.setKey(metricKey)
.setValueType(valueType.name())
.setHidden(hidden));
QualityGateConditionDto condition = insertCondition(metricDto.getId(), "LT", null, "80", null);
dbSession.commit();
MetricDto metric = db.measures().insertMetric(m -> m.setKey(metricKey).setValueType(valueType.name()).setHidden(hidden));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());
QualityGateConditionDto condition = db.qualityGates().addCondition(qualityGate, metric,
c -> c.setOperator("LT").setWarningThreshold(null).setErrorThreshold("80").setPeriod(null));

expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Metric '" + metricKey + "' cannot be used to define a condition.");
underTest.updateCondition(dbSession, condition, metricDto.getKey(), "GT", "60", null, 1);
expectedException.expectMessage(format("Metric '%s' cannot be used to define a condition.", metric.getKey()));

underTest.updateCondition(db.getSession(), condition, metric.getKey(), "GT", "60", null, 1);
}

@Test
public void fail_to_update_condition_when_condition_on_same_metric_already_exist() {
QualityGateConditionDto conditionNotOnLeakPeriod = insertCondition(coverageMetricDto.getId(), "GT", "80", null, null);
QualityGateConditionDto conditionOnLeakPeriod = insertCondition(coverageMetricDto.getId(), "GT", "80", null, 1);
MetricDto metric = db.measures().insertMetric(m -> m.setValueType(INT.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());
QualityGateConditionDto conditionNotOnLeakPeriod = db.qualityGates().addCondition(qualityGate, metric,
c -> c.setOperator("GT").setWarningThreshold("80").setErrorThreshold(null).setPeriod(null));
QualityGateConditionDto conditionOnLeakPeriod = db.qualityGates().addCondition(qualityGate, metric,
c -> c.setOperator("GT").setWarningThreshold("80").setErrorThreshold(null).setPeriod(1));

expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Condition on metric 'Coverage' over leak period already exists.");
expectedException.expectMessage(format("Condition on metric '%s' over leak period already exists.", metric.getShortName()));

// Update condition not on leak period to be on leak period => will fail as this condition already exist
underTest.updateCondition(dbSession, conditionNotOnLeakPeriod, coverageMetricDto.getKey(), "GT", "80", null, 1);
underTest.updateCondition(db.getSession(), conditionNotOnLeakPeriod, metric.getKey(), "GT", "80", null, 1);
}

@Test
@UseDataProvider("valid_values")
public void update_warning_condition(Metric.ValueType valueType, String value) {
MetricDto metric = db.measures().insertMetric(m -> m.setValueType(valueType.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());
QualityGateConditionDto condition = db.qualityGates().addCondition(qualityGate, metric,
c -> c.setOperator("LT").setWarningThreshold(null).setErrorThreshold("80").setPeriod(null));

QualityGateConditionDto result = underTest.updateCondition(db.getSession(), condition, metric.getKey(), "EQ", value, null, null);

verifyCondition(result, qualityGate, metric, "EQ", value, null, null);
}

@Test
@UseDataProvider("valid_values")
public void update_error_condition(Metric.ValueType valueType, String value) {
MetricDto metric = db.measures().insertMetric(m -> m.setValueType(valueType.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());
QualityGateConditionDto condition = db.qualityGates().addCondition(qualityGate, metric,
c -> c.setOperator("LT").setWarningThreshold(null).setErrorThreshold("80").setPeriod(null));

QualityGateConditionDto result = underTest.updateCondition(db.getSession(), condition, metric.getKey(), "EQ", null, value, null);

verifyCondition(result, qualityGate, metric, "EQ", null, value, null);
}

@Test
@UseDataProvider("invalid_values")
public void fail_to_update_warning_INT_condition_when_value_is_not_an_integer(Metric.ValueType valueType, String value) {
MetricDto metric = db.measures().insertMetric(m -> m.setValueType(valueType.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());
QualityGateConditionDto condition = db.qualityGates().addCondition(qualityGate, metric,
c -> c.setOperator("LT").setWarningThreshold(null).setErrorThreshold("80").setPeriod(null));

expectedException.expect(BadRequestException.class);
expectedException.expectMessage(format("Invalid value '%s' for metric '%s'", value, metric.getShortName()));

underTest.updateCondition(db.getSession(), condition, metric.getKey(), "EQ", value, null, null);
}

@Test
@UseDataProvider("invalid_values")
public void fail_to_update_error_INT_condition_when_value_is_not_an_integer(Metric.ValueType valueType, String value) {
MetricDto metric = db.measures().insertMetric(m -> m.setValueType(valueType.name()).setHidden(false));
QualityGateDto qualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization());
QualityGateConditionDto condition = db.qualityGates().addCondition(qualityGate, metric,
c -> c.setOperator("LT").setWarningThreshold(null).setErrorThreshold("80").setPeriod(null));

expectedException.expect(BadRequestException.class);
expectedException.expectMessage(format("Invalid value '%s' for metric '%s'", value, metric.getShortName()));

underTest.updateCondition(db.getSession(), condition, metric.getKey(), "EQ", null, value, null);
}

@DataProvider
@@ -296,30 +414,42 @@ public class QualityGateConditionsUpdaterTest {
};
}

private QualityGateConditionDto insertCondition(long metricId, String operator, @Nullable String warning, @Nullable String error,
@DataProvider
public static Object[][] valid_values() {
return new Object[][] {
{INT, "10"},
{BOOL, "1"},
{MILLISEC, "1000"},
{WORK_DUR, "1000"},
{FLOAT, "5.12"},
{PERCENT, "10.30"},
};
}

@DataProvider
public static Object[][] invalid_values() {
return new Object[][] {
{INT, "ABCD"},
{BOOL, "ABCD"},
{MILLISEC, "ABCD"},
{WORK_DUR, "ABCD"},
{FLOAT, "ABCD"},
{PERCENT, "ABCD"},
};
}

private void verifyCondition(QualityGateConditionDto dto, QualityGateDto qualityGate, MetricDto metric, String operator, @Nullable String warning, @Nullable String error,
@Nullable Integer period) {
QualityGateConditionDto qualityGateConditionDto = new QualityGateConditionDto().setQualityGateId(qualityGateDto.getId())
.setMetricId(metricId)
.setOperator(operator)
.setWarningThreshold(warning)
.setErrorThreshold(error)
.setPeriod(period);
dbClient.gateConditionDao().insert(qualityGateConditionDto, dbSession);
dbSession.commit();
return qualityGateConditionDto;
}

private void verifyCondition(QualityGateConditionDto dto, int metricId, String operator, @Nullable String warning, @Nullable String error, @Nullable Integer period) {
QualityGateConditionDto reloaded = dbClient.gateConditionDao().selectById(dto.getId(), dbSession);
assertThat(reloaded.getQualityGateId()).isEqualTo(qualityGateDto.getId());
assertThat(reloaded.getMetricId()).isEqualTo(metricId);
QualityGateConditionDto reloaded = db.getDbClient().gateConditionDao().selectById(dto.getId(), db.getSession());
assertThat(reloaded.getQualityGateId()).isEqualTo(qualityGate.getId());
assertThat(reloaded.getMetricId()).isEqualTo(metric.getId().longValue());
assertThat(reloaded.getOperator()).isEqualTo(operator);
assertThat(reloaded.getWarningThreshold()).isEqualTo(warning);
assertThat(reloaded.getErrorThreshold()).isEqualTo(error);
assertThat(reloaded.getPeriod()).isEqualTo(period);

assertThat(dto.getQualityGateId()).isEqualTo(qualityGateDto.getId());
assertThat(dto.getMetricId()).isEqualTo(metricId);
assertThat(dto.getQualityGateId()).isEqualTo(qualityGate.getId());
assertThat(dto.getMetricId()).isEqualTo(metric.getId().longValue());
assertThat(dto.getOperator()).isEqualTo(operator);
assertThat(dto.getWarningThreshold()).isEqualTo(warning);
assertThat(dto.getErrorThreshold()).isEqualTo(error);

+ 38
- 0
server/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/CreateConditionActionTest.java View File

@@ -147,6 +147,44 @@ public class CreateConditionActionTest {
assertCondition(qualityGate, metric, "LT", "90", null, null);
}

@Test
public void create_warning_condition_with_empty_string_on_error() {
OrganizationDto organization = db.organizations().insert();
logInAsQualityGateAdmin(organization);
QGateWithOrgDto qualityGate = db.qualityGates().insertQualityGate(organization);
MetricDto metric = insertMetric();

ws.newRequest()
.setParam(PARAM_GATE_ID, qualityGate.getId().toString())
.setParam(PARAM_METRIC, metric.getKey())
.setParam(PARAM_OPERATOR, "LT")
.setParam(PARAM_WARNING, "90")
.setParam(PARAM_ERROR, "")
.setParam(PARAM_ORGANIZATION, organization.getKey())
.execute();

assertCondition(qualityGate, metric, "LT", "90", null, null);
}

@Test
public void create_error_condition_with_empty_string_on_warning() {
OrganizationDto organization = db.organizations().insert();
logInAsQualityGateAdmin(organization);
QGateWithOrgDto qualityGate = db.qualityGates().insertQualityGate(organization);
MetricDto metric = insertMetric();

ws.newRequest()
.setParam(PARAM_GATE_ID, qualityGate.getId().toString())
.setParam(PARAM_METRIC, metric.getKey())
.setParam(PARAM_OPERATOR, "LT")
.setParam(PARAM_WARNING, "")
.setParam(PARAM_ERROR, "90")
.setParam(PARAM_ORGANIZATION, organization.getKey())
.execute();

assertCondition(qualityGate, metric, "LT", null, "90", null);
}

@Test
public void fail_to_update_built_in_quality_gate() {
OrganizationDto organization = db.organizations().insert();

+ 42
- 0
server/sonar-server/src/test/java/org/sonar/server/qualitygate/ws/UpdateConditionActionTest.java View File

@@ -151,6 +151,48 @@ public class UpdateConditionActionTest {
assertCondition(qualityGate, metric, "LT", "90", null, null);
}

@Test
public void update_warning_condition_with_empty_string_on_error() {
OrganizationDto organization = db.organizations().insert();
userSession.addPermission(ADMINISTER_QUALITY_GATES, organization);
QGateWithOrgDto qualityGate = db.qualityGates().insertQualityGate(organization);
MetricDto metric = insertMetric();
QualityGateConditionDto condition = db.qualityGates().addCondition(qualityGate, metric,
c -> c.setOperator("GT").setWarningThreshold(null).setErrorThreshold("80").setPeriod(null));

ws.newRequest()
.setParam(PARAM_ORGANIZATION, organization.getKey())
.setParam(PARAM_ID, Long.toString(condition.getId()))
.setParam(PARAM_METRIC, metric.getKey())
.setParam(PARAM_OPERATOR, "LT")
.setParam(PARAM_WARNING, "90")
.setParam(PARAM_ERROR, "")
.execute();

assertCondition(qualityGate, metric, "LT", "90", null, null);
}

@Test
public void update_error_condition_with_empty_string_on_warning() {
OrganizationDto organization = db.organizations().insert();
userSession.addPermission(ADMINISTER_QUALITY_GATES, organization);
QGateWithOrgDto qualityGate = db.qualityGates().insertQualityGate(organization);
MetricDto metric = insertMetric();
QualityGateConditionDto condition = db.qualityGates().addCondition(qualityGate, metric,
c -> c.setOperator("GT").setWarningThreshold(null).setErrorThreshold("80").setPeriod(null));

ws.newRequest()
.setParam(PARAM_ORGANIZATION, organization.getKey())
.setParam(PARAM_ID, Long.toString(condition.getId()))
.setParam(PARAM_METRIC, metric.getKey())
.setParam(PARAM_OPERATOR, "LT")
.setParam(PARAM_ERROR, "90")
.setParam(PARAM_WARNING, "")
.execute();

assertCondition(qualityGate, metric, "LT", null, "90", null);
}

@Test
public void test_response() {
OrganizationDto organization = db.organizations().insert();

+ 111
- 157
tests/src/test/java/org/sonarqube/tests/qualityGate/QualityGateTest.java View File

@@ -19,6 +19,7 @@
*/
package org.sonarqube.tests.qualityGate;

import com.google.common.collect.ImmutableList;
import com.google.gson.Gson;
import com.sonar.orchestrator.Orchestrator;
import com.sonar.orchestrator.build.BuildResult;
@@ -34,49 +35,45 @@ import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.apache.commons.io.Charsets;
import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.wsclient.qualitygate.NewCondition;
import org.sonar.wsclient.qualitygate.QualityGate;
import org.sonar.wsclient.qualitygate.QualityGateClient;
import org.sonarqube.qa.util.QGateTester;
import org.sonarqube.qa.util.Tester;
import org.sonarqube.qa.util.TesterSession;
import org.sonarqube.ws.Ce;
import org.sonarqube.ws.Measures.Measure;
import org.sonarqube.ws.MediaTypes;
import org.sonarqube.ws.Organizations.Organization;
import org.sonarqube.ws.Projects.CreateWsResponse.Project;
import org.sonarqube.ws.Qualitygates;
import org.sonarqube.ws.Qualitygates.CreateResponse;
import org.sonarqube.ws.Qualitygates.ListWsResponse.QualityGate;
import org.sonarqube.ws.Qualitygates.ProjectStatusResponse;
import org.sonarqube.ws.Users;
import org.sonarqube.ws.client.GetRequest;
import org.sonarqube.ws.client.PostRequest;
import org.sonarqube.ws.client.WsResponse;
import org.sonarqube.ws.client.ce.TaskRequest;
import org.sonarqube.ws.client.metrics.CreateRequest;
import org.sonarqube.ws.client.metrics.DeleteRequest;
import org.sonarqube.ws.client.permissions.AddUserRequest;
import org.sonarqube.ws.client.qualitygates.CreateConditionRequest;
import org.sonarqube.ws.client.qualitygates.DeleteConditionRequest;
import org.sonarqube.ws.client.qualitygates.DestroyRequest;
import org.sonarqube.ws.client.qualitygates.ListRequest;
import org.sonarqube.ws.client.qualitygates.ProjectStatusRequest;
import org.sonarqube.ws.client.qualitygates.QualitygatesService;
import org.sonarqube.ws.client.qualitygates.SelectRequest;
import org.sonarqube.ws.client.qualitygates.SetAsDefaultRequest;
import org.sonarqube.ws.client.qualitygates.UpdateConditionRequest;

import static java.lang.String.format;
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.groups.Tuple.tuple;
import static org.sonarqube.ws.Qualitygates.ProjectStatusResponse.Status.ERROR;
import static util.ItUtils.concat;
import static util.ItUtils.expectHttpError;
import static util.ItUtils.extractCeTaskId;
import static util.ItUtils.getMeasure;
import static util.ItUtils.newProjectKey;
import static util.ItUtils.projectDir;

public class QualityGateTest {

private static final String TASK_STATUS_SUCCESS = "SUCCESS";
private static final String QG_STATUS_NO_QG = "null";
private static final String QG_STATUS_OK = "OK";
private static final String QG_STATUS_ERROR = "ERROR";
private static final String QG_STATUS_WARN = "WARN";
@@ -85,114 +82,101 @@ public class QualityGateTest {
public static Orchestrator orchestrator = QualityGateSuite.ORCHESTRATOR;

@Rule
public Tester tester = new Tester(orchestrator)
// all the tests of QualityGateSuite must disable organizations
.disableOrganizations();

private QualityGate defaultGate;

@Before
public void setUp() {
defaultGate = qgClient().list().defaultGate();
}

@After
public void tearDown() {
if (defaultGate != null) {
qgClient().setDefault(defaultGate.id());
}
}
public Tester tester = new Tester(orchestrator).disableOrganizations();

@Test
public void status_ok_if_empty_gate() throws Exception {
Qualitygates.CreateResponse empty = tester.qGates().generate();
qgClient().setDefault(empty.getId());

String projectKey = newProjectKey();
BuildResult buildResult = executeAnalysis(projectKey);
Project project = tester.projects().provision();
Qualitygates.CreateResponse qualityGate = tester.qGates().generate();
tester.qGates().associateProject(qualityGate, project);
BuildResult buildResult = executeAnalysis(project.getKey());

verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_OK);
verifyQGStatusInPostTask(buildResult, project.getKey(), TASK_STATUS_SUCCESS, QG_STATUS_OK);

assertThat(getGateStatusMeasure(projectKey).getValue()).isEqualTo("OK");
assertThat(getGateStatusMeasure(project.getKey()).getValue()).isEqualTo("OK");
}

@Test
public void test_status_ok() throws IOException {
Qualitygates.CreateResponse simple = tester.qGates().generate();
qgClient().setDefault(simple.getId());
qgClient().createCondition(NewCondition.create(simple.getId()).metricKey("ncloc").operator("GT").warningThreshold("40"));

String projectKey = newProjectKey();
BuildResult buildResult = executeAnalysis(projectKey);
Project project = tester.projects().provision();
Qualitygates.CreateResponse qualityGate = tester.qGates().generate();
tester.qGates().associateProject(qualityGate, project);
tester.qGates().service().createCondition(new CreateConditionRequest().setGateId(Long.toString(qualityGate.getId())).setMetric("ncloc").setOp("GT").setWarning("40"));
BuildResult buildResult = executeAnalysis(project.getKey());

verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_OK);
verifyQGStatusInPostTask(buildResult, project.getKey(), TASK_STATUS_SUCCESS, QG_STATUS_OK);

assertThat(getGateStatusMeasure(projectKey).getValue()).isEqualTo("OK");
assertThat(getGateStatusMeasure(project.getKey()).getValue()).isEqualTo("OK");
}

@Test
public void test_status_warning() throws IOException {
Qualitygates.CreateResponse simple = tester.qGates().generate();
qgClient().setDefault(simple.getId());
qgClient().createCondition(NewCondition.create(simple.getId()).metricKey("ncloc").operator("GT").warningThreshold("10"));

String projectKey = newProjectKey();
BuildResult buildResult = executeAnalysis(projectKey);
Project project = tester.projects().provision();
Qualitygates.CreateResponse qualityGate = tester.qGates().generate();
tester.qGates().associateProject(qualityGate, project);
tester.qGates().service().createCondition(new CreateConditionRequest().setGateId(Long.toString(qualityGate.getId())).setMetric("ncloc").setOp("GT").setWarning("10"));
BuildResult buildResult = executeAnalysis(project.getKey());

verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_WARN);
verifyQGStatusInPostTask(buildResult, project.getKey(), TASK_STATUS_SUCCESS, QG_STATUS_WARN);

assertThat(getGateStatusMeasure(projectKey).getValue()).isEqualTo("WARN");
assertThat(getGateStatusMeasure(project.getKey()).getValue()).isEqualTo("WARN");
}

@Test
public void test_status_error() throws IOException {
Qualitygates.CreateResponse simple = tester.qGates().generate();
qgClient().setDefault(simple.getId());
qgClient().createCondition(NewCondition.create(simple.getId()).metricKey("ncloc").operator("GT").errorThreshold("10"));

String projectKey = newProjectKey();
BuildResult buildResult = executeAnalysis(projectKey);
Project project = tester.projects().provision();
Qualitygates.CreateResponse qualityGate = tester.qGates().generate();
tester.qGates().associateProject(qualityGate, project);
tester.qGates().service().createCondition(new CreateConditionRequest().setGateId(Long.toString(qualityGate.getId())).setMetric("ncloc").setOp("GT").setError("10"));
BuildResult buildResult = executeAnalysis(project.getKey());

verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_ERROR);
verifyQGStatusInPostTask(buildResult, project.getKey(), TASK_STATUS_SUCCESS, QG_STATUS_ERROR);

assertThat(getGateStatusMeasure(projectKey).getValue()).isEqualTo("ERROR");
assertThat(getGateStatusMeasure(project.getKey()).getValue()).isEqualTo("ERROR");
}

@Test
public void use_server_settings_instead_of_default_gate() throws IOException {
Qualitygates.CreateResponse alert = tester.qGates().generate();
qgClient().createCondition(NewCondition.create(alert.getId()).metricKey("ncloc").operator("GT").warningThreshold("10"));
Qualitygates.CreateResponse error = tester.qGates().generate();
qgClient().createCondition(NewCondition.create(error.getId()).metricKey("ncloc").operator("GT").errorThreshold("10"));
qgClient().setDefault(alert.getId());
String projectKey = newProjectKey();
orchestrator.getServer().provisionProject(projectKey, projectKey);
associateQualityGateToProject(error.getId(), projectKey);
QualityGate existingDefaultQualityGate = tester.qGates().service().list(new ListRequest()).getQualitygatesList()
.stream()
.filter(QualityGate::getIsDefault)
.findFirst()
.orElseThrow(() -> new IllegalStateException("No default quality gate found"));
try {
Qualitygates.CreateResponse defaultQualityGate = tester.qGates().generate();
tester.qGates().service().createCondition(new CreateConditionRequest().setGateId(Long.toString(defaultQualityGate.getId())).setMetric("ncloc").setOp("GT").setWarning("10"));
tester.qGates().service().setAsDefault(new SetAsDefaultRequest().setId(Long.toString(defaultQualityGate.getId())));

BuildResult buildResult = executeAnalysis(projectKey);
Project project = tester.projects().provision();
Qualitygates.CreateResponse qualityGate = tester.qGates().generate();
tester.qGates().service().createCondition(new CreateConditionRequest().setGateId(Long.toString(qualityGate.getId())).setMetric("ncloc").setOp("GT").setError("10"));
tester.qGates().associateProject(qualityGate, project);

verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_ERROR);
BuildResult buildResult = executeAnalysis(project.getKey());

assertThat(getGateStatusMeasure(projectKey).getValue()).isEqualTo("ERROR");
verifyQGStatusInPostTask(buildResult, project.getKey(), TASK_STATUS_SUCCESS, QG_STATUS_ERROR);
assertThat(getGateStatusMeasure(project.getKey()).getValue()).isEqualTo("ERROR");
} finally {
tester.qGates().service().setAsDefault(new SetAsDefaultRequest().setId(Long.toString(existingDefaultQualityGate.getId())));
}
}

@Test
public void conditions_on_multiple_metric_types() throws IOException {
Qualitygates.CreateResponse allTypes = tester.qGates().generate();
qgClient().createCondition(NewCondition.create(allTypes.getId()).metricKey("ncloc").operator("GT").warningThreshold("10"));
qgClient().createCondition(NewCondition.create(allTypes.getId()).metricKey("duplicated_lines_density").operator("GT").warningThreshold("20"));
qgClient().setDefault(allTypes.getId());
String projectKey = newProjectKey();
BuildResult buildResult = executeAnalysis(projectKey, "sonar.cpd.xoo.minimumLines", "2", "sonar.cpd.xoo.minimumTokens", "5");
Project project = tester.projects().provision();
Qualitygates.CreateResponse qualityGate = tester.qGates().generate();
tester.qGates().associateProject(qualityGate, project);
tester.qGates().service().createCondition(new CreateConditionRequest().setGateId(Long.toString(qualityGate.getId())).setMetric("ncloc").setOp("GT").setWarning("10"));
tester.qGates().service()
.createCondition(new CreateConditionRequest().setGateId(Long.toString(qualityGate.getId())).setMetric("duplicated_lines_density").setOp("GT").setWarning("20"));
BuildResult buildResult = executeAnalysis(project.getKey(), "sonar.cpd.xoo.minimumLines", "2", "sonar.cpd.xoo.minimumTokens", "5");

verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_WARN);
verifyQGStatusInPostTask(buildResult, project.getKey(), TASK_STATUS_SUCCESS, QG_STATUS_WARN);

Measure alertStatus = getGateStatusMeasure(projectKey);
Measure alertStatus = getGateStatusMeasure(project.getKey());
assertThat(alertStatus.getValue()).isEqualTo("WARN");

String qualityGateDetailJson = getMeasure(orchestrator, projectKey, "quality_gate_details").getValue();
String qualityGateDetailJson = getMeasure(orchestrator, project.getKey(), "quality_gate_details").getValue();
assertThat(QualityGateDetails.parse(qualityGateDetailJson).getConditions())
.extracting(QualityGateDetails.Conditions::getMetric, QualityGateDetails.Conditions::getOp, QualityGateDetails.Conditions::getWarning)
.contains(tuple("ncloc", "GT", "10"), tuple("duplicated_lines_density", "GT", "20"));
@@ -200,19 +184,18 @@ public class QualityGateTest {

@Test
public void ad_hoc_build_break_strategy() throws IOException {
Qualitygates.CreateResponse simple = tester.qGates().generate();
qgClient().setDefault(simple.getId());
qgClient().createCondition(NewCondition.create(simple.getId()).metricKey("ncloc").operator("GT").errorThreshold("7"));

String projectKey = newProjectKey();
BuildResult buildResult = executeAnalysis(projectKey);
Project project = tester.projects().provision();
Qualitygates.CreateResponse qualityGate = tester.qGates().generate();
tester.qGates().associateProject(qualityGate, project);
tester.qGates().service().createCondition(new CreateConditionRequest().setGateId(Long.toString(qualityGate.getId())).setMetric("ncloc").setOp("GT").setError("7"));
BuildResult buildResult = executeAnalysis(project.getKey());

verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_ERROR);
verifyQGStatusInPostTask(buildResult, project.getKey(), TASK_STATUS_SUCCESS, QG_STATUS_ERROR);

String taskId = getTaskIdInLocalReport(projectDir("qualitygate/xoo-sample"));
String analysisId = getAnalysisId(taskId);
String analysisId = tester.wsClient().ce().task(new TaskRequest().setId(taskId)).getTask().getAnalysisId();

ProjectStatusResponse projectStatusWsResponse = tester.wsClient().qualitygates().projectStatus(new ProjectStatusRequest().setAnalysisId(analysisId));
ProjectStatusResponse projectStatusWsResponse = tester.qGates().service().projectStatus(new ProjectStatusRequest().setAnalysisId(analysisId));
ProjectStatusResponse.ProjectStatus projectStatus = projectStatusWsResponse.getProjectStatus();
assertThat(projectStatus.getStatus()).isEqualTo(ERROR);
assertThat(projectStatus.getConditionsCount()).isEqualTo(1);
@@ -223,52 +206,59 @@ public class QualityGateTest {

@Test
public void does_not_fail_when_condition_is_on_removed_metric() throws Exception {
// create project
Project project = tester.projects().provision();
String projectKey = project.getKey();

// create custom metric
String customMetricKey = randomAlphabetic(10);
createCustomIntMetric(customMetricKey);
tester.wsClient().metrics().create(new CreateRequest().setKey(customMetricKey).setName(customMetricKey).setType("INT"));
try {
// create quality gate
Qualitygates.CreateResponse simple = tester.qGates().generate();
Long qualityGateId = simple.getId();
qgClient().createCondition(NewCondition.create(qualityGateId).metricKey(customMetricKey).operator("GT").warningThreshold("40"));

Qualitygates.CreateResponse qualityGate = tester.qGates().generate();
Long qualityGateId = qualityGate.getId();
tester.qGates().service().createCondition(new CreateConditionRequest().setGateId(Long.toString(qualityGate.getId())).setMetric(customMetricKey).setOp("GT").setWarning("40"));
// delete custom metric
deleteCustomMetric(customMetricKey);
tester.wsClient().metrics().delete(new DeleteRequest().setKeys(ImmutableList.of(customMetricKey)));

// run analysis
tester.wsClient().qualitygates().select(new SelectRequest().setProjectKey(projectKey).setGateId(String.valueOf(qualityGateId)));
BuildResult buildResult = executeAnalysis(projectKey);
tester.qGates().service().select(new SelectRequest().setProjectKey(project.getKey()).setGateId(String.valueOf(qualityGateId)));
BuildResult buildResult = executeAnalysis(project.getKey());

// verify quality gate
verifyQGStatusInPostTask(buildResult, projectKey, TASK_STATUS_SUCCESS, QG_STATUS_OK);
assertThat(getGateStatusMeasure(projectKey).getValue()).isEqualTo("OK");
verifyQGStatusInPostTask(buildResult, project.getKey(), TASK_STATUS_SUCCESS, QG_STATUS_OK);
assertThat(getGateStatusMeasure(project.getKey()).getValue()).isEqualTo("OK");
} finally {
deleteCustomMetric(customMetricKey);
tester.wsClient().metrics().delete(new DeleteRequest().setKeys(ImmutableList.of(customMetricKey)));
}
}

@Test
public void administrate_quality_gate_with_gateadmin_permission() {
// user is quality gate admin of default organization
Organization organization = tester.organizations().getDefaultOrganization();
Users.CreateWsResponse.User user = tester.users().generateMember(organization);
tester.wsClient().permissions().addUser(new AddUserRequest().setLogin(user.getLogin()).setPermission("gateadmin").setOrganization(organization.getKey()));
TesterSession qGateAdminTester = tester.as(user.getLogin());
QualitygatesService qGateService = qGateAdminTester.qGates().service();
Users.CreateWsResponse.User user = tester.users().generate();
tester.wsClient().permissions().addUser(new AddUserRequest().setLogin(user.getLogin()).setPermission("gateadmin"));
QGateTester qGateAdminTester = tester.as(user.getLogin()).qGates();

// perform administration operations
CreateResponse qualityGate = qGateAdminTester.qGates().generate();
Qualitygates.CreateConditionResponse condition = qGateService.createCondition(new CreateConditionRequest()
CreateResponse qualityGate = qGateAdminTester.generate();
Qualitygates.CreateConditionResponse condition = qGateAdminTester.service().createCondition(new CreateConditionRequest()
.setGateId(String.valueOf(qualityGate.getId())).setMetric("coverage").setOp("LT").setError("90"));
qGateService.updateCondition(new UpdateConditionRequest()
qGateAdminTester.service().updateCondition(new UpdateConditionRequest()
.setId(String.valueOf(condition.getId())).setMetric("coverage").setOp("LT").setError("90").setWarning("80"));
qGateAdminTester.wsClient().wsConnector().call(new PostRequest("api/qualitygates/set_as_default").setParam("id", qualityGate.getId()));
qGateAdminTester.wsClient().wsConnector().call(new PostRequest("api/qualitygates/delete_condition").setParam("id", condition.getId()));
qGateAdminTester.wsClient().wsConnector().call(new PostRequest("api/qualitygates/unset_default").setParam("id", qualityGate.getId()));
qGateAdminTester.wsClient().wsConnector().call(new PostRequest("api/qualitygates/destroy").setParam("id", qualityGate.getId()));
qGateAdminTester.service().deleteCondition(new DeleteConditionRequest().setId(Long.toString(condition.getId())));
qGateAdminTester.service().destroy(new DestroyRequest().setId(Long.toString(qualityGate.getId())));
}

@Test
public void fail_to_create_and_update_conditions_when_using_invalid_values() {
Qualitygates.CreateResponse qualityGate = tester.qGates().generate();

expectHttpError(400,
format("Invalid value 'INVALID' for metric 'ncloc'"),
() -> tester.qGates().service().createCondition(new CreateConditionRequest().setGateId(Long.toString(qualityGate.getId())).setMetric("ncloc").setOp("GT").setWarning("INVALID")));
expectHttpError(400,
format("User '%s' is not member of organization '%s'"),
() -> tester.qGates().service().createCondition(new CreateConditionRequest().setGateId(Long.toString(qualityGate.getId())).setMetric("sqale_index").setOp("GT").setWarning("10d")));
expectHttpError(400,
format("User '%s' is not member of organization '%s'"),
() -> tester.qGates().service().createCondition(new CreateConditionRequest().setGateId(Long.toString(qualityGate.getId())).setMetric("coverage").setOp("GT").setWarning("10%")));
}

private BuildResult executeAnalysis(String projectKey, String... keyValueProperties) {
@@ -287,16 +277,6 @@ public class QualityGateTest {
.contains("QualityGate[" + qgStatus + "]");
}

private String getAnalysisId(String taskId) throws IOException {
WsResponse activity = tester.wsClient()
.wsConnector()
.call(new GetRequest("api/ce/task")
.setParam("id", taskId)
.setMediaType(MediaTypes.PROTOBUF));
Ce.TaskResponse activityWsResponse = Ce.TaskResponse.parseFrom(activity.contentStream());
return activityWsResponse.getTask().getAnalysisId();
}

private String getTaskIdInLocalReport(File projectDirectory) throws IOException {
File metadata = new File(projectDirectory, ".sonar/report-task.txt");
assertThat(metadata).exists().isFile();
@@ -312,18 +292,6 @@ public class QualityGateTest {
return getMeasure(orchestrator, projectKey, "alert_status");
}

private QualityGateClient qgClient() {
return orchestrator.getServer().adminWsClient().qualityGateClient();
}

private void associateQualityGateToProject(long qGateId, String projectKey) {
tester.wsClient().wsConnector()
.call(new PostRequest("api/qualitygates/select")
.setParam("gateId", qGateId)
.setParam("projectKey", projectKey))
.failIfNotSuccessful();
}

private static List<String> extractPosttaskPluginLogs(String taskUuid, Iterable<String> ceLogs) {
return StreamSupport.stream(ceLogs.spliterator(), false)
.filter(s -> s.contains("POSTASKPLUGIN: finished()"))
@@ -331,20 +299,6 @@ public class QualityGateTest {
.collect(Collectors.toList());
}

private void createCustomIntMetric(String metricKey) {
tester.wsClient().wsConnector().call(new PostRequest("api/metrics/create")
.setParam("key", metricKey)
.setParam("name", metricKey)
.setParam("type", "INT"))
.failIfNotSuccessful();
}

private void deleteCustomMetric(String metricKey) {
tester.wsClient().wsConnector().call(new PostRequest("api/metrics/delete")
.setParam("keys", metricKey))
.failIfNotSuccessful();
}

static class QualityGateDetails {

private String level;

+ 2
- 12
tests/src/test/java/util/ItUtils.java View File

@@ -85,7 +85,6 @@ import static com.sonar.orchestrator.container.Server.ADMIN_PASSWORD;
import static java.lang.Double.parseDouble;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static java.util.Locale.ENGLISH;
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
@@ -314,7 +313,7 @@ public class ItUtils {
}

/**
* @deprecated no more needed as already done by n by {@link Tester#after()}
* @deprecated no more needed as already done by {@link Tester#after()}
*/
@Deprecated
public static void resetEmailSettings(Orchestrator orchestrator) {
@@ -323,7 +322,7 @@ public class ItUtils {
}

/**
* @deprecated no more needed as already done by n by {@link Tester#after()}
* @deprecated no more needed as already done by {@link Tester#after()}
*/
@Deprecated
public static void resetPeriod(Orchestrator orchestrator) {
@@ -432,10 +431,6 @@ public class ItUtils {
.build().call(httpRequest);
}

public static String newOrganizationKey() {
return randomAlphabetic(32).toLowerCase(ENGLISH);
}

public static String newProjectKey() {
return "key-" + randomAlphabetic(100);
}
@@ -499,11 +494,6 @@ public class ItUtils {
return sdf.format(d);
}

public static String formatDateTime(Date d) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
return sdf.format(d);
}

public static String extractCeTaskId(BuildResult buildResult) {
List<String> taskIds = extractCeTaskIds(buildResult);
checkState(taskIds.size() == 1, "More than one task id retrieved from logs");

Loading…
Cancel
Save