diff options
Diffstat (limited to 'server/sonar-server-common/src/test/java/org')
22 files changed, 2476 insertions, 0 deletions
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/async/AsyncExecutionExecutorServiceImplTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/async/AsyncExecutionExecutorServiceImplTest.java new file mode 100644 index 00000000000..8aa2ee5165b --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/async/AsyncExecutionExecutorServiceImplTest.java @@ -0,0 +1,58 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.async; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AsyncExecutionExecutorServiceImplTest { + private AsyncExecutionExecutorServiceImpl underTest = new AsyncExecutionExecutorServiceImpl(); + + @Test + public void submit_executes_runnable_in_another_thread() { + try (SlowRunnable slowRunnable = new SlowRunnable()) { + underTest.submit(slowRunnable); + assertThat(slowRunnable.executed).isFalse(); + } + } + + private static final class SlowRunnable implements Runnable, AutoCloseable { + private final CountDownLatch latch = new CountDownLatch(1); + private volatile boolean executed = false; + + @Override + public void run() { + try { + latch.await(30, TimeUnit.SECONDS); + } catch (InterruptedException e) { + // ignore + } + executed = true; + } + + @Override + public void close() { + latch.countDown(); + } + } +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/async/AsyncExecutionImplTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/async/AsyncExecutionImplTest.java new file mode 100644 index 00000000000..0ff2d3cec07 --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/async/AsyncExecutionImplTest.java @@ -0,0 +1,68 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.async; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; + +import static org.assertj.core.api.Assertions.assertThat; + +public class AsyncExecutionImplTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Rule + public LogTester logTester = new LogTester(); + + private AsyncExecutionExecutorService synchronousExecutorService = Runnable::run; + private AsyncExecutionImpl underTest = new AsyncExecutionImpl(synchronousExecutorService); + + @Test + public void addToQueue_fails_with_NPE_if_Runnable_is_null() { + expectedException.expect(NullPointerException.class); + + underTest.addToQueue(null); + } + + @Test + public void addToQueue_submits_runnable_to_executorService_which_does_not_fail_if_Runnable_argument_throws_exception() { + underTest.addToQueue(() -> { + throw new RuntimeException("Faking an exception thrown by Runnable argument"); + }); + + assertThat(logTester.logs()).hasSize(1); + assertThat(logTester.logs(LoggerLevel.ERROR)).containsOnly("Asynchronous task failed"); + } + + @Test + public void addToQueue_submits_runnable_that_fails_if_Runnable_argument_throws_Error() { + Error expected = new Error("Faking an exception thrown by Runnable argument"); + Runnable runnable = () -> { + throw expected; + }; + + expectedException.expect(Error.class); + expectedException.expectMessage(expected.getMessage()); + + underTest.addToQueue(runnable); + } +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/async/AsyncExecutionMBeanImplTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/async/AsyncExecutionMBeanImplTest.java new file mode 100644 index 00000000000..e534273abfb --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/async/AsyncExecutionMBeanImplTest.java @@ -0,0 +1,87 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.async; + +import java.lang.management.ManagementFactory; +import javax.annotation.CheckForNull; +import javax.management.InstanceNotFoundException; +import javax.management.ObjectInstance; +import javax.management.ObjectName; +import org.junit.Test; +import org.mockito.Mockito; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class AsyncExecutionMBeanImplTest { + private AsyncExecutionMonitoring asyncExecutionMonitoring = Mockito.mock(AsyncExecutionMonitoring.class); + + private AsyncExecutionMBeanImpl underTest = new AsyncExecutionMBeanImpl(asyncExecutionMonitoring); + + @Test + public void register_and_unregister() throws Exception { + assertThat(getMBean()).isNull(); + + underTest.start(); + assertThat(getMBean()).isNotNull(); + + underTest.stop(); + assertThat(getMBean()).isNull(); + } + + @Test + public void getQueueSize_delegates_to_AsyncExecutionMonitoring() { + when(asyncExecutionMonitoring.getQueueSize()).thenReturn(12); + + assertThat(underTest.getQueueSize()).isEqualTo(12); + + verify(asyncExecutionMonitoring).getQueueSize(); + } + + @Test + public void getWorkerCount_delegates_to_AsyncExecutionMonitoring() { + when(asyncExecutionMonitoring.getWorkerCount()).thenReturn(12); + + assertThat(underTest.getWorkerCount()).isEqualTo(12); + + verify(asyncExecutionMonitoring).getWorkerCount(); + } + + @Test + public void getLargestWorkerCount_delegates_to_AsyncExecutionMonitoring() { + when(asyncExecutionMonitoring.getLargestWorkerCount()).thenReturn(12); + + assertThat(underTest.getLargestWorkerCount()).isEqualTo(12); + + verify(asyncExecutionMonitoring).getLargestWorkerCount(); + } + + @CheckForNull + private ObjectInstance getMBean() throws Exception { + try { + return ManagementFactory.getPlatformMBeanServer().getObjectInstance(new ObjectName(AsyncExecutionMBean.OBJECT_NAME)); + } catch (InstanceNotFoundException e) { + return null; + } + } + + +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/project/ProjectTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/project/ProjectTest.java new file mode 100644 index 00000000000..4f2a351e844 --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/project/ProjectTest.java @@ -0,0 +1,78 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.project; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ProjectTest { + @Test + public void test_bean_without_description() { + Project project1 = new Project("U1", "K1", "N1"); + Project project2 = new Project("U1", "K1", "N1", null); + + assertThat(project1.getUuid()).isEqualTo(project2.getUuid()).isEqualTo("U1"); + assertThat(project1.getKey()).isEqualTo(project2.getKey()).isEqualTo("K1"); + assertThat(project1.getName()).isEqualTo(project2.getName()).isEqualTo("N1"); + assertThat(project1.getDescription()).isEqualTo(project2.getDescription()).isNull(); + + assertThat(project1.toString()) + .isEqualTo(project2.toString()) + .isEqualTo("Project{uuid='U1', key='K1', name='N1', description=null}"); + } + + @Test + public void test_bean_with_description() { + Project project1 = new Project("U1", "K1", "N1", "D1"); + + assertThat(project1.getUuid()).isEqualTo("U1"); + assertThat(project1.getKey()).isEqualTo("K1"); + assertThat(project1.getName()).isEqualTo("N1"); + assertThat(project1.getDescription()).isEqualTo("D1"); + + assertThat(project1.toString()) + .isEqualTo(project1.toString()) + .isEqualTo("Project{uuid='U1', key='K1', name='N1', description='D1'}"); + } + + @Test + public void test_equals_and_hashCode() { + Project project1 = new Project("U1", "K1", "N1"); + Project project2 = new Project("U1", "K1", "N1", "D1"); + + assertThat(project1).isEqualTo(project1); + assertThat(project1).isNotEqualTo(null); + assertThat(project1).isNotEqualTo(new Object()); + assertThat(project1).isEqualTo(new Project("U1", "K1", "N1", null)); + assertThat(project1).isNotEqualTo(new Project("U1", "K2", "N1", null)); + assertThat(project1).isNotEqualTo(new Project("U1", "K1", "N2", null)); + assertThat(project1).isEqualTo(project2); + + assertThat(project1.hashCode()).isEqualTo(project1.hashCode()); + assertThat(project1.hashCode()).isNotEqualTo(null); + assertThat(project1.hashCode()).isNotEqualTo(new Object().hashCode()); + assertThat(project1.hashCode()).isEqualTo(new Project("U1", "K1", "N1", null).hashCode()); + assertThat(project1.hashCode()).isNotEqualTo(new Project("U1", "K2", "N1", null).hashCode()); + assertThat(project1.hashCode()).isNotEqualTo(new Project("U1", "K1", "N2", null).hashCode()); + assertThat(project1.hashCode()).isEqualTo(project2.hashCode()); + } + +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/ConditionTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/ConditionTest.java new file mode 100644 index 00000000000..ba0b79a7fef --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/ConditionTest.java @@ -0,0 +1,128 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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-common/src/test/java/org/sonar/server/qualitygate/EvaluatedConditionTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/EvaluatedConditionTest.java new file mode 100644 index 00000000000..7cadd61e89f --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/EvaluatedConditionTest.java @@ -0,0 +1,110 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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-common/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java new file mode 100644 index 00000000000..b02d5dd2cd5 --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java @@ -0,0 +1,200 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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 org.sonar.api.measures.Metric.Level; + +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; + +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 Level randomStatus = Level.values()[random.nextInt(Level.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 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(Level.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(Level.OK).build()); + assertThat(underTest).isNotEqualTo(newBuilder() + .setQualityGate(ONE_CONDITION_QUALITY_GATE) + .setStatus(Level.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(Level.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(Level.OK).build().hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(newBuilder() + .setQualityGate(ONE_CONDITION_QUALITY_GATE) + .setStatus(Level.WARN) + .addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.OK, "foo") + .build().hashCode()); + } +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/QualityGateTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/QualityGateTest.java new file mode 100644 index 00000000000..d1fe1dde3ca --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/qualitygate/QualityGateTest.java @@ -0,0 +1,135 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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()); + } +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/util/OkHttpClientProviderTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/util/OkHttpClientProviderTest.java new file mode 100644 index 00000000000..b6f2f037a98 --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/util/OkHttpClientProviderTest.java @@ -0,0 +1,88 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.util; + +import java.io.IOException; +import java.util.Base64; +import okhttp3.OkHttpClient; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.SonarQubeSide; +import org.sonar.api.SonarRuntime; +import org.sonar.api.config.internal.MapSettings; +import org.sonar.api.internal.SonarRuntimeImpl; +import org.sonar.api.utils.Version; + +import static org.assertj.core.api.Assertions.assertThat; + +public class OkHttpClientProviderTest { + + private MapSettings settings = new MapSettings(); + private SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.parse("6.2"), SonarQubeSide.SERVER); + private final OkHttpClientProvider underTest = new OkHttpClientProvider(); + + @Rule + public MockWebServer server = new MockWebServer(); + + @Test + public void get_returns_a_OkHttpClient_with_default_configuration() throws Exception { + OkHttpClient client = underTest.provide(settings.asConfig(), runtime); + + assertThat(client.connectTimeoutMillis()).isEqualTo(10_000); + assertThat(client.readTimeoutMillis()).isEqualTo(10_000); + assertThat(client.proxy()).isNull(); + + RecordedRequest recordedRequest = call(client); + assertThat(recordedRequest.getHeader("User-Agent")).isEqualTo("SonarQube/6.2"); + assertThat(recordedRequest.getHeader("Proxy-Authorization")).isNull(); + } + + @Test + public void get_returns_a_OkHttpClient_with_proxy_authentication() throws Exception { + settings.setProperty("http.proxyUser", "the-login"); + settings.setProperty("http.proxyPassword", "the-password"); + + OkHttpClient client = underTest.provide(settings.asConfig(), runtime); + Response response = new Response.Builder().protocol(Protocol.HTTP_1_1).request(new Request.Builder().url("http://foo").build()).code(407).build(); + Request request = client.proxyAuthenticator().authenticate(null, response); + + assertThat(request.header("Proxy-Authorization")).isEqualTo("Basic " + Base64.getEncoder().encodeToString("the-login:the-password".getBytes())); + } + + @Test + public void get_returns_a_singleton() { + OkHttpClient client1 = underTest.provide(settings.asConfig(), runtime); + OkHttpClient client2 = underTest.provide(settings.asConfig(), runtime); + assertThat(client2).isNotNull().isSameAs(client1); + } + + private RecordedRequest call(OkHttpClient client) throws IOException, InterruptedException { + server.enqueue(new MockResponse().setBody("pong")); + client.newCall(new Request.Builder().url(server.url("/ping")).build()).execute(); + + return server.takeRequest(); + } +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/AnalysisTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/AnalysisTest.java new file mode 100644 index 00000000000..5f307d904d4 --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/AnalysisTest.java @@ -0,0 +1,69 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.webhook; + +import java.util.Date; +import java.util.Random; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; +import static org.assertj.core.api.Assertions.assertThat; + +public class AnalysisTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void constructor_throws_NPE_when_uuid_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("uuid must not be null"); + + new Analysis(null, 1_990L); + } + + @Test + public void test_equality() { + String uuid = randomAlphanumeric(35); + long date = new Random().nextLong(); + Analysis underTest = new Analysis(uuid, date); + + assertThat(underTest).isEqualTo(underTest); + assertThat(underTest.getUuid()).isEqualTo(uuid); + assertThat(underTest.getDate()).isEqualTo(new Date(date)); + + assertThat(underTest).isNotEqualTo(null); + assertThat(underTest).isNotEqualTo(new Analysis(uuid + "1", date)); + assertThat(underTest).isNotEqualTo(new Analysis(uuid, date + 1_000L)); + } + + @Test + public void test_hashcode() { + String uuid = randomAlphanumeric(35); + long date = new Random().nextLong(); + Analysis underTest = new Analysis(uuid, date); + + assertThat(underTest.hashCode()).isEqualTo(underTest.hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new Analysis(uuid + "1", date).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new Analysis(uuid, date + 1_000).hashCode()); + } +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/AsynchronousWebHooksImplTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/AsynchronousWebHooksImplTest.java new file mode 100644 index 00000000000..b7ca6d75fde --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/AsynchronousWebHooksImplTest.java @@ -0,0 +1,106 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.webhook; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDbTester; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.organization.OrganizationDbTester; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.db.webhook.WebhookDbTester; +import org.sonar.server.async.AsyncExecution; +import org.sonar.server.organization.DefaultOrganizationProvider; + +import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.sonar.db.DbTester.create; +import static org.sonar.db.webhook.WebhookTesting.newWebhook; +import static org.sonar.server.organization.TestDefaultOrganizationProvider.from; + +public class AsynchronousWebHooksImplTest { + + private System2 system2 = mock(System2.class); + + @Rule + public DbTester db = create(system2); + private WebhookDbTester webhookDbTester = db.webhooks(); + private ComponentDbTester componentDbTester = db.components(); + private OrganizationDbTester organizationDbTester = db.organizations(); + private DefaultOrganizationProvider defaultOrganizationProvider = from(db); + + private static final long NOW = 1_500_000_000_000L; + + + private final TestWebhookCaller caller = new TestWebhookCaller(); + private final WebhookDeliveryStorage deliveryStorage = mock(WebhookDeliveryStorage.class); + private final WebhookPayload mock = mock(WebhookPayload.class); + private final RecordingAsyncExecution asyncExecution = new RecordingAsyncExecution(); + + private final WebHooksImpl underTest = new WebHooksImpl(caller, deliveryStorage, asyncExecution, db.getDbClient()); + + @Test + public void send_global_webhooks() { + + OrganizationDto organizationDto = db.getDefaultOrganization() ; + ComponentDto project = componentDbTester.insertPrivateProject(componentDto -> componentDto.setOrganizationUuid(organizationDto.getUuid())); + webhookDbTester.insert(newWebhook(organizationDto).setName("First").setUrl("http://url1")); + webhookDbTester.insert(newWebhook(organizationDto).setName("Second").setUrl("http://url2")); + + caller.enqueueSuccess(NOW, 200, 1_234); + caller.enqueueFailure(NOW, new IOException("Fail to connect")); + + underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(project.uuid(), "1", "#1"), () -> mock); + + assertThat(caller.countSent()).isZero(); + verifyZeroInteractions(deliveryStorage); + + asyncExecution.executeRecorded(); + + assertThat(caller.countSent()).isEqualTo(2); + verify(deliveryStorage, times(2)).persist(any(WebhookDelivery.class)); + verify(deliveryStorage).purge(project.uuid()); + } + + private static class RecordingAsyncExecution implements AsyncExecution { + private final List<Runnable> runnableList = new ArrayList<>(); + + @Override + public void addToQueue(Runnable r) { + runnableList.add(requireNonNull(r)); + } + + public void executeRecorded() { + ArrayList<Runnable> runnables = new ArrayList<>(runnableList); + runnableList.clear(); + runnables.forEach(Runnable::run); + } + } +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/BranchTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/BranchTest.java new file mode 100644 index 00000000000..b935d89b248 --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/BranchTest.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.webhook; + +import java.util.Random; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BranchTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private Branch underTest = new Branch(true, "b", Branch.Type.SHORT); + + @Test + public void constructor_throws_NPE_if_type_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("type can't be null"); + + new Branch(new Random().nextBoolean(), "s", null); + } + + @Test + public void verify_getters() { + assertThat(underTest.isMain()).isTrue(); + assertThat(underTest.getName()).contains("b"); + assertThat(underTest.getType()).isEqualTo(Branch.Type.SHORT); + + + Branch underTestWithNull = new Branch(false, null, Branch.Type.LONG); + assertThat(underTestWithNull.isMain()).isFalse(); + assertThat(underTestWithNull.getName()).isEmpty(); + assertThat(underTestWithNull.getType()).isEqualTo(Branch.Type.LONG); + } +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/CeTaskTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/CeTaskTest.java new file mode 100644 index 00000000000..0ed58b7f00a --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/CeTaskTest.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.webhook; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class CeTaskTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private CeTask underTest = new CeTask("A", CeTask.Status.SUCCESS); + + @Test + public void constructor_throws_NPE_if_id_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("id can't be null"); + + new CeTask(null, CeTask.Status.SUCCESS); + } + + @Test + public void constructor_throws_NPE_if_status_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("status can't be null"); + + new CeTask("B", null); + } + + @Test + public void verify_getters() { + assertThat(underTest.getId()).isEqualTo("A"); + assertThat(underTest.getStatus()).isEqualTo(CeTask.Status.SUCCESS); + } +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/ProjectAnalysisTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/ProjectAnalysisTest.java new file mode 100644 index 00000000000..0a453709b67 --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/ProjectAnalysisTest.java @@ -0,0 +1,154 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.webhook; + +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.measures.Metric; +import org.sonar.server.project.Project; +import org.sonar.server.qualitygate.EvaluatedQualityGate; +import org.sonar.server.qualitygate.QualityGate; + +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySet; +import static org.assertj.core.api.Assertions.assertThat; + +public class ProjectAnalysisTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private final CeTask ceTask = new CeTask("id", CeTask.Status.SUCCESS); + private final Project project = new Project("uuid", "key", "name"); + private final Analysis analysis = new Analysis("analysis_uuid", 1_500L); + private final Branch branch = new Branch(true, "name", Branch.Type.SHORT); + private final EvaluatedQualityGate qualityGate = EvaluatedQualityGate.newBuilder() + .setQualityGate(new QualityGate("id", "name", emptySet())) + .setStatus(Metric.Level.WARN) + .build(); + private final Map<String, String> properties = ImmutableMap.of("a", "b"); + private ProjectAnalysis underTest = new ProjectAnalysis(project, ceTask, analysis, branch, qualityGate, 1L, properties); + + @Test + public void constructor_throws_NPE_if_project_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("project can't be null"); + + new ProjectAnalysis(null, + ceTask, + analysis, + branch, + qualityGate, + 1L, + emptyMap()); + } + + @Test + public void constructor_throws_NPE_if_properties_is_null() { + expectedException.expect(NullPointerException.class); + expectedException.expectMessage("properties can't be null"); + + new ProjectAnalysis(project, + ceTask, + analysis, + branch, + qualityGate, + 1L, + null); + } + + @Test + public void verify_getters() { + assertThat(underTest.getCeTask().get()).isSameAs(ceTask); + assertThat(underTest.getProject()).isSameAs(project); + assertThat(underTest.getBranch().get()).isSameAs(branch); + assertThat(underTest.getQualityGate().get()).isSameAs(qualityGate); + assertThat(underTest.getProperties()).isEqualTo(properties); + assertThat(underTest.getAnalysis().get()).isEqualTo(analysis); + + ProjectAnalysis underTestWithNulls = new ProjectAnalysis(project, null, null, null, null, null, emptyMap()); + assertThat(underTestWithNulls.getCeTask()).isEmpty(); + assertThat(underTestWithNulls.getBranch()).isEmpty(); + assertThat(underTestWithNulls.getQualityGate()).isEmpty(); + assertThat(underTestWithNulls.getProperties()).isEmpty(); + assertThat(underTestWithNulls.getAnalysis()).isEmpty(); + } + + @Test + public void defines_equals_based_on_all_fields() { + assertThat(underTest).isEqualTo(underTest); + assertThat(underTest).isEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, qualityGate, 1L, properties)); + assertThat(underTest).isNotEqualTo(null); + assertThat(underTest).isNotEqualTo(new Object()); + assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, new CeTask("2", CeTask.Status.SUCCESS), analysis, branch, qualityGate, 1L, properties)); + assertThat(underTest).isNotEqualTo(new ProjectAnalysis(new Project("A", "B", "C"), ceTask, analysis, branch, qualityGate, 1L, properties)); + assertThat(underTest).isNotEqualTo(new ProjectAnalysis(new Project("A", "B", "C"), ceTask, analysis, branch, qualityGate, 1L, properties)); + assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, null, null, null, qualityGate, 1L, properties)); + assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, null, null, qualityGate, 1L, properties)); + assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, new Analysis("foo", 1_500L), null, qualityGate, 1L, properties)); + assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, null, qualityGate, 1L, properties)); + assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, new Branch(false, "B", Branch.Type.SHORT), qualityGate, 1L, properties)); + assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, null, 1L, properties)); + EvaluatedQualityGate otherQualityGate = EvaluatedQualityGate.newBuilder() + .setQualityGate(new QualityGate("A", "B", emptySet())) + .setStatus(Metric.Level.WARN) + .build(); + assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, otherQualityGate, 1L, properties)); + assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, qualityGate, null, properties)); + assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, qualityGate, 2L, properties)); + assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, qualityGate, 1L, emptyMap())); + assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, qualityGate, 1L, ImmutableMap.of("A", "B"))); + } + + @Test + public void defines_hashcode_based_on_all_fields() { + assertThat(underTest.hashCode()).isEqualTo(underTest.hashCode()); + assertThat(underTest.hashCode()).isEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, qualityGate, 1L, properties).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new Object().hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, new CeTask("2", CeTask.Status.SUCCESS), analysis, branch, qualityGate, 1L, properties).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(new Project("A", "B", "C"), ceTask, analysis, branch, qualityGate, 1L, properties).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(new Project("A", "B", "C"), ceTask, analysis, branch, qualityGate, 1L, properties).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, null, null, null, qualityGate, 1L, properties).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, null, null, qualityGate, 1L, properties).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, new Analysis("foo", 1_500L), null, qualityGate, 1L, properties).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, null, qualityGate, 1L, properties).hashCode()); + assertThat(underTest.hashCode()) + .isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, new Branch(false, "B", Branch.Type.SHORT), qualityGate, 1L, properties).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, null, 1L, properties).hashCode()); + EvaluatedQualityGate otherQualityGate = EvaluatedQualityGate.newBuilder() + .setQualityGate(new QualityGate("A", "B", emptySet())) + .setStatus(Metric.Level.WARN) + .build(); + assertThat(underTest.hashCode()) + .isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, otherQualityGate, 1L, properties).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, this.qualityGate, null, properties).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, this.qualityGate, 2L, properties).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, this.qualityGate, 1L, emptyMap()).hashCode()); + assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, this.qualityGate, 1L, ImmutableMap.of("B", "C")).hashCode()); + } + + @Test + public void verify_toString() { + assertThat(underTest.toString()).isEqualTo( + "ProjectAnalysis{project=Project{uuid='uuid', key='key', name='name', description=null}, ceTask=CeTask{id='id', status=SUCCESS}, branch=Branch{main=true, name='name', type=SHORT}, qualityGate=EvaluatedQualityGate{qualityGate=QualityGate{id=id, name='name', conditions=[]}, status=WARN, evaluatedConditions=[]}, updatedAt=1, properties={a=b}, analysis=Analysis{uuid='analysis_uuid', date=1500}}"); + } +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/SynchronousWebHooksImplTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/SynchronousWebHooksImplTest.java new file mode 100644 index 00000000000..de8d298052f --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/SynchronousWebHooksImplTest.java @@ -0,0 +1,138 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.webhook; + +import java.io.IOException; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.log.LogTester; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDbTester; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.webhook.WebhookDbTester; +import org.sonar.server.async.AsyncExecution; +import org.sonar.server.organization.DefaultOrganizationProvider; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.sonar.api.utils.log.LoggerLevel.DEBUG; +import static org.sonar.db.DbTester.create; +import static org.sonar.db.webhook.WebhookTesting.newWebhook; +import static org.sonar.server.organization.TestDefaultOrganizationProvider.from; + +public class SynchronousWebHooksImplTest { + + private static final long NOW = 1_500_000_000_000L; + + @Rule + public LogTester logTester = new LogTester(); + + @Rule + public DbTester db = create(); + private DbClient dbClient = db.getDbClient(); + + private WebhookDbTester webhookDbTester = db.webhooks(); + private ComponentDbTester componentDbTester = db.components(); + private DefaultOrganizationProvider defaultOrganizationProvider = from(db); + + private final TestWebhookCaller caller = new TestWebhookCaller(); + private final WebhookDeliveryStorage deliveryStorage = mock(WebhookDeliveryStorage.class); + private final WebhookPayload mock = mock(WebhookPayload.class); + private final AsyncExecution synchronousAsyncExecution = Runnable::run; + private final WebHooksImpl underTest = new WebHooksImpl(caller, deliveryStorage, synchronousAsyncExecution, dbClient); + + @Test + public void isEnabled_returns_false_if_no_webhooks() { + ComponentDto componentDto = componentDbTester.insertPrivateProject(); + + assertThat(underTest.isEnabled(componentDto)).isFalse(); + } + + @Test + public void isEnabled_returns_true_if_one_valid_global_webhook() { + ComponentDto componentDto = componentDbTester.insertPrivateProject(); + webhookDbTester.insert(newWebhook(componentDto).setName("First").setUrl("http://url1")); + + assertThat(underTest.isEnabled(componentDto)).isTrue(); + } + + @Test + public void isEnabled_returns_true_if_one_valid_project_webhook() { + String organizationUuid = defaultOrganizationProvider.get().getUuid(); + ComponentDto componentDto = componentDbTester.insertPrivateProject().setOrganizationUuid(organizationUuid); + webhookDbTester.insert(newWebhook(componentDto).setName("First").setUrl("http://url1")); + + assertThat(underTest.isEnabled(componentDto)).isTrue(); + } + + + @Test + public void do_nothing_if_no_webhooks() { + ComponentDto componentDto = componentDbTester.insertPrivateProject().setOrganizationUuid(defaultOrganizationProvider.get().getUuid()); + + underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(componentDto.uuid(), "1", "#1"), () -> mock); + + assertThat(caller.countSent()).isEqualTo(0); + assertThat(logTester.logs(DEBUG)).isEmpty(); + verifyZeroInteractions(deliveryStorage); + } + + @Test + public void send_global_webhooks() { + + ComponentDto componentDto = componentDbTester.insertPrivateProject(); + webhookDbTester.insert(newWebhook(componentDto).setName("First").setUrl("http://url1")); + webhookDbTester.insert(newWebhook(componentDto).setName("Second").setUrl("http://url2")); + caller.enqueueSuccess(NOW, 200, 1_234); + caller.enqueueFailure(NOW, new IOException("Fail to connect")); + + underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(componentDto.uuid(), "1", "#1"), () -> mock); + + assertThat(caller.countSent()).isEqualTo(2); + assertThat(logTester.logs(DEBUG)).contains("Sent webhook 'First' | url=http://url1 | time=1234ms | status=200"); + assertThat(logTester.logs(DEBUG)).contains("Failed to send webhook 'Second' | url=http://url2 | message=Fail to connect"); + verify(deliveryStorage, times(2)).persist(any(WebhookDelivery.class)); + verify(deliveryStorage).purge(componentDto.uuid()); + + } + + @Test + public void send_project_webhooks() { + + String organizationUuid = defaultOrganizationProvider.get().getUuid(); + ComponentDto componentDto = componentDbTester.insertPrivateProject().setOrganizationUuid(organizationUuid); + webhookDbTester.insert(newWebhook(componentDto).setName("First").setUrl("http://url1")); + caller.enqueueSuccess(NOW, 200, 1_234); + + underTest.sendProjectAnalysisUpdate(new WebHooks.Analysis(componentDto.uuid(), "1", "#1"), () -> mock); + + assertThat(caller.countSent()).isEqualTo(1); + assertThat(logTester.logs(DEBUG)).contains("Sent webhook 'First' | url=http://url1 | time=1234ms | status=200"); + verify(deliveryStorage).persist(any(WebhookDelivery.class)); + verify(deliveryStorage).purge(componentDto.uuid()); + + } + +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/TestWebhookCaller.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/TestWebhookCaller.java new file mode 100644 index 00000000000..0634f160622 --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/TestWebhookCaller.java @@ -0,0 +1,75 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.webhook; + +import java.util.LinkedList; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.Nullable; + +import static java.util.Objects.requireNonNull; + +public class TestWebhookCaller implements WebhookCaller { + + private final Queue<Item> deliveries = new LinkedList<>(); + private final AtomicInteger countSent = new AtomicInteger(0); + + public TestWebhookCaller enqueueSuccess(long at, int httpCode, int durationMs) { + deliveries.add(new Item(at, httpCode, durationMs, null)); + return this; + } + + public TestWebhookCaller enqueueFailure(long at, Throwable t) { + deliveries.add(new Item(at, null, null, t)); + return this; + } + + @Override + public WebhookDelivery call(Webhook webhook, WebhookPayload payload) { + Item item = requireNonNull(deliveries.poll(), "Queue is empty"); + countSent.incrementAndGet(); + return new WebhookDelivery.Builder() + .setAt(item.at) + .setHttpStatus(item.httpCode) + .setDurationInMs(item.durationMs) + .setError(item.throwable) + .setPayload(payload) + .setWebhook(webhook) + .build(); + } + + public int countSent() { + return countSent.get(); + } + + private static class Item { + final long at; + final Integer httpCode; + final Integer durationMs; + final Throwable throwable; + + Item(long at, @Nullable Integer httpCode, @Nullable Integer durationMs, @Nullable Throwable throwable) { + this.at = at; + this.httpCode = httpCode; + this.durationMs = durationMs; + this.throwable = throwable; + } + } +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookCallerImplTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookCallerImplTest.java new file mode 100644 index 00000000000..60ac24ee262 --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookCallerImplTest.java @@ -0,0 +1,218 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.webhook; + +import okhttp3.Credentials; +import okhttp3.HttpUrl; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.apache.commons.lang.RandomStringUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.sonar.api.SonarQubeSide; +import org.sonar.api.SonarRuntime; +import org.sonar.api.config.internal.MapSettings; +import org.sonar.api.internal.SonarRuntimeImpl; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.Version; +import org.sonar.api.utils.internal.TestSystem2; +import org.sonar.server.util.OkHttpClientProvider; + +import static org.assertj.core.api.Assertions.assertThat; + +public class WebhookCallerImplTest { + + private static final long NOW = 1_500_000_000_000L; + private static final String PROJECT_UUID = "P_UUID1"; + private static final String WEBHOOK_UUID = "WH_UUID1"; + private static final String CE_TASK_UUID = "CE_UUID1"; + private static final String SOME_JSON = "{\"payload\": {}}"; + private static final WebhookPayload PAYLOAD = new WebhookPayload("P1", SOME_JSON); + + @Rule + public MockWebServer server = new MockWebServer(); + + @Rule + public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60)); + + private System2 system = new TestSystem2().setNow(NOW); + + @Test + public void send_posts_payload_to_http_server() throws Exception { + Webhook webhook = new Webhook(WEBHOOK_UUID, PROJECT_UUID, CE_TASK_UUID, RandomStringUtils.randomAlphanumeric(40),"my-webhook", server.url("/ping").toString()); + + server.enqueue(new MockResponse().setBody("pong").setResponseCode(201)); + WebhookDelivery delivery = newSender().call(webhook, PAYLOAD); + + assertThat(delivery.getHttpStatus()).hasValue(201); + assertThat(delivery.getWebhook().getUuid()).isEqualTo(WEBHOOK_UUID); + assertThat(delivery.getDurationInMs().get()).isGreaterThanOrEqualTo(0); + assertThat(delivery.getError()).isEmpty(); + assertThat(delivery.getAt()).isEqualTo(NOW); + assertThat(delivery.getWebhook()).isSameAs(webhook); + assertThat(delivery.getPayload()).isSameAs(PAYLOAD); + + RecordedRequest recordedRequest = server.takeRequest(); + assertThat(recordedRequest.getMethod()).isEqualTo("POST"); + assertThat(recordedRequest.getPath()).isEqualTo("/ping"); + assertThat(recordedRequest.getBody().readUtf8()).isEqualTo(PAYLOAD.getJson()); + assertThat(recordedRequest.getHeader("User-Agent")).isEqualTo("SonarQube/6.2"); + assertThat(recordedRequest.getHeader("Content-Type")).isEqualTo("application/json; charset=utf-8"); + assertThat(recordedRequest.getHeader("X-SonarQube-Project")).isEqualTo(PAYLOAD.getProjectKey()); + } + + @Test + public void silently_catch_error_when_external_server_does_not_answer() throws Exception { + Webhook webhook = new Webhook(WEBHOOK_UUID, PROJECT_UUID, CE_TASK_UUID, RandomStringUtils.randomAlphanumeric(40),"my-webhook", server.url("/ping").toString()); + + server.shutdown(); + WebhookDelivery delivery = newSender().call(webhook, PAYLOAD); + + assertThat(delivery.getHttpStatus()).isEmpty(); + assertThat(delivery.getDurationInMs().get()).isGreaterThanOrEqualTo(0); + // message can be "Connection refused" or "connect timed out" + assertThat(delivery.getErrorMessage().get()).matches("(.*Connection refused.*)|(.*connect timed out.*)"); + assertThat(delivery.getAt()).isEqualTo(NOW); + assertThat(delivery.getWebhook()).isSameAs(webhook); + assertThat(delivery.getPayload()).isSameAs(PAYLOAD); + } + + @Test + public void silently_catch_error_when_url_is_incorrect() { + Webhook webhook = new Webhook(WEBHOOK_UUID, PROJECT_UUID, CE_TASK_UUID, RandomStringUtils.randomAlphanumeric(40),"my-webhook", "this_is_not_an_url"); + + WebhookDelivery delivery = newSender().call(webhook, PAYLOAD); + + assertThat(delivery.getHttpStatus()).isEmpty(); + assertThat(delivery.getDurationInMs().get()).isGreaterThanOrEqualTo(0); + assertThat(delivery.getError().get()).isInstanceOf(IllegalArgumentException.class); + assertThat(delivery.getErrorMessage().get()).isEqualTo("Webhook URL is not valid: this_is_not_an_url"); + assertThat(delivery.getAt()).isEqualTo(NOW); + assertThat(delivery.getWebhook()).isSameAs(webhook); + assertThat(delivery.getPayload()).isSameAs(PAYLOAD); + } + + /** + * SONAR-8799 + */ + @Test + public void redirects_should_be_followed_with_POST_method() throws Exception { + Webhook webhook = new Webhook(WEBHOOK_UUID, PROJECT_UUID, CE_TASK_UUID, RandomStringUtils.randomAlphanumeric(40),"my-webhook", server.url("/redirect").toString()); + + // /redirect redirects to /target + server.enqueue(new MockResponse().setResponseCode(307).setHeader("Location", server.url("target"))); + server.enqueue(new MockResponse().setResponseCode(200)); + + WebhookDelivery delivery = newSender().call(webhook, PAYLOAD); + + assertThat(delivery.getHttpStatus().get()).isEqualTo(200); + assertThat(delivery.getDurationInMs().get()).isGreaterThanOrEqualTo(0); + assertThat(delivery.getError()).isEmpty(); + assertThat(delivery.getAt()).isEqualTo(NOW); + assertThat(delivery.getWebhook()).isSameAs(webhook); + assertThat(delivery.getPayload()).isSameAs(PAYLOAD); + + takeAndVerifyPostRequest("/redirect"); + takeAndVerifyPostRequest("/target"); + } + + @Test + public void credentials_are_propagated_to_POST_redirects() throws Exception { + HttpUrl url = server.url("/redirect").newBuilder().username("theLogin").password("thePassword").build(); + Webhook webhook = new Webhook(WEBHOOK_UUID, PROJECT_UUID, CE_TASK_UUID, RandomStringUtils.randomAlphanumeric(40),"my-webhook", url.toString()); + + // /redirect redirects to /target + server.enqueue(new MockResponse().setResponseCode(307).setHeader("Location", server.url("target"))); + server.enqueue(new MockResponse().setResponseCode(200)); + + WebhookDelivery delivery = newSender().call(webhook, PAYLOAD); + + assertThat(delivery.getHttpStatus().get()).isEqualTo(200); + + RecordedRequest redirectedRequest = takeAndVerifyPostRequest("/redirect"); + assertThat(redirectedRequest.getHeader("Authorization")).isEqualTo(Credentials.basic(url.username(), url.password())); + + RecordedRequest targetRequest = takeAndVerifyPostRequest("/target"); + assertThat(targetRequest.getHeader("Authorization")).isEqualTo(Credentials.basic(url.username(), url.password())); + } + + @Test + public void redirects_throws_ISE_if_header_Location_is_missing() { + HttpUrl url = server.url("/redirect"); + Webhook webhook = new Webhook(WEBHOOK_UUID, PROJECT_UUID, CE_TASK_UUID, RandomStringUtils.randomAlphanumeric(40),"my-webhook", url.toString()); + + server.enqueue(new MockResponse().setResponseCode(307)); + + WebhookDelivery delivery = newSender().call(webhook, PAYLOAD); + + Throwable error = delivery.getError().get(); + assertThat(error) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Missing HTTP header 'Location' in redirect of " + url); + } + + @Test + public void redirects_throws_ISE_if_header_Location_does_not_relate_to_a_supported_protocol() { + HttpUrl url = server.url("/redirect"); + Webhook webhook = new Webhook(WEBHOOK_UUID, PROJECT_UUID, CE_TASK_UUID, RandomStringUtils.randomAlphanumeric(40),"my-webhook", url.toString()); + + server.enqueue(new MockResponse().setResponseCode(307).setHeader("Location", "ftp://foo")); + + WebhookDelivery delivery = newSender().call(webhook, PAYLOAD); + + Throwable error = delivery.getError().get(); + assertThat(error) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Unsupported protocol in redirect of " + url + " to ftp://foo"); + } + + @Test + public void send_basic_authentication_header_if_url_contains_credentials() throws Exception { + HttpUrl url = server.url("/ping").newBuilder().username("theLogin").password("thePassword").build(); + Webhook webhook = new Webhook(WEBHOOK_UUID, PROJECT_UUID, CE_TASK_UUID, RandomStringUtils.randomAlphanumeric(40),"my-webhook", url.toString()); + server.enqueue(new MockResponse().setBody("pong")); + + WebhookDelivery delivery = newSender().call(webhook, PAYLOAD); + + assertThat(delivery.getWebhook().getUrl()) + .isEqualTo(url.toString()) + .contains("://theLogin:thePassword@"); + RecordedRequest recordedRequest = takeAndVerifyPostRequest("/ping"); + assertThat(recordedRequest.getHeader("Authorization")).isEqualTo(Credentials.basic(url.username(), url.password())); + } + + private RecordedRequest takeAndVerifyPostRequest(String expectedPath) throws Exception { + RecordedRequest request = server.takeRequest(); + + assertThat(request.getMethod()).isEqualTo("POST"); + assertThat(request.getPath()).isEqualTo(expectedPath); + assertThat(request.getHeader("User-Agent")).isEqualTo("SonarQube/6.2"); + return request; + } + + private WebhookCaller newSender() { + SonarRuntime runtime = SonarRuntimeImpl.forSonarQube(Version.parse("6.2"), SonarQubeSide.SERVER); + return new WebhookCallerImpl(system, new OkHttpClientProvider().provide(new MapSettings().asConfig(), runtime)); + } +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookDeliveryStorageTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookDeliveryStorageTest.java new file mode 100644 index 00000000000..7273b947c86 --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookDeliveryStorageTest.java @@ -0,0 +1,119 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.webhook; + +import java.io.IOException; +import org.apache.commons.lang.RandomStringUtils; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.webhook.WebhookDbTesting; +import org.sonar.db.webhook.WebhookDeliveryDto; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.db.webhook.WebhookDbTesting.selectAllDeliveryUuids; + +public class WebhookDeliveryStorageTest { + + private static final String DELIVERY_UUID = "abcde1234"; + private static final long NOW = 1_500_000_000_000L; + private static final long TWO_MONTHS_AGO = NOW - 60L * 24 * 60 * 60 * 1000; + private static final long TWO_WEEKS_AGO = NOW - 14L * 24 * 60 * 60 * 1000; + + private final System2 system = mock(System2.class); + + @Rule + public final DbTester dbTester = DbTester.create(system).setDisableDefaultOrganization(true); + + private DbClient dbClient = dbTester.getDbClient(); + private DbSession dbSession = dbTester.getSession(); + private UuidFactory uuidFactory = mock(UuidFactory.class); + private WebhookDeliveryStorage underTest = new WebhookDeliveryStorage(dbClient, system, uuidFactory); + + @Test + public void persist_generates_uuid_then_inserts_record() { + when(uuidFactory.create()).thenReturn(DELIVERY_UUID); + WebhookDelivery delivery = newBuilderTemplate().build(); + + underTest.persist(delivery); + + WebhookDeliveryDto dto = dbClient.webhookDeliveryDao().selectByUuid(dbSession, DELIVERY_UUID).get(); + assertThat(dto.getUuid()).isEqualTo(DELIVERY_UUID); + assertThat(dto.getWebhookUuid()).isEqualTo("WEBHOOK_UUID_1"); + assertThat(dto.getComponentUuid()).isEqualTo(delivery.getWebhook().getComponentUuid()); + assertThat(dto.getCeTaskUuid()).isEqualTo(delivery.getWebhook().getCeTaskUuid().get()); + assertThat(dto.getName()).isEqualTo(delivery.getWebhook().getName()); + assertThat(dto.getUrl()).isEqualTo(delivery.getWebhook().getUrl()); + assertThat(dto.getCreatedAt()).isEqualTo(delivery.getAt()); + assertThat(dto.getHttpStatus()).isEqualTo(delivery.getHttpStatus().get()); + assertThat(dto.getDurationMs()).isEqualTo(delivery.getDurationInMs().get()); + assertThat(dto.getPayload()).isEqualTo(delivery.getPayload().getJson()); + assertThat(dto.getErrorStacktrace()).isNull(); + } + + @Test + public void persist_error_stacktrace() { + when(uuidFactory.create()).thenReturn(DELIVERY_UUID); + WebhookDelivery delivery = newBuilderTemplate() + .setError(new IOException("fail to connect")) + .build(); + + underTest.persist(delivery); + + WebhookDeliveryDto dto = dbClient.webhookDeliveryDao().selectByUuid(dbSession, DELIVERY_UUID).get(); + assertThat(dto.getErrorStacktrace()).contains("java.io.IOException", "fail to connect"); + } + + @Test + public void purge_deletes_records_older_than_one_month_on_the_project() { + when(system.now()).thenReturn(NOW); + dbClient.webhookDeliveryDao().insert(dbSession, newDto("D1", "PROJECT_1", TWO_MONTHS_AGO)); + dbClient.webhookDeliveryDao().insert(dbSession, newDto("D2", "PROJECT_1", TWO_WEEKS_AGO)); + dbClient.webhookDeliveryDao().insert(dbSession, newDto("D3", "PROJECT_2", TWO_MONTHS_AGO)); + dbSession.commit(); + + underTest.purge("PROJECT_1"); + + // do not purge another project PROJECT_2 + assertThat(selectAllDeliveryUuids(dbTester, dbSession)).containsOnly("D2", "D3"); + } + + private static WebhookDelivery.Builder newBuilderTemplate() { + return new WebhookDelivery.Builder() + .setWebhook(new Webhook("WEBHOOK_UUID_1", "COMPONENT1", "TASK1", RandomStringUtils.randomAlphanumeric(40),"Jenkins", "http://jenkins")) + .setPayload(new WebhookPayload("my-project", "{json}")) + .setAt(1_000_000L) + .setHttpStatus(200) + .setDurationInMs(1_000); + } + + private static WebhookDeliveryDto newDto(String uuid, String componentUuid, long at) { + return WebhookDbTesting.newDto() + .setUuid(uuid) + .setComponentUuid(componentUuid) + .setCreatedAt(at); + } +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookDeliveryTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookDeliveryTest.java new file mode 100644 index 00000000000..1e627f560e3 --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookDeliveryTest.java @@ -0,0 +1,82 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.webhook; + +import java.io.IOException; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + + +public class WebhookDeliveryTest { + + @Test + public void isSuccess_returns_false_if_failed_to_send_http_request() { + WebhookDelivery delivery = newBuilderTemplate() + .setError(new IOException("Fail to connect")) + .build(); + + assertThat(delivery.isSuccess()).isFalse(); + } + + @Test + public void isSuccess_returns_false_if_http_response_returns_error_status() { + WebhookDelivery delivery = newBuilderTemplate() + .setHttpStatus(404) + .build(); + + assertThat(delivery.isSuccess()).isFalse(); + } + + @Test + public void isSuccess_returns_true_if_http_response_returns_2xx_code() { + WebhookDelivery delivery = newBuilderTemplate() + .setHttpStatus(204) + .build(); + + assertThat(delivery.isSuccess()).isTrue(); + } + + @Test + public void getErrorMessage_returns_empty_if_no_error() { + WebhookDelivery delivery = newBuilderTemplate().build(); + + assertThat(delivery.getErrorMessage()).isEmpty(); + } + + @Test + public void getErrorMessage_returns_root_cause_message_if_error() { + Exception rootCause = new IOException("fail to connect"); + Exception cause = new IOException("nested", rootCause); + WebhookDelivery delivery = newBuilderTemplate() + .setError(cause) + .build(); + + assertThat(delivery.getErrorMessage().get()).isEqualTo("fail to connect"); + } + + private static WebhookDelivery.Builder newBuilderTemplate() { + return new WebhookDelivery.Builder() + .setWebhook(mock(Webhook.class)) + .setPayload(mock(WebhookPayload.class)) + .setAt(1_000L); + } +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookModuleTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookModuleTest.java new file mode 100644 index 00000000000..58b17bf35ee --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookModuleTest.java @@ -0,0 +1,46 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.webhook; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.core.platform.ComponentContainer; +import org.sonar.server.webhook.WebhookModule; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER; + +public class WebhookModuleTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private WebhookModule underTest = new WebhookModule(); + + @Test + public void verify_count_of_added_components() { + ComponentContainer container = new ComponentContainer(); + + underTest.configure(container); + + assertThat(container.size()).isEqualTo(4 + COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER); + } +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookPayloadFactoryImplTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookPayloadFactoryImplTest.java new file mode 100644 index 00000000000..a494b9a86e7 --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookPayloadFactoryImplTest.java @@ -0,0 +1,329 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.webhook; + +import com.google.common.collect.ImmutableMap; +import java.util.Map; +import javax.annotation.Nullable; +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.measures.Metric; +import org.sonar.api.platform.Server; +import org.sonar.api.utils.System2; +import org.sonar.server.project.Project; +import org.sonar.server.qualitygate.Condition; +import org.sonar.server.qualitygate.EvaluatedCondition; +import org.sonar.server.qualitygate.EvaluatedQualityGate; +import org.sonar.server.qualitygate.QualityGate; + +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySet; +import static java.util.Collections.singleton; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.test.JsonAssert.assertJson; + +public class WebhookPayloadFactoryImplTest { + + private static final String PROJECT_KEY = "P1"; + + private Server server = mock(Server.class); + private System2 system2 = mock(System2.class); + private WebhookPayloadFactory underTest = new WebhookPayloadFactoryImpl(server, system2); + + @Before + public void setUp() throws Exception { + when(server.getPublicRootUrl()).thenReturn("http://foo"); + when(system2.now()).thenReturn(1_500_999L); + } + + @Test + public void create_payload_for_successful_analysis() { + CeTask task = new CeTask("#1", CeTask.Status.SUCCESS); + Condition condition = new Condition("coverage", Condition.Operator.GREATER_THAN, "70.0", "75.0", true); + EvaluatedQualityGate gate = EvaluatedQualityGate.newBuilder() + .setQualityGate(new QualityGate("G1", "Gate One", singleton(condition))) + .setStatus(Metric.Level.WARN) + .addCondition(condition, EvaluatedCondition.EvaluationStatus.WARN, "74.0") + .build(); + ProjectAnalysis analysis = newAnalysis(task, gate, null, 1_500_000_000_000L, emptyMap()); + + WebhookPayload payload = underTest.create(analysis); + assertThat(payload.getProjectKey()).isEqualTo(PROJECT_KEY); + assertJson(payload.getJson()) + .isSimilarTo("{" + + " \"serverUrl\": \"http://foo\"," + + " \"taskId\": \"#1\"," + + " \"status\": \"SUCCESS\"," + + " \"analysedAt\": \"2017-07-14T04:40:00+0200\"," + + " \"changedAt\": \"2017-07-14T04:40:00+0200\"," + + " \"project\": {" + + " \"key\": \"P1\"," + + " \"name\": \"Project One\"," + + " \"url\": \"http://foo/dashboard?id=P1\"" + + " }," + + " \"qualityGate\": {" + + " \"name\": \"Gate One\"," + + " \"status\": \"WARN\"," + + " \"conditions\": [" + + " {" + + " \"metric\": \"coverage\"," + + " \"operator\": \"GREATER_THAN\"," + + " \"value\": \"74.0\"," + + " \"status\": \"WARN\"," + + " \"onLeakPeriod\": true," + + " \"errorThreshold\": \"70.0\"," + + " \"warningThreshold\": \"75.0\"" + + " }" + + " ]" + + " }," + + " \"properties\": {" + + " }" + + "}"); + } + + @Test + public void create_payload_with_gate_conditions_without_value() { + CeTask task = new CeTask("#1", CeTask.Status.SUCCESS); + + Condition condition = new Condition("coverage", Condition.Operator.GREATER_THAN, "70.0", "75.0", false); + EvaluatedQualityGate gate = EvaluatedQualityGate.newBuilder() + .setQualityGate(new QualityGate("G1", "Gate One", singleton(condition))) + .setStatus(Metric.Level.WARN) + .addCondition(condition, EvaluatedCondition.EvaluationStatus.NO_VALUE, null) + .build(); + ProjectAnalysis analysis = newAnalysis(task, gate, null, 1_500_000_000_000L, emptyMap()); + + WebhookPayload payload = underTest.create(analysis); + assertThat(payload.getProjectKey()).isEqualTo(PROJECT_KEY); + assertJson(payload.getJson()) + .isSimilarTo("{" + + " \"serverUrl\": \"http://foo\"," + + " \"taskId\": \"#1\"," + + " \"status\": \"SUCCESS\"," + + " \"analysedAt\": \"2017-07-14T04:40:00+0200\"," + + " \"changedAt\": \"2017-07-14T04:40:00+0200\"," + + " \"project\": {" + + " \"key\": \"P1\"," + + " \"name\": \"Project One\"," + + " \"url\": \"http://foo/dashboard?id=P1\"" + + " }," + + " \"qualityGate\": {" + + " \"name\": \"Gate One\"," + + " \"status\": \"WARN\"," + + " \"conditions\": [" + + " {" + + " \"metric\": \"coverage\"," + + " \"operator\": \"GREATER_THAN\"," + + " \"status\": \"NO_VALUE\"," + + " \"onLeakPeriod\": false," + + " \"errorThreshold\": \"70.0\"," + + " \"warningThreshold\": \"75.0\"" + + " }" + + " ]" + + " }" + + "}"); + } + + @Test + public void create_payload_with_analysis_properties() { + CeTask task = new CeTask("#1", CeTask.Status.SUCCESS); + EvaluatedQualityGate gate = EvaluatedQualityGate.newBuilder() + .setQualityGate(new QualityGate("G1", "Gate One", emptySet())) + .setStatus(Metric.Level.WARN) + .build(); + Map<String, String> scannerProperties = ImmutableMap.of( + "sonar.analysis.revision", "ab45d24", + "sonar.analysis.buildNumber", "B123", + "not.prefixed.with.sonar.analysis", "should be ignored", + "ignored", "should be ignored too"); + ProjectAnalysis analysis = newAnalysis(task, gate, null, 1_500_000_000_000L, scannerProperties); + + WebhookPayload payload = underTest.create(analysis); + assertJson(payload.getJson()) + .isSimilarTo("{" + + " \"serverUrl\": \"http://foo\"," + + " \"taskId\": \"#1\"," + + " \"status\": \"SUCCESS\"," + + " \"analysedAt\": \"2017-07-14T04:40:00+0200\"," + + " \"changedAt\": \"2017-07-14T04:40:00+0200\"," + + " \"project\": {" + + " \"key\": \"P1\"," + + " \"name\": \"Project One\"," + + " \"url\": \"http://foo/dashboard?id=P1\"" + + " }," + + " \"qualityGate\": {" + + " \"name\": \"Gate One\"," + + " \"status\": \"WARN\"," + + " \"conditions\": [" + + " ]" + + " }," + + " \"properties\": {" + + " \"sonar.analysis.revision\": \"ab45d24\"," + + " \"sonar.analysis.buildNumber\": \"B123\"" + + " }" + + "}"); + assertThat(payload.getJson()) + .doesNotContain("not.prefixed.with.sonar.analysis") + .doesNotContain("ignored"); + } + + @Test + public void create_payload_for_failed_analysis() { + CeTask ceTask = new CeTask("#1", CeTask.Status.FAILED); + ProjectAnalysis analysis = newAnalysis(ceTask, null, null, 1_500_000_000_000L, emptyMap()); + + WebhookPayload payload = underTest.create(analysis); + + assertThat(payload.getProjectKey()).isEqualTo(PROJECT_KEY); + assertJson(payload.getJson()) + .isSimilarTo("{" + + " \"serverUrl\": \"http://foo\"," + + " \"taskId\": \"#1\"," + + " \"status\": \"FAILED\"," + + " \"changedAt\": \"2017-07-14T04:40:00+0200\"," + + " \"project\": {" + + " \"key\": \"P1\"," + + " \"name\": \"Project One\"," + + " \"url\": \"http://foo/dashboard?id=P1\"" + + " }," + + " \"properties\": {" + + " }" + + "}"); + } + + @Test + public void create_payload_for_no_analysis_date() { + CeTask ceTask = new CeTask("#1", CeTask.Status.FAILED); + ProjectAnalysis analysis = newAnalysis(ceTask, null, null, null, emptyMap()); + + WebhookPayload payload = underTest.create(analysis); + + assertThat(payload.getProjectKey()).isEqualTo(PROJECT_KEY); + assertJson(payload.getJson()) + .isSimilarTo("{" + + " \"serverUrl\": \"http://foo\"," + + " \"taskId\": \"#1\"," + + " \"status\": \"FAILED\"," + + " \"changedAt\": \"1970-01-01T01:25:00+0100\"," + + " \"project\": {" + + " \"key\": \"P1\"," + + " \"name\": \"Project One\"" + + " }," + + " \"properties\": {" + + " }" + + "}"); + } + + @Test + public void create_payload_on_short_branch() { + CeTask task = new CeTask("#1", CeTask.Status.SUCCESS); + ProjectAnalysis analysis = newAnalysis(task, null, new Branch(false, "feature/foo", Branch.Type.SHORT), 1_500_000_000_000L, emptyMap()); + + WebhookPayload payload = underTest.create(analysis); + assertJson(payload.getJson()) + .isSimilarTo("{" + + "\"branch\": {" + + " \"name\": \"feature/foo\"," + + " \"type\": \"SHORT\"," + + " \"isMain\": false," + + " \"url\": \"http://foo/project/issues?branch=feature%2Ffoo&id=P1&resolved=false\"" + + "}" + + "}"); + } + + @Test + public void create_payload_on_pull_request() { + CeTask task = new CeTask("#1", CeTask.Status.SUCCESS); + ProjectAnalysis analysis = newAnalysis(task, null, new Branch(false, "pr/foo", Branch.Type.PULL_REQUEST), 1_500_000_000_000L, emptyMap()); + + WebhookPayload payload = underTest.create(analysis); + assertJson(payload.getJson()) + .isSimilarTo("{" + + "\"branch\": {" + + " \"name\": \"pr/foo\"," + + " \"type\": \"PULL_REQUEST\"," + + " \"isMain\": false," + + " \"url\": \"http://foo/project/issues?pullRequest=pr%2Ffoo&id=P1&resolved=false\"" + + "}" + + "}"); + } + + @Test + public void create_without_ce_task() { + ProjectAnalysis analysis = newAnalysis(null, null, null, null, emptyMap()); + + WebhookPayload payload = underTest.create(analysis); + String json = payload.getJson(); + assertThat(json).doesNotContain("taskId"); + assertJson(json) + .isSimilarTo("{" + + " \"serverUrl\": \"http://foo\"," + + " \"status\": \"SUCCESS\"," + + " \"changedAt\": \"1970-01-01T01:25:00+0100\"," + + " \"project\": {" + + " \"key\": \"P1\"," + + " \"name\": \"Project One\"" + + " }," + + " \"properties\": {" + + " }" + + "}"); + } + + @Test + public void create_payload_on_long_branch() { + CeTask task = new CeTask("#1", CeTask.Status.SUCCESS); + ProjectAnalysis analysis = newAnalysis(task, null, new Branch(false, "feature/foo", Branch.Type.LONG), 1_500_000_000_000L, emptyMap()); + + WebhookPayload payload = underTest.create(analysis); + assertJson(payload.getJson()) + .isSimilarTo("{" + + "\"branch\": {" + + " \"name\": \"feature/foo\"" + + " \"type\": \"LONG\"" + + " \"isMain\": false," + + " \"url\": \"http://foo/dashboard?branch=feature%2Ffoo&id=P1\"" + + "}" + + "}"); + } + + @Test + public void create_payload_on_main_branch_without_name() { + CeTask task = new CeTask("#1", CeTask.Status.SUCCESS); + ProjectAnalysis analysis = newAnalysis(task, null, new Branch(true, null, Branch.Type.LONG), 1_500_000_000_000L, emptyMap()); + + WebhookPayload payload = underTest.create(analysis); + assertJson(payload.getJson()) + .isSimilarTo("{" + + "\"branch\": {" + + " \"type\": \"LONG\"" + + " \"isMain\": true," + + " \"url\": \"http://foo/dashboard?id=P1\"" + + "}" + + "}"); + } + + private static ProjectAnalysis newAnalysis(@Nullable CeTask task, @Nullable EvaluatedQualityGate gate, + @Nullable Branch branch, @Nullable Long analysisDate, Map<String, String> scannerProperties) { + return new ProjectAnalysis(new Project("P1_UUID", PROJECT_KEY, "Project One"), task, analysisDate == null ? null : new Analysis("A_UUID1", analysisDate), branch, + gate, analysisDate, scannerProperties); + } +} diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookTest.java new file mode 100644 index 00000000000..64778c0dfa4 --- /dev/null +++ b/server/sonar-server-common/src/test/java/org/sonar/server/webhook/WebhookTest.java @@ -0,0 +1,78 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.webhook; + +import java.util.Optional; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; +import static org.assertj.core.api.Assertions.assertThat; + +public class WebhookTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void constructor_with_null_componentUuid_should_throw_NPE() { + expectedException.expect(NullPointerException.class); + + new Webhook(randomAlphanumeric(40), null, null, null, randomAlphanumeric(10), randomAlphanumeric(10)); + } + + @Test + public void constructor_with_null_name_should_throw_NPE() { + expectedException.expect(NullPointerException.class); + + new Webhook(randomAlphanumeric(40), randomAlphanumeric(10), null, null, null, randomAlphanumeric(10)); + } + + @Test + public void constructor_with_null_url_should_throw_NPE() { + expectedException.expect(NullPointerException.class); + + new Webhook(randomAlphanumeric(40), randomAlphanumeric(10), null, null, randomAlphanumeric(10), null); + } + + @Test + public void constructor_with_null_ceTaskUuid_or_analysisUuidurl_should_return_Optional_empty() { + String componentUuid = randomAlphanumeric(10); + String name = randomAlphanumeric(10); + String url = randomAlphanumeric(10); + Webhook underTest = new Webhook(randomAlphanumeric(40), componentUuid, null, null, name, url); + + assertThat(underTest.getComponentUuid()).isEqualTo(componentUuid); + assertThat(underTest.getName()).isEqualTo(name); + assertThat(underTest.getUrl()).isEqualTo(url); + assertThat(underTest.getCeTaskUuid()).isEqualTo(Optional.empty()); + assertThat(underTest.getAnalysisUuid()).isEqualTo(Optional.empty()); + + String ceTaskUuid = randomAlphanumeric(10); + String analysisUuid = randomAlphanumeric(10); + underTest = new Webhook(randomAlphanumeric(40), componentUuid, ceTaskUuid, analysisUuid, name, url); + assertThat(underTest.getComponentUuid()).isEqualTo(componentUuid); + assertThat(underTest.getName()).isEqualTo(name); + assertThat(underTest.getUrl()).isEqualTo(url); + assertThat(underTest.getCeTaskUuid().get()).isEqualTo(ceTaskUuid); + assertThat(underTest.getAnalysisUuid().get()).isEqualTo(analysisUuid); + } +} |