]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10085 add QualityGate model to be shared accross server
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 17 Nov 2017 10:17:06 +0000 (11:17 +0100)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 24 Nov 2017 08:23:58 +0000 (09:23 +0100)
server/sonar-server/src/main/java/org/sonar/server/qualitygate/Condition.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGate.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/qualitygate/ConditionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedConditionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateTest.java [new file with mode: 0644]

diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/Condition.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/Condition.java
new file mode 100644 (file)
index 0000000..5fa0a01
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.Objects;
+import java.util.Optional;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+import static java.util.Objects.requireNonNull;
+
+@Immutable
+public class Condition {
+
+  private final String metricKey;
+  private final Operator operator;
+  @CheckForNull
+  private final String warningThreshold;
+  @CheckForNull
+  private final String errorThreshold;
+  private final boolean onLeakPeriod;
+
+  public Condition(String metricKey, Operator operator,
+    @Nullable String errorThreshold, @Nullable String warningThreshold,
+    boolean onLeakPeriod) {
+    this.metricKey = requireNonNull(metricKey, "metricKey can't be null");
+    this.operator = requireNonNull(operator, "operator can't be null");
+    this.onLeakPeriod = onLeakPeriod;
+    this.errorThreshold = errorThreshold;
+    this.warningThreshold = warningThreshold;
+  }
+
+  public String getMetricKey() {
+    return metricKey;
+  }
+
+  public boolean isOnLeakPeriod() {
+    return onLeakPeriod;
+  }
+
+  public Operator getOperator() {
+    return operator;
+  }
+
+  public Optional<String> getWarningThreshold() {
+    return Optional.ofNullable(warningThreshold);
+  }
+
+  public Optional<String> getErrorThreshold() {
+    return Optional.ofNullable(errorThreshold);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    Condition condition = (Condition) o;
+    return onLeakPeriod == condition.onLeakPeriod &&
+      Objects.equals(metricKey, condition.metricKey) &&
+      operator == condition.operator &&
+      Objects.equals(warningThreshold, condition.warningThreshold) &&
+      Objects.equals(errorThreshold, condition.errorThreshold);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(metricKey, operator, warningThreshold, errorThreshold, onLeakPeriod);
+  }
+
+  @Override
+  public String toString() {
+    return "Condition{" +
+      "metricKey='" + metricKey + '\'' +
+      ", operator=" + operator +
+      ", warningThreshold=" + toString(warningThreshold) +
+      ", errorThreshold=" + toString(errorThreshold) +
+      ", onLeakPeriod=" + onLeakPeriod +
+      '}';
+  }
+
+  private static String toString(@Nullable String errorThreshold) {
+    if (errorThreshold == null) {
+      return null;
+    }
+    return '\'' + errorThreshold + '\'';
+  }
+
+  public enum Operator {
+    EQUALS("EQ"), NOT_EQUALS("NE"), GREATER_THAN("GT"), LESS_THAN("LT");
+
+    private final String dbValue;
+
+    Operator(String dbValue) {
+      this.dbValue = dbValue;
+    }
+
+    public String getDbValue() {
+      return dbValue;
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java
new file mode 100644 (file)
index 0000000..71e65c3
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.Objects;
+import java.util.Optional;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+import static java.util.Objects.requireNonNull;
+
+@Immutable
+public class EvaluatedCondition {
+  private final Condition condition;
+  private final EvaluationStatus status;
+  private final String value;
+
+  public EvaluatedCondition(Condition condition, EvaluationStatus status, @Nullable String value) {
+    this.condition = requireNonNull(condition, "condition can't be null");
+    this.status = requireNonNull(status, "status can't be null");
+    this.value = value;
+  }
+
+  public Condition getCondition() {
+    return condition;
+  }
+
+  public EvaluationStatus getStatus() {
+    return status;
+  }
+
+  public Optional<String> getValue() {
+    return Optional.ofNullable(value);
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    EvaluatedCondition that = (EvaluatedCondition) o;
+    return Objects.equals(condition, that.condition) &&
+      status == that.status &&
+      Objects.equals(value, that.value);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(condition, status, value);
+  }
+
+  @Override
+  public String toString() {
+    return "EvaluatedCondition{" +
+      "condition=" + condition +
+      ", status=" + status +
+      ", value=" + (value == null ? null : '\'' + value + '\'') +
+      '}';
+  }
+
+  /**
+   * Quality gate condition evaluation status.
+   */
+  public enum EvaluationStatus {
+    /**
+     * No measure found or measure had no value. The condition has not been evaluated and therefor ignored in
+     * the computation of the Quality Gate status.
+     */
+    NO_VALUE,
+    /**
+     * Condition evaluated as OK, neither error nor warning thresholds have been reached.
+     */
+    OK,
+    /**
+     * Condition evaluated as WARN, only warning thresholds has been reached.
+     */
+    WARN,
+    /**
+     * Condition evaluated as ERROR, error thresholds has been reached (and most likely warning thresholds too).
+     */
+    ERROR
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java
new file mode 100644 (file)
index 0000000..f594de7
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 com.google.common.collect.ImmutableSet;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import org.sonar.server.qualitygate.EvaluatedCondition.EvaluationStatus;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+@Immutable
+public class EvaluatedQualityGate {
+  private final QualityGate qualityGate;
+  private final Status status;
+  private final Set<EvaluatedCondition> evaluatedConditions;
+
+  private EvaluatedQualityGate(QualityGate qualityGate, Status status, Set<EvaluatedCondition> evaluatedConditions) {
+    this.qualityGate = qualityGate;
+    this.status = status;
+    this.evaluatedConditions = evaluatedConditions;
+  }
+
+  public QualityGate getQualityGate() {
+    return qualityGate;
+  }
+
+  public Status getStatus() {
+    return status;
+  }
+
+  public Set<EvaluatedCondition> getEvaluatedConditions() {
+    return evaluatedConditions;
+  }
+
+  public static Builder newBuilder() {
+    return new Builder();
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    EvaluatedQualityGate that = (EvaluatedQualityGate) o;
+    return Objects.equals(qualityGate, that.qualityGate) &&
+      status == that.status &&
+      Objects.equals(evaluatedConditions, that.evaluatedConditions);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(qualityGate, status, evaluatedConditions);
+  }
+
+  @Override
+  public String toString() {
+    return "EvaluatedQualityGate{" +
+      "qualityGate=" + qualityGate +
+      ", status=" + status +
+      ", evaluatedConditions=" + evaluatedConditions +
+      '}';
+  }
+
+  public static final class Builder {
+    private QualityGate qualityGate;
+    private Status status;
+    private final Map<Condition, EvaluatedCondition> evaluatedConditions = new HashMap<>();
+
+    private Builder() {
+      // use static factory method
+    }
+
+    public Builder setQualityGate(QualityGate qualityGate) {
+      this.qualityGate = checkQualityGate(qualityGate);
+      return this;
+    }
+
+    public Builder setStatus(Status status) {
+      this.status = checkStatus(status);
+      return this;
+    }
+
+    public Builder addCondition(Condition condition, EvaluationStatus status, @Nullable String value) {
+      evaluatedConditions.put(condition, new EvaluatedCondition(condition, status, value));
+      return this;
+    }
+
+    public Set<EvaluatedCondition> getEvaluatedConditions() {
+      return ImmutableSet.copyOf(evaluatedConditions.values());
+    }
+
+    public EvaluatedQualityGate build() {
+      checkQualityGate(this.qualityGate);
+      checkStatus(this.status);
+
+      return new EvaluatedQualityGate(
+        this.qualityGate,
+        this.status,
+        checkEvaluatedConditions(qualityGate, evaluatedConditions));
+    }
+
+    private static Set<EvaluatedCondition> checkEvaluatedConditions(QualityGate qualityGate, Map<Condition, EvaluatedCondition> evaluatedConditions) {
+      Set<Condition> conditions = qualityGate.getConditions();
+
+      Set<Condition> conditionsNotEvaluated = conditions.stream()
+        .filter(c -> !evaluatedConditions.containsKey(c))
+        .collect(Collectors.toSet());
+      checkArgument(conditionsNotEvaluated.isEmpty(), "Evaluation missing for the following conditions: %s", conditionsNotEvaluated);
+
+      Set<Condition> unknownConditions = evaluatedConditions.keySet().stream()
+        .filter(c -> !conditions.contains(c))
+        .collect(Collectors.toSet());
+      checkArgument(unknownConditions.isEmpty(), "Evaluation provided for unknown conditions: %s", unknownConditions);
+
+      return ImmutableSet.copyOf(evaluatedConditions.values());
+    }
+
+    private static QualityGate checkQualityGate(@Nullable QualityGate qualityGate) {
+      return requireNonNull(qualityGate, "qualityGate can't be null");
+    }
+
+    private static Status checkStatus(@Nullable Status status) {
+      return requireNonNull(status, "status can't be null");
+    }
+  }
+
+  public enum Status {
+    OK,
+    WARN,
+    ERROR
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGate.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGate.java
new file mode 100644 (file)
index 0000000..fe482fa
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.Objects;
+import java.util.Set;
+import javax.annotation.concurrent.Immutable;
+
+import static java.util.Objects.requireNonNull;
+import static org.sonar.core.util.stream.MoreCollectors.toSet;
+
+@Immutable
+public class QualityGate {
+  private final String id;
+  private final String name;
+  private final Set<Condition> conditions;
+
+  public QualityGate(String id, String name, Set<Condition> conditions) {
+    this.id = requireNonNull(id, "id can't be null");
+    this.name = requireNonNull(name, "name can't be null");
+    this.conditions = requireNonNull(conditions, "conditions can't be null")
+      .stream()
+      .map(c -> requireNonNull(c, "condition can't be null"))
+      .collect(toSet(conditions.size()));
+  }
+
+  public String getId() {
+    return id;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public Set<Condition> getConditions() {
+    return conditions;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    QualityGate that = (QualityGate) o;
+    return Objects.equals(id, that.id) &&
+      Objects.equals(name, that.name) &&
+      Objects.equals(conditions, that.conditions);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(id, name, conditions);
+  }
+
+  @Override
+  public String toString() {
+    return "QualityGate{" +
+      "id=" + id +
+      ", name='" + name + '\'' +
+      ", conditions=" + conditions +
+      '}';
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ConditionTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ConditionTest.java
new file mode 100644 (file)
index 0000000..578871d
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.Arrays;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ConditionTest {
+  private static final String METRIC_KEY = "metric_key";
+  private static final Condition.Operator OPERATOR = Condition.Operator.EQUALS;
+  private static final String ERROR_THRESHOLD = "2";
+  private static final String WARN_THRESHOLD = "4";
+  private static final boolean ON_LEAK_PERIOD = true;
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private Condition underTest = new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD);
+
+  @Test
+  public void constructor_throws_NPE_if_metricKey_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("metricKey can't be null");
+
+    new Condition(null, OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD);
+  }
+
+  @Test
+  public void constructor_throws_NPE_if_operator_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("operator can't be null");
+
+    new Condition(METRIC_KEY, null, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD);
+  }
+
+  @Test
+  public void errorThreshold_can_be_null() {
+    Condition underTest = new Condition(METRIC_KEY, OPERATOR, null, WARN_THRESHOLD, ON_LEAK_PERIOD);
+
+    assertThat(underTest.getErrorThreshold()).isEmpty();
+  }
+
+  @Test
+  public void warnThreshold_can_be_null() {
+    Condition underTest = new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, null, ON_LEAK_PERIOD);
+
+    assertThat(underTest.getWarningThreshold()).isEmpty();
+  }
+
+  @Test
+  public void verify_getters() {
+    assertThat(underTest.getMetricKey()).isEqualTo(METRIC_KEY);
+    assertThat(underTest.getOperator()).isEqualTo(OPERATOR);
+    assertThat(underTest.getErrorThreshold()).contains(ERROR_THRESHOLD);
+    assertThat(underTest.getWarningThreshold()).contains(WARN_THRESHOLD);
+    assertThat(underTest.isOnLeakPeriod()).isEqualTo(ON_LEAK_PERIOD);
+  }
+
+  @Test
+  public void toString_is_override() {
+    assertThat(underTest.toString())
+      .isEqualTo("Condition{metricKey='metric_key', operator=EQUALS, warningThreshold='4', errorThreshold='2', onLeakPeriod=true}");
+  }
+
+  @Test
+  public void toString_does_not_quote_nulls() {
+    Condition withNulls = new Condition("metric_key", Condition.Operator.LESS_THAN, null, null, false);
+    assertThat(withNulls.toString())
+      .isEqualTo("Condition{metricKey='metric_key', operator=LESS_THAN, warningThreshold=null, errorThreshold=null, onLeakPeriod=false}");
+  }
+
+  @Test
+  public void equals_is_based_on_all_fields() {
+    assertThat(underTest).isEqualTo(underTest);
+    assertThat(underTest).isNotEqualTo(null);
+    assertThat(underTest).isNotEqualTo(new Object());
+    assertThat(underTest).isEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD));
+    assertThat(underTest).isNotEqualTo(new Condition("other_metric_key", OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD));
+    Arrays.stream(Condition.Operator.values())
+        .filter(s -> !OPERATOR.equals(s))
+        .forEach(otherOperator ->  assertThat(underTest)
+            .isNotEqualTo(new Condition(METRIC_KEY, otherOperator, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD)));
+    assertThat(underTest).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, null, WARN_THRESHOLD, ON_LEAK_PERIOD));
+    assertThat(underTest).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, "other_error_threshold", WARN_THRESHOLD, ON_LEAK_PERIOD));
+    assertThat(underTest).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, null, ON_LEAK_PERIOD));
+    assertThat(underTest).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, "other_warn_threshold", ON_LEAK_PERIOD));
+    assertThat(underTest).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, !ON_LEAK_PERIOD));
+  }
+
+  @Test
+  public void hashcode_is_based_on_all_fields() {
+    assertThat(underTest.hashCode()).isEqualTo(underTest.hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(null);
+    assertThat(underTest.hashCode()).isNotEqualTo(new Object().hashCode());
+    assertThat(underTest.hashCode()).isEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new Condition("other_metric_key", OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD).hashCode());
+    Arrays.stream(Condition.Operator.values())
+        .filter(s -> !OPERATOR.equals(s))
+        .forEach(otherOperator ->  assertThat(underTest.hashCode())
+            .isNotEqualTo(new Condition(METRIC_KEY, otherOperator, ERROR_THRESHOLD, WARN_THRESHOLD, ON_LEAK_PERIOD).hashCode()));
+    assertThat(underTest.hashCode()).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, null, WARN_THRESHOLD, ON_LEAK_PERIOD).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, "other_error_threshold", WARN_THRESHOLD, ON_LEAK_PERIOD).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, null, ON_LEAK_PERIOD).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, "other_warn_threshold", ON_LEAK_PERIOD).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new Condition(METRIC_KEY, OPERATOR, ERROR_THRESHOLD, WARN_THRESHOLD, !ON_LEAK_PERIOD).hashCode());
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedConditionTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedConditionTest.java
new file mode 100644 (file)
index 0000000..4213dea
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.qualitygate.Condition.Operator.EQUALS;
+import static org.sonar.server.qualitygate.EvaluatedCondition.EvaluationStatus.OK;
+import static org.sonar.server.qualitygate.EvaluatedCondition.EvaluationStatus.WARN;
+
+public class EvaluatedConditionTest {
+  private static final Condition CONDITION_1 = new Condition("metricKey", EQUALS, "2", "4", false);
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private EvaluatedCondition underTest = new EvaluatedCondition(CONDITION_1, WARN, "value");
+
+  @Test
+  public void constructor_throws_NPE_if_condition_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("condition can't be null");
+
+    new EvaluatedCondition(null, WARN, "value");
+  }
+
+  @Test
+  public void constructor_throws_NPE_if_EvaluationStatus_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("status can't be null");
+
+    new EvaluatedCondition(CONDITION_1, null, "value");
+  }
+
+  @Test
+  public void constructor_accepts_null_value() {
+    EvaluatedCondition underTest = new EvaluatedCondition(CONDITION_1, WARN, null);
+
+    assertThat(underTest.getValue()).isEmpty();
+  }
+
+  @Test
+  public void verify_getters() {
+    EvaluatedCondition underTest = new EvaluatedCondition(CONDITION_1, WARN, "value");
+
+    assertThat(underTest.getCondition()).isEqualTo(CONDITION_1);
+    assertThat(underTest.getStatus()).isEqualTo(WARN);
+    assertThat(underTest.getValue()).contains("value");
+  }
+
+  @Test
+  public void override_toString() {
+    assertThat(underTest.toString()).isEqualTo("EvaluatedCondition{condition=" +
+      "Condition{metricKey='metricKey', operator=EQUALS, warningThreshold='4', errorThreshold='2', onLeakPeriod=false}, " +
+      "status=WARN, value='value'}");
+  }
+
+  @Test
+  public void toString_does_not_quote_null_value() {
+    EvaluatedCondition underTest = new EvaluatedCondition(CONDITION_1, WARN, null);
+
+    assertThat(underTest.toString()).isEqualTo("EvaluatedCondition{condition=" +
+      "Condition{metricKey='metricKey', operator=EQUALS, warningThreshold='4', errorThreshold='2', onLeakPeriod=false}, " +
+      "status=WARN, value=null}");
+  }
+
+  @Test
+  public void equals_is_based_on_all_fields() {
+    assertThat(underTest).isEqualTo(underTest);
+    assertThat(underTest).isEqualTo(new EvaluatedCondition(CONDITION_1, WARN, "value"));
+    assertThat(underTest).isNotEqualTo(null);
+    assertThat(underTest).isNotEqualTo(new Object());
+    assertThat(underTest).isNotEqualTo(new EvaluatedCondition(new Condition("other_metric", EQUALS, "a", "b", true), WARN, "value"));
+    assertThat(underTest).isNotEqualTo(new EvaluatedCondition(CONDITION_1, OK, "value"));
+    assertThat(underTest).isNotEqualTo(new EvaluatedCondition(CONDITION_1, WARN, null));
+    assertThat(underTest).isNotEqualTo(new EvaluatedCondition(CONDITION_1, WARN, "other_value"));
+  }
+
+  @Test
+  public void hashcode_is_based_on_all_fields() {
+    assertThat(underTest.hashCode()).isEqualTo(underTest.hashCode());
+    assertThat(underTest.hashCode()).isEqualTo(new EvaluatedCondition(CONDITION_1, WARN, "value").hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(null);
+    assertThat(underTest.hashCode()).isNotEqualTo(new Object().hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new EvaluatedCondition(new Condition("other_metric", EQUALS, "a", "b", true), WARN, "value").hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new EvaluatedCondition(CONDITION_1, OK, "value").hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new EvaluatedCondition(CONDITION_1, WARN, null).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new EvaluatedCondition(CONDITION_1, WARN, "other_value").hashCode());
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java
new file mode 100644 (file)
index 0000000..acea863
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 com.google.common.collect.ImmutableSet;
+import java.util.Random;
+import org.apache.commons.lang.RandomStringUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static java.util.Collections.emptySet;
+import static java.util.Collections.singleton;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.qualitygate.EvaluatedQualityGate.newBuilder;
+import static org.sonar.server.qualitygate.EvaluatedQualityGate.Status.OK;
+import static org.sonar.server.qualitygate.EvaluatedQualityGate.Status.WARN;
+
+public class EvaluatedQualityGateTest {
+  private static final String QUALITY_GATE_ID = "qg_id";
+  private static final String QUALITY_GATE_NAME = "qg_name";
+  private static final QualityGate NO_CONDITION_QUALITY_GATE = new QualityGate(QUALITY_GATE_ID, QUALITY_GATE_NAME, emptySet());
+  private static final Condition CONDITION_1 = new Condition("metric_key", Condition.Operator.LESS_THAN, "2", "4", true);
+  private static final Condition CONDITION_2 = new Condition("metric_key_2", Condition.Operator.GREATER_THAN, "6", "12", false);
+  private static final QualityGate ONE_CONDITION_QUALITY_GATE = new QualityGate(QUALITY_GATE_ID, QUALITY_GATE_NAME, singleton(CONDITION_1));
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private final Random random = new Random();
+  private final EvaluatedQualityGate.Status randomStatus = EvaluatedQualityGate.Status.values()[random.nextInt(EvaluatedQualityGate.Status.values().length)];
+  private final EvaluatedCondition.EvaluationStatus randomEvaluationStatus = EvaluatedCondition.EvaluationStatus.values()[random
+    .nextInt(EvaluatedCondition.EvaluationStatus.values().length)];
+  private final String randomValue = random.nextBoolean() ? null : RandomStringUtils.randomAlphanumeric(3);
+
+  private EvaluatedQualityGate.Builder builder = newBuilder();
+
+  @Test
+  public void setQualityGate_fails_with_NPE_if_argument_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("qualityGate can't be null");
+
+    builder.setQualityGate(null);
+  }
+
+  @Test
+  public void setStatus_fails_with_NPE_if_argument_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("status can't be null");
+
+    builder.setStatus(null);
+  }
+
+  @Test
+  public void build_fails_with_NPE_if_qualityGate_not_set() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("qualityGate can't be null");
+
+    builder.build();
+  }
+
+  @Test
+  public void build_fails_with_NPE_if_status_not_set() {
+    builder.setQualityGate(NO_CONDITION_QUALITY_GATE);
+
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("status can't be null");
+
+    builder.build();
+  }
+
+  @Test
+  public void addCondition_fails_with_NPE_if_condition_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("condition can't be null");
+
+    builder.addCondition(null, EvaluatedCondition.EvaluationStatus.WARN, "a_value");
+  }
+
+  @Test
+  public void addCondition_fails_with_NPE_if_status_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("status can't be null");
+
+    builder.addCondition(new Condition("metric_key", Condition.Operator.LESS_THAN, "2", "4", true), null, "a_value");
+  }
+
+  @Test
+  public void addCondition_accepts_null_value() {
+    builder.addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.NO_VALUE, null);
+
+    assertThat(builder.getEvaluatedConditions())
+      .containsOnly(new EvaluatedCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.NO_VALUE, null));
+  }
+
+  @Test
+  public void getEvaluatedConditions_returns_empty_with_no_condition_added_to_builder() {
+    assertThat(builder.getEvaluatedConditions()).isEmpty();
+  }
+
+  @Test
+  public void build_fails_with_IAE_if_condition_added_and_no_on_QualityGate() {
+    builder.setQualityGate(NO_CONDITION_QUALITY_GATE)
+      .setStatus(randomStatus)
+      .addCondition(CONDITION_1, randomEvaluationStatus, randomValue);
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Evaluation provided for unknown conditions: [" + CONDITION_1 + "]");
+
+    builder.build();
+  }
+
+  @Test
+  public void build_fails_with_IAE_if_condition_is_missing_for_one_defined_in_QualityGate() {
+    builder.setQualityGate(ONE_CONDITION_QUALITY_GATE)
+      .setStatus(randomStatus);
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Evaluation missing for the following conditions: [" + CONDITION_1 + "]");
+
+    builder.build();
+  }
+
+  @Test
+  public void verify_getters() {
+    EvaluatedQualityGate underTest = builder
+      .setQualityGate(ONE_CONDITION_QUALITY_GATE)
+      .setStatus(randomStatus)
+      .addCondition(CONDITION_1, randomEvaluationStatus, randomValue)
+      .build();
+
+    assertThat(underTest.getQualityGate()).isEqualTo(ONE_CONDITION_QUALITY_GATE);
+    assertThat(underTest.getStatus()).isEqualTo(randomStatus);
+    assertThat(underTest.getEvaluatedConditions())
+      .containsOnly(new EvaluatedCondition(CONDITION_1, randomEvaluationStatus, randomValue));
+  }
+
+  @Test
+  public void verify_getters_when_no_condition() {
+    EvaluatedQualityGate underTest = builder
+      .setQualityGate(NO_CONDITION_QUALITY_GATE)
+      .setStatus(randomStatus)
+      .build();
+
+    assertThat(underTest.getQualityGate()).isEqualTo(NO_CONDITION_QUALITY_GATE);
+    assertThat(underTest.getStatus()).isEqualTo(randomStatus);
+    assertThat(underTest.getEvaluatedConditions()).isEmpty();
+  }
+
+  @Test
+  public void verify_getters_when_multiple_conditions() {
+    QualityGate qualityGate = new QualityGate(QUALITY_GATE_ID, QUALITY_GATE_NAME, ImmutableSet.of(CONDITION_1, CONDITION_2));
+    EvaluatedQualityGate underTest = builder
+      .setQualityGate(qualityGate)
+      .setStatus(randomStatus)
+      .addCondition(CONDITION_1, randomEvaluationStatus, randomValue)
+      .addCondition(CONDITION_2, EvaluatedCondition.EvaluationStatus.WARN, "bad")
+      .build();
+
+    assertThat(underTest.getQualityGate()).isEqualTo(qualityGate);
+    assertThat(underTest.getStatus()).isEqualTo(randomStatus);
+    assertThat(underTest.getEvaluatedConditions()).containsOnly(
+      new EvaluatedCondition(CONDITION_1, randomEvaluationStatus, randomValue),
+      new EvaluatedCondition(CONDITION_2, EvaluatedCondition.EvaluationStatus.WARN, "bad"));
+  }
+
+  @Test
+  public void equals_is_based_on_all_fields() {
+    EvaluatedQualityGate.Builder builder = this.builder
+      .setQualityGate(ONE_CONDITION_QUALITY_GATE)
+      .setStatus(WARN)
+      .addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.WARN, "foo");
+
+    EvaluatedQualityGate underTest = builder.build();
+    assertThat(underTest).isEqualTo(builder.build());
+    assertThat(underTest).isNotSameAs(builder.build());
+    assertThat(underTest).isNotEqualTo(null);
+    assertThat(underTest).isNotEqualTo(new Object());
+    assertThat(underTest).isNotEqualTo(builder.setQualityGate(new QualityGate("other_id", QUALITY_GATE_NAME, singleton(CONDITION_1))).build());
+    assertThat(underTest).isNotEqualTo(builder.setQualityGate(ONE_CONDITION_QUALITY_GATE).setStatus(OK).build());
+    assertThat(underTest).isNotEqualTo(newBuilder()
+      .setQualityGate(ONE_CONDITION_QUALITY_GATE)
+      .setStatus(WARN)
+      .addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.OK, "foo")
+      .build());
+  }
+
+  @Test
+  public void hashcode_is_based_on_all_fields() {
+    EvaluatedQualityGate.Builder builder = this.builder
+      .setQualityGate(ONE_CONDITION_QUALITY_GATE)
+      .setStatus(WARN)
+      .addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.WARN, "foo");
+
+    EvaluatedQualityGate underTest = builder.build();
+    assertThat(underTest.hashCode()).isEqualTo(builder.build().hashCode());
+    assertThat(underTest.hashCode()).isNotSameAs(builder.build().hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(null);
+    assertThat(underTest.hashCode()).isNotEqualTo(new Object().hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(builder.setQualityGate(new QualityGate("other_id", QUALITY_GATE_NAME, singleton(CONDITION_1))).build().hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(builder.setQualityGate(ONE_CONDITION_QUALITY_GATE).setStatus(OK).build().hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(newBuilder()
+      .setQualityGate(ONE_CONDITION_QUALITY_GATE)
+      .setStatus(WARN)
+      .addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.OK, "foo")
+      .build().hashCode());
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateTest.java
new file mode 100644 (file)
index 0000000..8202653
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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 com.google.common.collect.ImmutableSet;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static java.util.Collections.emptySet;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class QualityGateTest {
+  private static final String QUALIGATE_ID = "qg_id";
+  private static final String QUALIGATE_NAME = "qg_name";
+  private static final Condition CONDITION_1 = new Condition("m1", Condition.Operator.EQUALS, "1", "2", false);
+  private static final Condition CONDITION_2 = new Condition("m2", Condition.Operator.LESS_THAN, "2", "4", true);
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private QualityGate underTest = new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_1, CONDITION_2));
+
+  @Test
+  public void constructor_fails_with_NPE_if_id_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("id can't be null");
+
+    new QualityGate(null, "name", emptySet());
+  }
+
+  @Test
+  public void constructor_fails_with_NPE_if_name_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("name can't be null");
+
+    new QualityGate("id", null, emptySet());
+  }
+
+  @Test
+  public void constructor_fails_with_NPE_if_conditions_is_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("conditions can't be null");
+
+    new QualityGate("id", "name", null);
+  }
+
+  @Test
+  public void constructor_fails_with_NPE_if_conditions_contains_null() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("condition can't be null");
+    Random random = new Random();
+    Set<Condition> conditions = Stream.of(
+      IntStream.range(0, random.nextInt(5))
+        .mapToObj(i -> new Condition("m_before_" + i, Condition.Operator.EQUALS, null, null, false)),
+      Stream.of((Condition) null),
+      IntStream.range(0, random.nextInt(5))
+        .mapToObj(i -> new Condition("m_after_" + i, Condition.Operator.EQUALS, null, null, false)))
+      .flatMap(s -> s)
+      .collect(Collectors.toSet());
+
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("condition can't be null");
+
+    new QualityGate("id", "name", conditions);
+  }
+
+  @Test
+  public void verify_getters() {
+    assertThat(underTest.getId()).isEqualTo(QUALIGATE_ID);
+    assertThat(underTest.getName()).isEqualTo(QUALIGATE_NAME);
+    assertThat(underTest.getConditions()).containsOnly(CONDITION_1, CONDITION_2);
+  }
+
+  @Test
+  public void toString_is_override() {
+    QualityGate underTest = new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_2));
+
+    assertThat(underTest.toString()).isEqualTo("QualityGate{id=qg_id, name='qg_name', conditions=[" +
+      "Condition{metricKey='m2', operator=LESS_THAN, warningThreshold='4', errorThreshold='2', onLeakPeriod=true}" +
+      "]}");
+  }
+
+  @Test
+  public void equals_is_based_on_all_fields() {
+    assertThat(underTest).isEqualTo(underTest);
+    assertThat(underTest).isEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_2, CONDITION_1)));
+    assertThat(underTest).isNotEqualTo(null);
+    assertThat(underTest).isNotEqualTo(new Object());
+    assertThat(underTest).isNotEqualTo(new QualityGate("other_id", QUALIGATE_NAME, ImmutableSet.of(CONDITION_2, CONDITION_1)));
+    assertThat(underTest).isNotEqualTo(new QualityGate(QUALIGATE_ID, "other_name", ImmutableSet.of(CONDITION_2, CONDITION_1)));
+    assertThat(underTest).isNotEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, emptySet()));
+    assertThat(underTest).isNotEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_1)));
+    assertThat(underTest).isNotEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_2)));
+    assertThat(underTest).isNotEqualTo(
+      new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_1, CONDITION_2, new Condition("new", Condition.Operator.GREATER_THAN, "a", "b", false))));
+  }
+
+  @Test
+  public void hashcode_is_based_on_all_fields() {
+    assertThat(underTest.hashCode()).isEqualTo(underTest.hashCode());
+    assertThat(underTest.hashCode()).isEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_2, CONDITION_1)).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(null);
+    assertThat(underTest.hashCode()).isNotEqualTo(new Object().hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new QualityGate("other_id", QUALIGATE_NAME, ImmutableSet.of(CONDITION_2, CONDITION_1)).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new QualityGate(QUALIGATE_ID, "other_name", ImmutableSet.of(CONDITION_2, CONDITION_1)).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, emptySet()).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_1)).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_2)).hashCode());
+    assertThat(underTest.hashCode()).isNotEqualTo(
+      new QualityGate(QUALIGATE_ID, QUALIGATE_NAME, ImmutableSet.of(CONDITION_1, CONDITION_2, new Condition("new", Condition.Operator.GREATER_THAN, "a", "b", false))).hashCode());
+  }
+}