From: Sébastien Lesaint Date: Fri, 22 Jun 2018 15:01:46 +0000 (+0200) Subject: move some classes (including webhooks) to server-common X-Git-Tag: 7.5~923 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=0316e0b883129594466be5b1088a8ee9b1d06461;p=sonarqube.git move some classes (including webhooks) to server-common --- diff --git a/server/sonar-server-common/build.gradle b/server/sonar-server-common/build.gradle index 6e189c3febf..3b2443c6f2a 100644 --- a/server/sonar-server-common/build.gradle +++ b/server/sonar-server-common/build.gradle @@ -15,8 +15,10 @@ dependencies { compile 'com.google.guava:guava' compile 'org.slf4j:slf4j-api' - compile project(':sonar-core') + compile 'com.squareup.okhttp3:okhttp' compile project(':server:sonar-db-dao') + compile project(':sonar-core') + compile project(':sonar-ws') compileOnly project(path: ':sonar-plugin-api') compileOnly project(path: ':server:sonar-process') @@ -25,6 +27,7 @@ dependencies { testCompile 'com.google.code.findbugs:jsr305' testCompile 'com.h2database:h2' + testCompile 'com.squareup.okhttp3:mockwebserver' testCompile 'com.tngtech.java:junit-dataprovider' testCompile 'junit:junit' testCompile 'org.assertj:assertj-core' diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecution.java b/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecution.java new file mode 100644 index 00000000000..053ab2c6f25 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecution.java @@ -0,0 +1,31 @@ +/* + * 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; + +public interface AsyncExecution { + /** + * Add the specified {@link Runnable} in queue for asynchronous processing. + * + * This method returns instantly and {@code r} is executed in another thread. + * + * @throws NullPointerException if r is {@code null} + */ + void addToQueue(Runnable r); +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionExecutorService.java b/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionExecutorService.java new file mode 100644 index 00000000000..1458e9a4eaf --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionExecutorService.java @@ -0,0 +1,24 @@ +/* + * 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; + +public interface AsyncExecutionExecutorService { + void addToQueue(Runnable r); +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionExecutorServiceImpl.java b/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionExecutorServiceImpl.java new file mode 100644 index 00000000000..ce5a82d7135 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionExecutorServiceImpl.java @@ -0,0 +1,77 @@ +/* + * 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 com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.server.util.AbstractStoppableExecutorService; + +import static java.util.concurrent.TimeUnit.MINUTES; + +public class AsyncExecutionExecutorServiceImpl + extends AbstractStoppableExecutorService + implements AsyncExecutionExecutorService, AsyncExecutionMonitoring { + private static final Logger LOG = Loggers.get(AsyncExecutionExecutorServiceImpl.class); + + private static final int MAX_THREAD_COUNT = 10; + private static final int UNLIMITED_QUEUE = Integer.MAX_VALUE; + private static final long KEEP_ALIVE_TIME_IN_MINUTES = 5L; + + public AsyncExecutionExecutorServiceImpl() { + super(createDelegate()); + } + + private static ThreadPoolExecutor createDelegate() { + ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( + MAX_THREAD_COUNT, MAX_THREAD_COUNT, + KEEP_ALIVE_TIME_IN_MINUTES, MINUTES, + new LinkedBlockingQueue<>(UNLIMITED_QUEUE), + new ThreadFactoryBuilder() + .setDaemon(false) + .setNameFormat("SQ_async-%d") + .setUncaughtExceptionHandler(((t, e) -> LOG.error("Thread " + t + " failed unexpectedly", e))) + .build()); + threadPoolExecutor.allowCoreThreadTimeOut(true); + return threadPoolExecutor; + } + + @Override + public void addToQueue(Runnable r) { + this.submit(r); + } + + @Override + public int getQueueSize() { + return delegate.getQueue().size(); + } + + @Override + public int getWorkerCount() { + return delegate.getPoolSize(); + } + + @Override + public int getLargestWorkerCount() { + return delegate.getLargestPoolSize(); + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionImpl.java b/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionImpl.java new file mode 100644 index 00000000000..7940fc3e9a0 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionImpl.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.async; + +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; + +import static java.util.Objects.requireNonNull; + +public class AsyncExecutionImpl implements AsyncExecution { + private static final Logger LOG = Loggers.get(AsyncExecutionImpl.class); + private final AsyncExecutionExecutorService executorService; + + public AsyncExecutionImpl(AsyncExecutionExecutorService executorService) { + this.executorService = executorService; + } + + @Override + public void addToQueue(Runnable r) { + requireNonNull(r); + executorService.addToQueue(() -> { + try { + r.run(); + } catch (Exception e) { + LOG.error("Asynchronous task failed", e); + } + }); + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionMBean.java b/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionMBean.java new file mode 100644 index 00000000000..d908702e818 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionMBean.java @@ -0,0 +1,31 @@ +/* + * 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; + +public interface AsyncExecutionMBean { + + String OBJECT_NAME = "SonarQube:name=AsyncExecution"; + + long getQueueSize(); + + long getWorkerCount(); + + long getLargestWorkerCount(); +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionMBeanImpl.java b/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionMBeanImpl.java new file mode 100644 index 00000000000..f155a95f4a0 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionMBeanImpl.java @@ -0,0 +1,57 @@ +/* + * 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.picocontainer.Startable; +import org.sonar.process.Jmx; + +public class AsyncExecutionMBeanImpl implements AsyncExecutionMBean, Startable { + + private final AsyncExecutionMonitoring asyncExecutionMonitoring; + + public AsyncExecutionMBeanImpl(AsyncExecutionMonitoring asyncExecutionMonitoring) { + this.asyncExecutionMonitoring = asyncExecutionMonitoring; + } + + @Override + public void start() { + Jmx.register(OBJECT_NAME, this); + } + + @Override + public void stop() { + Jmx.unregister(OBJECT_NAME); + } + + @Override + public long getQueueSize() { + return asyncExecutionMonitoring.getQueueSize(); + } + + @Override + public long getWorkerCount() { + return asyncExecutionMonitoring.getWorkerCount(); + } + + @Override + public long getLargestWorkerCount() { + return asyncExecutionMonitoring.getLargestWorkerCount(); + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionModule.java b/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionModule.java new file mode 100644 index 00000000000..d1ef08628db --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionModule.java @@ -0,0 +1,32 @@ +/* + * 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.sonar.core.platform.Module; + +public class AsyncExecutionModule extends Module { + @Override + protected void configureModule() { + add( + AsyncExecutionMBeanImpl.class, + AsyncExecutionExecutorServiceImpl.class, + AsyncExecutionImpl.class); + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionMonitoring.java b/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionMonitoring.java new file mode 100644 index 00000000000..ecc6e6782c6 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/async/AsyncExecutionMonitoring.java @@ -0,0 +1,28 @@ +/* + * 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; + +public interface AsyncExecutionMonitoring { + int getQueueSize(); + + int getWorkerCount(); + + int getLargestWorkerCount(); +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/async/package-info.java b/server/sonar-server-common/src/main/java/org/sonar/server/async/package-info.java new file mode 100644 index 00000000000..a940f445dbe --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/async/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.async; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/project/Project.java b/server/sonar-server-common/src/main/java/org/sonar/server/project/Project.java new file mode 100644 index 00000000000..af220370e34 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/project/Project.java @@ -0,0 +1,109 @@ +/* + * 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 java.util.Objects; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import org.sonar.db.component.ComponentDto; + +@Immutable +public class Project { + + private final String uuid; + private final String key; + private final String name; + private final String description; + + public Project(String uuid, String key, String name) { + this(uuid, key, name, null); + } + + public Project(String uuid, String key, String name, @Nullable String description) { + this.uuid = uuid; + this.key = key; + this.name = name; + this.description = description; + } + + public static Project from(ComponentDto project) { + return new Project(project.uuid(), project.getDbKey(), project.name(), project.description()); + } + + /** + * Always links to a row that exists in database. + */ + public String getUuid() { + return uuid; + } + + /** + * Always links to a row that exists in database. + */ + public String getKey() { + return key; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Project project = (Project) o; + return uuid.equals(project.uuid) + && key.equals(project.key) + && name.equals(project.name); + } + + @Override + public int hashCode() { + return Objects.hash(uuid, key, name); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Project{"); + sb.append("uuid='").append(uuid).append('\''); + sb.append(", key='").append(key).append('\''); + sb.append(", name='").append(name).append('\''); + sb.append(", description=").append(toString(this.description)); + sb.append('}'); + return sb.toString(); + } + + private static String toString(@Nullable String s) { + if (s == null) { + return null; + } + return '\'' + s + '\''; + } + +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/project/package-info.java b/server/sonar-server-common/src/main/java/org/sonar/server/project/package-info.java new file mode 100644 index 00000000000..7771f38cbe9 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/project/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.project; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/Condition.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/Condition.java new file mode 100644 index 00000000000..3319b92dd4a --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/Condition.java @@ -0,0 +1,136 @@ +/* + * 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.Objects; +import java.util.Optional; +import java.util.stream.Stream; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import org.sonar.db.qualitygate.QualityGateConditionDto; + +import static com.google.common.base.Strings.emptyToNull; +import static java.util.Objects.requireNonNull; + +@Immutable +public class Condition { + + private final String metricKey; + private final Operator operator; + @CheckForNull + private final String warningThreshold; + @CheckForNull + private final String errorThreshold; + private final boolean onLeakPeriod; + + public Condition(String metricKey, Operator operator, + @Nullable String errorThreshold, @Nullable String warningThreshold, + boolean onLeakPeriod) { + this.metricKey = requireNonNull(metricKey, "metricKey can't be null"); + this.operator = requireNonNull(operator, "operator can't be null"); + this.onLeakPeriod = onLeakPeriod; + this.errorThreshold = emptyToNull(errorThreshold); + this.warningThreshold = emptyToNull(warningThreshold); + } + + public String getMetricKey() { + return metricKey; + } + + public boolean isOnLeakPeriod() { + return onLeakPeriod; + } + + public Operator getOperator() { + return operator; + } + + public Optional getWarningThreshold() { + return Optional.ofNullable(warningThreshold); + } + + public Optional getErrorThreshold() { + return Optional.ofNullable(errorThreshold); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Condition condition = (Condition) o; + return onLeakPeriod == condition.onLeakPeriod && + Objects.equals(metricKey, condition.metricKey) && + operator == condition.operator && + Objects.equals(warningThreshold, condition.warningThreshold) && + Objects.equals(errorThreshold, condition.errorThreshold); + } + + @Override + public int hashCode() { + return Objects.hash(metricKey, operator, warningThreshold, errorThreshold, onLeakPeriod); + } + + @Override + public String toString() { + return "Condition{" + + "metricKey='" + metricKey + '\'' + + ", operator=" + operator + + ", warningThreshold=" + toString(warningThreshold) + + ", errorThreshold=" + toString(errorThreshold) + + ", onLeakPeriod=" + onLeakPeriod + + '}'; + } + + private static String toString(@Nullable String errorThreshold) { + if (errorThreshold == null) { + return null; + } + return '\'' + errorThreshold + '\''; + } + + public enum Operator { + EQUALS(QualityGateConditionDto.OPERATOR_EQUALS), + NOT_EQUALS(QualityGateConditionDto.OPERATOR_NOT_EQUALS), + GREATER_THAN(QualityGateConditionDto.OPERATOR_GREATER_THAN), + LESS_THAN(QualityGateConditionDto.OPERATOR_LESS_THAN); + + private final String dbValue; + + Operator(String dbValue) { + this.dbValue = dbValue; + } + + public String getDbValue() { + return dbValue; + } + + public static Operator fromDbValue(String s) { + return Stream.of(values()) + .filter(o -> o.getDbValue().equals(s)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Unsupported operator db value: " + s)); + } + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java new file mode 100644 index 00000000000..b47c176ac92 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java @@ -0,0 +1,104 @@ +/* + * 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.Objects; +import java.util.Optional; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +import static java.util.Objects.requireNonNull; + +@Immutable +public class EvaluatedCondition { + private final Condition condition; + private final EvaluationStatus status; + @Nullable + private final String value; + + public EvaluatedCondition(Condition condition, EvaluationStatus status, @Nullable String value) { + this.condition = requireNonNull(condition, "condition can't be null"); + this.status = requireNonNull(status, "status can't be null"); + this.value = value; + } + + public Condition getCondition() { + return condition; + } + + public EvaluationStatus getStatus() { + return status; + } + + public Optional getValue() { + return Optional.ofNullable(value); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + EvaluatedCondition that = (EvaluatedCondition) o; + return Objects.equals(condition, that.condition) && + status == that.status && + Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(condition, status, value); + } + + @Override + public String toString() { + return "EvaluatedCondition{" + + "condition=" + condition + + ", status=" + status + + ", value=" + (value == null ? null : ('\'' + value + '\'')) + + '}'; + } + + /** + * Quality gate condition evaluation status. + */ + public enum EvaluationStatus { + /** + * No measure found or measure had no value. The condition has not been evaluated and therefor ignored in + * the computation of the Quality Gate status. + */ + NO_VALUE, + /** + * Condition evaluated as OK, neither error nor warning thresholds have been reached. + */ + OK, + /** + * Condition evaluated as WARN, only warning thresholds has been reached. + */ + WARN, + /** + * Condition evaluated as ERROR, error thresholds has been reached (and most likely warning thresholds too). + */ + ERROR + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java new file mode 100644 index 00000000000..f568d242c76 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java @@ -0,0 +1,161 @@ +/* + * 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.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import org.sonar.api.measures.Metric; +import org.sonar.server.qualitygate.EvaluatedCondition.EvaluationStatus; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; + +@Immutable +public class EvaluatedQualityGate { + private final QualityGate qualityGate; + private final Metric.Level status; + private final Set evaluatedConditions; + private final boolean ignoredConditionsOnSmallChangeset; + + private EvaluatedQualityGate(QualityGate qualityGate, Metric.Level status, Set evaluatedConditions, boolean ignoredConditionsOnSmallChangeset) { + this.qualityGate = requireNonNull(qualityGate, "qualityGate can't be null"); + this.status = requireNonNull(status, "status can't be null"); + this.evaluatedConditions = evaluatedConditions; + this.ignoredConditionsOnSmallChangeset = ignoredConditionsOnSmallChangeset; + } + + public QualityGate getQualityGate() { + return qualityGate; + } + + public Metric.Level getStatus() { + return status; + } + + public Set getEvaluatedConditions() { + return evaluatedConditions; + } + + public boolean hasIgnoredConditionsOnSmallChangeset() { + return ignoredConditionsOnSmallChangeset; + } + + public static Builder newBuilder() { + return new Builder(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + EvaluatedQualityGate that = (EvaluatedQualityGate) o; + return Objects.equals(qualityGate, that.qualityGate) && + status == that.status && + Objects.equals(evaluatedConditions, that.evaluatedConditions); + } + + @Override + public int hashCode() { + return Objects.hash(qualityGate, status, evaluatedConditions); + } + + @Override + public String toString() { + return "EvaluatedQualityGate{" + + "qualityGate=" + qualityGate + + ", status=" + status + + ", evaluatedConditions=" + evaluatedConditions + + '}'; + } + + public static final class Builder { + private QualityGate qualityGate; + private Metric.Level status; + private final Map evaluatedConditions = new HashMap<>(); + private boolean ignoredConditionsOnSmallChangeset = false; + + private Builder() { + // use static factory method + } + + public Builder setQualityGate(QualityGate qualityGate) { + this.qualityGate = qualityGate; + return this; + } + + public Builder setStatus(Metric.Level status) { + this.status = status; + return this; + } + + public Builder setIgnoredConditionsOnSmallChangeset(boolean b) { + this.ignoredConditionsOnSmallChangeset = b; + return this; + } + + public Builder addCondition(Condition condition, EvaluationStatus status, @Nullable String value) { + evaluatedConditions.put(condition, new EvaluatedCondition(condition, status, value)); + return this; + } + + public Builder addCondition(EvaluatedCondition c) { + evaluatedConditions.put(c.getCondition(), c); + return this; + } + + public Set getEvaluatedConditions() { + return ImmutableSet.copyOf(evaluatedConditions.values()); + } + + public EvaluatedQualityGate build() { + return new EvaluatedQualityGate( + this.qualityGate, + this.status, + checkEvaluatedConditions(qualityGate, evaluatedConditions), + ignoredConditionsOnSmallChangeset); + } + + private static Set checkEvaluatedConditions(QualityGate qualityGate, Map evaluatedConditions) { + Set conditions = qualityGate.getConditions(); + + Set conditionsNotEvaluated = conditions.stream() + .filter(c -> !evaluatedConditions.containsKey(c)) + .collect(Collectors.toSet()); + checkArgument(conditionsNotEvaluated.isEmpty(), "Evaluation missing for the following conditions: %s", conditionsNotEvaluated); + + Set unknownConditions = evaluatedConditions.keySet().stream() + .filter(c -> !conditions.contains(c)) + .collect(Collectors.toSet()); + checkArgument(unknownConditions.isEmpty(), "Evaluation provided for unknown conditions: %s", unknownConditions); + + return ImmutableSet.copyOf(evaluatedConditions.values()); + } + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGate.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGate.java new file mode 100644 index 00000000000..d04742347d7 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/QualityGate.java @@ -0,0 +1,83 @@ +/* + * 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.Objects; +import java.util.Set; +import javax.annotation.concurrent.Immutable; + +import static java.util.Objects.requireNonNull; +import static org.sonar.core.util.stream.MoreCollectors.toSet; + +@Immutable +public class QualityGate { + private final String id; + private final String name; + private final Set conditions; + + public QualityGate(String id, String name, Set conditions) { + this.id = requireNonNull(id, "id can't be null"); + this.name = requireNonNull(name, "name can't be null"); + this.conditions = requireNonNull(conditions, "conditions can't be null") + .stream() + .map(c -> requireNonNull(c, "condition can't be null")) + .collect(toSet(conditions.size())); + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public Set getConditions() { + return conditions; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + QualityGate that = (QualityGate) o; + return Objects.equals(id, that.id) && + Objects.equals(name, that.name) && + Objects.equals(conditions, that.conditions); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, conditions); + } + + @Override + public String toString() { + return "QualityGate{" + + "id=" + id + + ", name='" + name + '\'' + + ", conditions=" + conditions + + '}'; + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/package-info.java b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/package-info.java new file mode 100644 index 00000000000..c378d26367b --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/qualitygate/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.qualitygate; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/util/OkHttpClientProvider.java b/server/sonar-server-common/src/main/java/org/sonar/server/util/OkHttpClientProvider.java new file mode 100644 index 00000000000..d28be2041ad --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/util/OkHttpClientProvider.java @@ -0,0 +1,70 @@ +/* + * 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 okhttp3.OkHttpClient; +import org.picocontainer.injectors.ProviderAdapter; +import org.sonar.api.SonarRuntime; +import org.sonar.api.ce.ComputeEngineSide; +import org.sonar.api.config.Configuration; +import org.sonar.api.server.ServerSide; +import org.sonarqube.ws.client.OkHttpClientBuilder; + +import static java.lang.String.format; +import static org.sonar.process.ProcessProperties.Property.HTTP_PROXY_PASSWORD; +import static org.sonar.process.ProcessProperties.Property.HTTP_PROXY_USER; + +/** + * Provide a unique instance of {@link OkHttpClient} which configuration: + *
    + *
  • supports HTTPS
  • + *
  • supports proxy, including authentication, as defined by the properties like "http.proxyHost" in + * conf/sonar.properties
  • + *
  • has connect and read timeouts of 10 seconds each
  • + *
  • sends automatically the HTTP header "User-Agent" with value "SonarQube/{version}", for instance "SonarQube/6.2"
  • + *
+ */ +@ServerSide +@ComputeEngineSide +public class OkHttpClientProvider extends ProviderAdapter { + + private static final int DEFAULT_CONNECT_TIMEOUT_IN_MS = 10_000; + private static final int DEFAULT_READ_TIMEOUT_IN_MS = 10_000; + + private okhttp3.OkHttpClient okHttpClient; + + /** + * @return a {@link OkHttpClient} singleton + */ + public OkHttpClient provide(Configuration config, SonarRuntime runtime) { + if (okHttpClient == null) { + OkHttpClientBuilder builder = new OkHttpClientBuilder(); + builder.setConnectTimeoutMs(DEFAULT_CONNECT_TIMEOUT_IN_MS); + builder.setReadTimeoutMs(DEFAULT_READ_TIMEOUT_IN_MS); + // no need to define proxy URL as system-wide proxy is used and properly + // configured by bootstrap process. + builder.setProxyLogin(config.get(HTTP_PROXY_USER.getKey()).orElse(null)); + builder.setProxyPassword(config.get(HTTP_PROXY_PASSWORD.getKey()).orElse(null)); + builder.setUserAgent(format("SonarQube/%s", runtime.getApiVersion().toString())); + okHttpClient = builder.build(); + } + return okHttpClient; + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/Analysis.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/Analysis.java new file mode 100644 index 00000000000..5d1c24b1f26 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/Analysis.java @@ -0,0 +1,70 @@ +/* + * 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.base.Objects; +import java.util.Date; + +import static java.util.Objects.requireNonNull; + +public final class Analysis { + private final String uuid; + private final long date; + + public Analysis(String uuid, long date) { + requireNonNull(uuid, "uuid must not be null"); + this.uuid = uuid; + this.date = date; + } + + public String getUuid() { + return uuid; + } + + public Date getDate() { + return new Date(date); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Analysis)) { + return false; + } + Analysis analysis = (Analysis) o; + return Objects.equal(uuid, analysis.uuid) && + Objects.equal(date, analysis.date); + } + + @Override + public int hashCode() { + return Objects.hashCode(uuid, date); + } + + @Override + public String toString() { + return "Analysis{" + + "uuid='" + uuid + '\'' + + ", date=" + date + + '}'; + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/Branch.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/Branch.java new file mode 100644 index 00000000000..2d62e584785 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/Branch.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.util.Objects; +import java.util.Optional; +import javax.annotation.Nullable; + +import static java.util.Objects.requireNonNull; + +public final class Branch { + private final boolean main; + private final String name; + private final Type type; + + public Branch(boolean main, @Nullable String name, Type type) { + this.main = main; + this.name = name; + this.type = requireNonNull(type, "type can't be null"); + } + + public boolean isMain() { + return main; + } + + public Optional getName() { + return Optional.ofNullable(name); + } + + public Type getType() { + return type; + } + + public enum Type { + LONG, SHORT, PULL_REQUEST + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Branch branch = (Branch) o; + return main == branch.main && + Objects.equals(name, branch.name) && + type == branch.type; + } + + @Override + public int hashCode() { + return Objects.hash(main, name, type); + } + + @Override + public String toString() { + return "Branch{" + + "main=" + main + + ", name='" + name + '\'' + + ", type=" + type + + '}'; + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/CeTask.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/CeTask.java new file mode 100644 index 00000000000..226e1f800c6 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/CeTask.java @@ -0,0 +1,72 @@ +/* + * 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.Objects; + +import static java.util.Objects.requireNonNull; + +public final class CeTask { + private final String id; + private final Status status; + + public CeTask(String id, Status status) { + this.id = requireNonNull(id, "id can't be null"); + this.status = requireNonNull(status, "status can't be null"); + } + + public String getId() { + return id; + } + + public Status getStatus() { + return status; + } + + public enum Status { + SUCCESS, FAILED + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CeTask task = (CeTask) o; + return Objects.equals(id, task.id) && + status == task.status; + } + + @Override + public int hashCode() { + return Objects.hash(id, status); + } + + @Override + public String toString() { + return "CeTask{" + + "id='" + id + '\'' + + ", status=" + status + + '}'; + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/ProjectAnalysis.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/ProjectAnalysis.java new file mode 100644 index 00000000000..3a9175adbfb --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/ProjectAnalysis.java @@ -0,0 +1,116 @@ +/* + * 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.Map; +import java.util.Objects; +import java.util.Optional; +import javax.annotation.Nullable; +import org.sonar.server.project.Project; +import org.sonar.server.qualitygate.EvaluatedQualityGate; + +import static com.google.common.collect.ImmutableMap.copyOf; +import static java.util.Objects.requireNonNull; + +public class ProjectAnalysis { + private final Project project; + private final CeTask ceTask; + private final Branch branch; + private final EvaluatedQualityGate qualityGate; + private final Long updatedAt; + private final Map properties; + private final Analysis analysis; + + public ProjectAnalysis(Project project, @Nullable CeTask ceTask, @Nullable Analysis analysis, + @Nullable Branch branch, @Nullable EvaluatedQualityGate qualityGate, @Nullable Long updatedAt, + Map properties) { + this.project = requireNonNull(project, "project can't be null"); + this.ceTask = ceTask; + this.branch = branch; + this.qualityGate = qualityGate; + this.updatedAt = updatedAt; + this.properties = copyOf(requireNonNull(properties, "properties can't be null")); + this.analysis = analysis; + } + + public Optional getCeTask() { + return Optional.ofNullable(ceTask); + } + + public Project getProject() { + return project; + } + + public Optional getBranch() { + return Optional.ofNullable(branch); + } + + public Optional getQualityGate() { + return Optional.ofNullable(qualityGate); + } + + public Map getProperties() { + return properties; + } + + public Optional getAnalysis() { + return Optional.ofNullable(analysis); + } + + public Optional getUpdatedAt() { + return Optional.ofNullable(updatedAt); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ProjectAnalysis that = (ProjectAnalysis) o; + return Objects.equals(project, that.project) && + Objects.equals(ceTask, that.ceTask) && + Objects.equals(branch, that.branch) && + Objects.equals(qualityGate, that.qualityGate) && + Objects.equals(updatedAt, that.updatedAt) && + Objects.equals(properties, that.properties) && + Objects.equals(analysis, that.analysis); + } + + @Override + public int hashCode() { + return Objects.hash(project, ceTask, branch, qualityGate, updatedAt, properties, analysis); + } + + @Override + public String toString() { + return "ProjectAnalysis{" + + "project=" + project + + ", ceTask=" + ceTask + + ", branch=" + branch + + ", qualityGate=" + qualityGate + + ", updatedAt=" + updatedAt + + ", properties=" + properties + + ", analysis=" + analysis + + '}'; + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebHooks.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebHooks.java new file mode 100644 index 00000000000..b1c339f7b12 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebHooks.java @@ -0,0 +1,100 @@ +/* + * 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.Objects; +import java.util.function.Supplier; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.sonar.api.config.Configuration; +import org.sonar.db.component.ComponentDto; + +import static java.util.Objects.requireNonNull; + +public interface WebHooks { + + /** + * Tells whether any webHook is configured for the specified {@link Configuration}. + * + *

+ * This can be used to not do consuming operations before calling + * {@link #sendProjectAnalysisUpdate(Analysis, Supplier)} + */ + boolean isEnabled(ComponentDto projectDto); + + /** + * Calls all WebHooks configured in the specified {@link Configuration} for the specified analysis with the + * {@link WebhookPayload} provided by the specified Supplier. + */ + void sendProjectAnalysisUpdate(Analysis analysis, Supplier payloadSupplier); + + final class Analysis { + private final String projectUuid; + private final String ceTaskUuid; + private final String analysisUuid; + + public Analysis(String projectUuid, @Nullable String analysisUuid, @Nullable String ceTaskUuid) { + this.projectUuid = requireNonNull(projectUuid, "projectUuid can't be null"); + this.analysisUuid = analysisUuid; + this.ceTaskUuid = ceTaskUuid; + } + + public String getProjectUuid() { + return projectUuid; + } + + @CheckForNull + public String getCeTaskUuid() { + return ceTaskUuid; + } + + @CheckForNull + public String getAnalysisUuid() { + return analysisUuid; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Analysis analysis = (Analysis) o; + return Objects.equals(projectUuid, analysis.projectUuid) && + Objects.equals(ceTaskUuid, analysis.ceTaskUuid) && + Objects.equals(analysisUuid, analysis.analysisUuid); + } + + @Override + public int hashCode() { + return Objects.hash(projectUuid, ceTaskUuid, analysisUuid); + } + + @Override + public String toString() { + return "Analysis{" + + "projectUuid='" + projectUuid + '\'' + + ", ceTaskUuid='" + ceTaskUuid + '\'' + + '}'; + } + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebHooksImpl.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebHooksImpl.java new file mode 100644 index 00000000000..26c432656f6 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebHooksImpl.java @@ -0,0 +1,121 @@ +/* + * 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.List; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; +import org.sonar.api.ce.ComputeEngineSide; +import org.sonar.api.server.ServerSide; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.core.util.stream.MoreCollectors; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.webhook.WebhookDao; +import org.sonar.db.webhook.WebhookDto; +import org.sonar.server.async.AsyncExecution; + +import static java.lang.String.format; +import static java.util.Optional.ofNullable; + +@ServerSide +@ComputeEngineSide +public class WebHooksImpl implements WebHooks { + + private static final Logger LOGGER = Loggers.get(WebHooksImpl.class); + + private final WebhookCaller caller; + private final WebhookDeliveryStorage deliveryStorage; + private final AsyncExecution asyncExecution; + private final DbClient dbClient; + + public WebHooksImpl(WebhookCaller caller, WebhookDeliveryStorage deliveryStorage, AsyncExecution asyncExecution, DbClient dbClient) { + this.caller = caller; + this.deliveryStorage = deliveryStorage; + this.asyncExecution = asyncExecution; + this.dbClient = dbClient; + } + + @Override + public boolean isEnabled(ComponentDto projectDto) { + return readWebHooksFrom(projectDto.uuid()) + .findAny() + .isPresent(); + } + + private Stream readWebHooksFrom(String projectUuid) { + try (DbSession dbSession = dbClient.openSession(false)) { + + Optional optionalComponentDto = ofNullable(dbClient.componentDao().selectByUuid(dbSession, projectUuid).orNull()); + ComponentDto componentDto = checkStateWithOptional(optionalComponentDto, "the requested project '%s' was not found", projectUuid); + + if (componentDto.getMainBranchProjectUuid() != null && !componentDto.uuid().equals(componentDto.getMainBranchProjectUuid())) { + Optional mainBranchComponentDto = ofNullable(dbClient.componentDao().selectByUuid(dbSession, componentDto.getMainBranchProjectUuid()).orNull()); + componentDto = checkStateWithOptional(mainBranchComponentDto, "the requested project '%s' was not found", projectUuid); + } + + WebhookDao dao = dbClient.webhookDao(); + return Stream.concat( + dao.selectByProject(dbSession, componentDto).stream(), + dao.selectByOrganizationUuid(dbSession, componentDto.getOrganizationUuid()).stream()); + } + } + + private static T checkStateWithOptional(java.util.Optional value, String message, Object... messageArguments) { + if (!value.isPresent()) { + throw new IllegalStateException(format(message, messageArguments)); + } + + return value.get(); + } + + @Override + public void sendProjectAnalysisUpdate(Analysis analysis, Supplier payloadSupplier) { + List webhooks = readWebHooksFrom(analysis.getProjectUuid()) + .map(dto -> new Webhook(dto.getUuid(), analysis.getProjectUuid(), analysis.getCeTaskUuid(), analysis.getAnalysisUuid(), dto.getName(), dto.getUrl())) + .collect(MoreCollectors.toList()); + if (webhooks.isEmpty()) { + return; + } + + WebhookPayload payload = payloadSupplier.get(); + webhooks.forEach(webhook -> asyncExecution.addToQueue(() -> { + WebhookDelivery delivery = caller.call(webhook, payload); + log(delivery); + deliveryStorage.persist(delivery); + })); + asyncExecution.addToQueue(() -> deliveryStorage.purge(analysis.getProjectUuid())); + } + + private static void log(WebhookDelivery delivery) { + Optional error = delivery.getErrorMessage(); + if (error.isPresent()) { + LOGGER.debug("Failed to send webhook '{}' | url={} | message={}", + delivery.getWebhook().getName(), delivery.getWebhook().getUrl(), error.get()); + } else { + LOGGER.debug("Sent webhook '{}' | url={} | time={}ms | status={}", + delivery.getWebhook().getName(), delivery.getWebhook().getUrl(), delivery.getDurationInMs().orElse(-1), delivery.getHttpStatus().orElse(-1)); + } + } + +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/Webhook.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/Webhook.java new file mode 100644 index 00000000000..8ac3b4c75a8 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/Webhook.java @@ -0,0 +1,71 @@ +/* + * 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 javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +import static java.util.Objects.requireNonNull; +import static java.util.Optional.ofNullable; + +@Immutable +public class Webhook { + + private final String uuid; + private final String componentUuid; + private final String ceTaskUuid; + private final String analysisUuid; + private final String name; + private final String url; + + public Webhook(String uuid, String componentUuid, @Nullable String ceTaskUuid, @Nullable String analysisUuid, String name, String url) { + this.uuid = uuid; + this.componentUuid = requireNonNull(componentUuid); + this.ceTaskUuid = ceTaskUuid; + this.analysisUuid = analysisUuid; + this.name = requireNonNull(name); + this.url = requireNonNull(url); + } + + public String getComponentUuid() { + return componentUuid; + } + + public Optional getCeTaskUuid() { + return ofNullable(ceTaskUuid); + } + + public String getName() { + return name; + } + + public String getUrl() { + return url; + } + + public String getUuid() { + return uuid; + } + + public Optional getAnalysisUuid() { + return ofNullable(analysisUuid); + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookCaller.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookCaller.java new file mode 100644 index 00000000000..979386ac1c4 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookCaller.java @@ -0,0 +1,34 @@ +/* + * 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; + +public interface WebhookCaller { + + /** + * Call webhook by sending a HTTP(S) POST request containing + * the JSON payload. + *
+ * Errors are silently ignored. They don't generate logs or + * throw exceptions. The error status is stored in the + * returned {@link WebhookDelivery}. + */ + WebhookDelivery call(Webhook webhook, WebhookPayload payload); + +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookCallerImpl.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookCallerImpl.java new file mode 100644 index 00000000000..4f0624b6117 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookCallerImpl.java @@ -0,0 +1,143 @@ +/* + * 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 okhttp3.Credentials; +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.sonar.api.ce.ComputeEngineSide; +import org.sonar.api.server.ServerSide; +import org.sonar.api.utils.System2; + +import static java.lang.String.format; +import static java.net.HttpURLConnection.HTTP_MOVED_PERM; +import static java.net.HttpURLConnection.HTTP_MOVED_TEMP; +import static java.nio.charset.StandardCharsets.UTF_8; +import static okhttp3.internal.http.StatusLine.HTTP_PERM_REDIRECT; +import static okhttp3.internal.http.StatusLine.HTTP_TEMP_REDIRECT; +import static org.apache.commons.lang.StringUtils.isNotEmpty; + +@ServerSide +@ComputeEngineSide +public class WebhookCallerImpl implements WebhookCaller { + + private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + private static final String PROJECT_KEY_HEADER = "X-SonarQube-Project"; + + private final System2 system; + private final OkHttpClient okHttpClient; + + public WebhookCallerImpl(System2 system, OkHttpClient okHttpClient) { + this.system = system; + this.okHttpClient = newClientWithoutRedirect(okHttpClient); + } + + @Override + public WebhookDelivery call(Webhook webhook, WebhookPayload payload) { + WebhookDelivery.Builder builder = new WebhookDelivery.Builder(); + long startedAt = system.now(); + builder + .setAt(startedAt) + .setPayload(payload) + .setWebhook(webhook); + + try { + Request request = buildHttpRequest(webhook, payload); + try (Response response = execute(request)) { + builder.setHttpStatus(response.code()); + } + } catch (Exception e) { + builder.setError(e); + } + + return builder + .setDurationInMs((int) (system.now() - startedAt)) + .build(); + } + + private static Request buildHttpRequest(Webhook webhook, WebhookPayload payload) { + HttpUrl url = HttpUrl.parse(webhook.getUrl()); + if (url == null) { + throw new IllegalArgumentException("Webhook URL is not valid: " + webhook.getUrl()); + } + Request.Builder request = new Request.Builder(); + request.url(url); + request.header(PROJECT_KEY_HEADER, payload.getProjectKey()); + if (isNotEmpty(url.username())) { + request.header("Authorization", Credentials.basic(url.username(), url.password(), UTF_8)); + } + + RequestBody body = RequestBody.create(JSON, payload.getJson()); + request.post(body); + return request.build(); + } + + private Response execute(Request request) throws IOException { + Response response = okHttpClient.newCall(request).execute(); + switch (response.code()) { + case HTTP_MOVED_PERM: + case HTTP_MOVED_TEMP: + case HTTP_TEMP_REDIRECT: + case HTTP_PERM_REDIRECT: + // OkHttpClient does not follow the redirect with the same HTTP method. A POST is + // redirected to a GET. Because of that the redirect must be manually + // implemented. + // See: + // https://github.com/square/okhttp/blob/07309c1c7d9e296014268ebd155ebf7ef8679f6c/okhttp/src/main/java/okhttp3/internal/http/RetryAndFollowUpInterceptor.java#L316 + // https://github.com/square/okhttp/issues/936#issuecomment-266430151 + return followPostRedirect(response); + default: + return response; + } + } + + /** + * Inspired by https://github.com/square/okhttp/blob/parent-3.6.0/okhttp/src/main/java/okhttp3/internal/http/RetryAndFollowUpInterceptor.java#L286 + */ + private Response followPostRedirect(Response response) throws IOException { + String location = response.header("Location"); + if (location == null) { + throw new IllegalStateException(format("Missing HTTP header 'Location' in redirect of %s", response.request().url())); + } + HttpUrl url = response.request().url().resolve(location); + + // Don't follow redirects to unsupported protocols. + if (url == null) { + throw new IllegalStateException(format("Unsupported protocol in redirect of %s to %s", response.request().url(), location)); + } + + Request.Builder redirectRequest = response.request().newBuilder(); + redirectRequest.post(response.request().body()); + response.body().close(); + return okHttpClient.newCall(redirectRequest.url(url).build()).execute(); + } + + private static OkHttpClient newClientWithoutRedirect(OkHttpClient client) { + return client.newBuilder() + .followRedirects(false) + .followSslRedirects(false) + .build(); + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookDelivery.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookDelivery.java new file mode 100644 index 00000000000..38194e05d80 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookDelivery.java @@ -0,0 +1,143 @@ +/* + * 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 javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + +import static com.google.common.base.Throwables.getRootCause; +import static java.util.Objects.requireNonNull; + +/** + * A {@link WebhookDelivery} represents the result of a webhook call. + */ +@Immutable +public class WebhookDelivery { + + private final Webhook webhook; + private final WebhookPayload payload; + private final Integer httpStatus; + private final Integer durationInMs; + private final long at; + private final Throwable error; + + private WebhookDelivery(Builder builder) { + this.webhook = requireNonNull(builder.webhook); + this.payload = requireNonNull(builder.payload); + this.httpStatus = builder.httpStatus; + this.durationInMs = builder.durationInMs; + this.at = builder.at; + this.error = builder.error; + } + + public Webhook getWebhook() { + return webhook; + } + + public WebhookPayload getPayload() { + return payload; + } + + /** + * @return the HTTP status if {@link #getError()} is empty, else returns + * {@link Optional#empty()} + */ + public Optional getHttpStatus() { + return Optional.ofNullable(httpStatus); + } + + /** + * @return the duration in milliseconds if {@link #getError()} is empty, + * else returns {@link Optional#empty()} + */ + public Optional getDurationInMs() { + return Optional.ofNullable(durationInMs); + } + + /** + * @return the date of sending + */ + public long getAt() { + return at; + } + + /** + * @return the error raised if the request could not be executed due to a connectivity + * problem or timeout + */ + public Optional getError() { + return Optional.ofNullable(error); + } + + /** + * @return the cause message of {@link #getError()}, Optional.empty() is error is not set. + */ + public Optional getErrorMessage() { + return error != null ? Optional.ofNullable(getRootCause(error).getMessage()) : Optional.empty(); + } + + public boolean isSuccess() { + return httpStatus != null && httpStatus >= 200 && httpStatus < 300; + } + + public static class Builder { + private Webhook webhook; + private WebhookPayload payload; + private Integer httpStatus; + private Integer durationInMs; + private long at; + private Throwable error; + + public Builder setWebhook(Webhook w) { + this.webhook = w; + return this; + } + + public Builder setPayload(WebhookPayload payload) { + this.payload = payload; + return this; + } + + public Builder setHttpStatus(@Nullable Integer httpStatus) { + this.httpStatus = httpStatus; + return this; + } + + public Builder setDurationInMs(@Nullable Integer durationInMs) { + this.durationInMs = durationInMs; + return this; + } + + public Builder setAt(long at) { + this.at = at; + return this; + } + + public Builder setError(@Nullable Throwable t) { + this.error = t; + return this; + } + + public WebhookDelivery build() { + return new WebhookDelivery(this); + } + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookDeliveryStorage.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookDeliveryStorage.java new file mode 100644 index 00000000000..7bf13c484a2 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookDeliveryStorage.java @@ -0,0 +1,84 @@ +/* + * 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.base.Throwables; +import org.sonar.api.ce.ComputeEngineSide; +import org.sonar.api.server.ServerSide; +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.webhook.WebhookDeliveryDao; +import org.sonar.db.webhook.WebhookDeliveryDto; + +/** + * Persist and purge {@link WebhookDelivery} into database + */ +@ServerSide +@ComputeEngineSide +public class WebhookDeliveryStorage { + + private static final long ALIVE_DELAY_MS = 30L * 24 * 60 * 60 * 1000; + + private final DbClient dbClient; + private final System2 system; + private final UuidFactory uuidFactory; + + public WebhookDeliveryStorage(DbClient dbClient, System2 system, UuidFactory uuidFactory) { + this.dbClient = dbClient; + this.system = system; + this.uuidFactory = uuidFactory; + } + + public void persist(WebhookDelivery delivery) { + WebhookDeliveryDao dao = dbClient.webhookDeliveryDao(); + try (DbSession dbSession = dbClient.openSession(false)) { + dao.insert(dbSession, toDto(delivery)); + dbSession.commit(); + } + } + + public void purge(String componentUuid) { + long beforeDate = system.now() - ALIVE_DELAY_MS; + try (DbSession dbSession = dbClient.openSession(false)) { + dbClient.webhookDeliveryDao().deleteComponentBeforeDate(dbSession, componentUuid, beforeDate); + dbSession.commit(); + } + } + + private WebhookDeliveryDto toDto(WebhookDelivery delivery) { + WebhookDeliveryDto dto = new WebhookDeliveryDto(); + dto.setUuid(uuidFactory.create()); + dto.setWebhookUuid(delivery.getWebhook().getUuid()); + dto.setComponentUuid(delivery.getWebhook().getComponentUuid()); + delivery.getWebhook().getCeTaskUuid().ifPresent(dto::setCeTaskUuid); + delivery.getWebhook().getAnalysisUuid().ifPresent(dto::setAnalysisUuid); + dto.setName(delivery.getWebhook().getName()); + dto.setUrl(delivery.getWebhook().getUrl()); + dto.setSuccess(delivery.isSuccess()); + dto.setHttpStatus(delivery.getHttpStatus().orElse(null)); + dto.setDurationMs(delivery.getDurationInMs().orElse(null)); + dto.setErrorStacktrace(delivery.getError().map(Throwables::getStackTraceAsString).orElse(null)); + dto.setPayload(delivery.getPayload().getJson()); + dto.setCreatedAt(delivery.getAt()); + return dto; + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookModule.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookModule.java new file mode 100644 index 00000000000..1768b2739f1 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookModule.java @@ -0,0 +1,33 @@ +/* + * 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.sonar.core.platform.Module; + +public class WebhookModule extends Module { + @Override + protected void configureModule() { + add( + WebhookCallerImpl.class, + WebhookDeliveryStorage.class, + WebHooksImpl.class, + WebhookPayloadFactoryImpl.class); + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookPayload.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookPayload.java new file mode 100644 index 00000000000..197f472c8a4 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookPayload.java @@ -0,0 +1,44 @@ +/* + * 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 javax.annotation.concurrent.Immutable; + +import static java.util.Objects.requireNonNull; + +@Immutable +public class WebhookPayload { + + private final String projectKey; + private final String json; + + public WebhookPayload(String projectKey, String json) { + this.projectKey = requireNonNull(projectKey); + this.json = requireNonNull(json); + } + + public String getProjectKey() { + return projectKey; + } + + public String getJson() { + return json; + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookPayloadFactory.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookPayloadFactory.java new file mode 100644 index 00000000000..cf2751f3243 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookPayloadFactory.java @@ -0,0 +1,27 @@ +/* + * 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; + +@FunctionalInterface +public interface WebhookPayloadFactory { + + WebhookPayload create(ProjectAnalysis analysis); + +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookPayloadFactoryImpl.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookPayloadFactoryImpl.java new file mode 100644 index 00000000000..d6b9b298ec6 --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/WebhookPayloadFactoryImpl.java @@ -0,0 +1,175 @@ +/* + * 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.StringWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.net.URLEncoder; +import java.util.Date; +import java.util.Map; +import java.util.Optional; +import org.sonar.api.ce.ComputeEngineSide; +import org.sonar.api.platform.Server; +import org.sonar.api.server.ServerSide; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.text.JsonWriter; +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 static java.lang.String.format; +import static org.sonar.core.config.CorePropertyDefinitions.SONAR_ANALYSIS; + +@ServerSide +@ComputeEngineSide +public class WebhookPayloadFactoryImpl implements WebhookPayloadFactory { + + private static final String PROPERTY_STATUS = "status"; + private final Server server; + private final System2 system2; + + public WebhookPayloadFactoryImpl(Server server, System2 system2) { + this.server = server; + this.system2 = system2; + } + + @Override + public WebhookPayload create(ProjectAnalysis analysis) { + Writer string = new StringWriter(); + try (JsonWriter writer = JsonWriter.of(string)) { + writer.beginObject(); + writeServer(writer); + writeTask(writer, analysis.getCeTask()); + writeDates(writer, analysis, system2); + writeProject(analysis, writer, analysis.getProject()); + analysis.getBranch().ifPresent(b -> writeBranch(writer, analysis.getProject(), b)); + analysis.getQualityGate().ifPresent(qualityGate -> writeQualityGate(writer, qualityGate)); + writeAnalysisProperties(writer, analysis.getProperties()); + writer.endObject().close(); + return new WebhookPayload(analysis.getProject().getKey(), string.toString()); + } + } + + private void writeServer(JsonWriter writer) { + writer.prop("serverUrl", server.getPublicRootUrl()); + } + + private static void writeDates(JsonWriter writer, ProjectAnalysis analysis, System2 system2) { + analysis.getAnalysis().ifPresent(a -> writer.propDateTime("analysedAt", a.getDate())); + writer.propDateTime("changedAt", new Date(analysis.getUpdatedAt().orElse(system2.now()))); + } + + private void writeProject(ProjectAnalysis analysis, JsonWriter writer, Project project) { + writer + .name("project") + .beginObject() + .prop("key", project.getKey()) + .prop("name", analysis.getProject().getName()) + .prop("url", projectUrlOf(project)) + .endObject(); + } + + private static void writeAnalysisProperties(JsonWriter writer, Map properties) { + writer + .name("properties") + .beginObject(); + properties.entrySet() + .stream() + .filter(prop -> prop.getKey().startsWith(SONAR_ANALYSIS)) + .forEach(prop -> writer.prop(prop.getKey(), prop.getValue())); + writer.endObject(); + } + + private static void writeTask(JsonWriter writer, Optional ceTask) { + ceTask.ifPresent(ceTask1 -> writer.prop("taskId", ceTask1.getId())); + writer.prop(PROPERTY_STATUS, ceTask.map(CeTask::getStatus).orElse(CeTask.Status.SUCCESS).toString()); + } + + private void writeBranch(JsonWriter writer, Project project, Branch branch) { + writer + .name("branch") + .beginObject() + .prop("name", branch.getName().orElse(null)) + .prop("type", branch.getType().name()) + .prop("isMain", branch.isMain()) + .prop("url", branchUrlOf(project, branch)) + .endObject(); + } + + private String projectUrlOf(Project project) { + return format("%s/dashboard?id=%s", server.getPublicRootUrl(), encode(project.getKey())); + } + + private String branchUrlOf(Project project, Branch branch) { + if (branch.getType() == Branch.Type.LONG) { + if (branch.isMain()) { + return projectUrlOf(project); + } + return format("%s/dashboard?branch=%s&id=%s", + server.getPublicRootUrl(), encode(branch.getName().orElse("")), encode(project.getKey())); + } + if (branch.getType() == Branch.Type.SHORT) { + return format("%s/project/issues?branch=%s&id=%s&resolved=false", + server.getPublicRootUrl(), encode(branch.getName().orElse("")), encode(project.getKey())); + } + if (branch.getType() == Branch.Type.PULL_REQUEST) { + return format("%s/project/issues?pullRequest=%s&id=%s&resolved=false", + server.getPublicRootUrl(), encode(branch.getName().orElse("")), encode(project.getKey())); + } + return projectUrlOf(project); + } + + private static void writeQualityGate(JsonWriter writer, EvaluatedQualityGate gate) { + writer + .name("qualityGate") + .beginObject() + .prop("name", gate.getQualityGate().getName()) + .prop(PROPERTY_STATUS, gate.getStatus().toString()) + .name("conditions") + .beginArray(); + for (EvaluatedCondition evaluatedCondition : gate.getEvaluatedConditions()) { + Condition condition = evaluatedCondition.getCondition(); + writer + .beginObject() + .prop("metric", condition.getMetricKey()) + .prop("operator", condition.getOperator().name()); + evaluatedCondition.getValue().ifPresent(t -> writer.prop("value", t)); + writer + .prop(PROPERTY_STATUS, evaluatedCondition.getStatus().name()) + .prop("onLeakPeriod", condition.isOnLeakPeriod()) + .prop("errorThreshold", condition.getErrorThreshold().orElse(null)) + .prop("warningThreshold", condition.getWarningThreshold().orElse(null)) + .endObject(); + } + writer + .endArray() + .endObject(); + } + + private static String encode(String toEncode) { + try { + return URLEncoder.encode(toEncode, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException("Encoding not supported", e); + } + } +} diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/webhook/package-info.java b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/package-info.java new file mode 100644 index 00000000000..e4048c7aa0c --- /dev/null +++ b/server/sonar-server-common/src/main/java/org/sonar/server/webhook/package-info.java @@ -0,0 +1,24 @@ +/* + * 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.webhook; + +import javax.annotation.ParametersAreNonnullByDefault; + 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 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 runnableList = new ArrayList<>(); + + @Override + public void addToQueue(Runnable r) { + runnableList.add(requireNonNull(r)); + } + + public void executeRecorded() { + ArrayList 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 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 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 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 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); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecution.java b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecution.java deleted file mode 100644 index 053ab2c6f25..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecution.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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; - -public interface AsyncExecution { - /** - * Add the specified {@link Runnable} in queue for asynchronous processing. - * - * This method returns instantly and {@code r} is executed in another thread. - * - * @throws NullPointerException if r is {@code null} - */ - void addToQueue(Runnable r); -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionExecutorService.java b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionExecutorService.java deleted file mode 100644 index 1458e9a4eaf..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionExecutorService.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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; - -public interface AsyncExecutionExecutorService { - void addToQueue(Runnable r); -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionExecutorServiceImpl.java b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionExecutorServiceImpl.java deleted file mode 100644 index ce5a82d7135..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionExecutorServiceImpl.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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 com.google.common.util.concurrent.ThreadFactoryBuilder; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; -import org.sonar.server.util.AbstractStoppableExecutorService; - -import static java.util.concurrent.TimeUnit.MINUTES; - -public class AsyncExecutionExecutorServiceImpl - extends AbstractStoppableExecutorService - implements AsyncExecutionExecutorService, AsyncExecutionMonitoring { - private static final Logger LOG = Loggers.get(AsyncExecutionExecutorServiceImpl.class); - - private static final int MAX_THREAD_COUNT = 10; - private static final int UNLIMITED_QUEUE = Integer.MAX_VALUE; - private static final long KEEP_ALIVE_TIME_IN_MINUTES = 5L; - - public AsyncExecutionExecutorServiceImpl() { - super(createDelegate()); - } - - private static ThreadPoolExecutor createDelegate() { - ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( - MAX_THREAD_COUNT, MAX_THREAD_COUNT, - KEEP_ALIVE_TIME_IN_MINUTES, MINUTES, - new LinkedBlockingQueue<>(UNLIMITED_QUEUE), - new ThreadFactoryBuilder() - .setDaemon(false) - .setNameFormat("SQ_async-%d") - .setUncaughtExceptionHandler(((t, e) -> LOG.error("Thread " + t + " failed unexpectedly", e))) - .build()); - threadPoolExecutor.allowCoreThreadTimeOut(true); - return threadPoolExecutor; - } - - @Override - public void addToQueue(Runnable r) { - this.submit(r); - } - - @Override - public int getQueueSize() { - return delegate.getQueue().size(); - } - - @Override - public int getWorkerCount() { - return delegate.getPoolSize(); - } - - @Override - public int getLargestWorkerCount() { - return delegate.getLargestPoolSize(); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionImpl.java b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionImpl.java deleted file mode 100644 index 7940fc3e9a0..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionImpl.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; - -import static java.util.Objects.requireNonNull; - -public class AsyncExecutionImpl implements AsyncExecution { - private static final Logger LOG = Loggers.get(AsyncExecutionImpl.class); - private final AsyncExecutionExecutorService executorService; - - public AsyncExecutionImpl(AsyncExecutionExecutorService executorService) { - this.executorService = executorService; - } - - @Override - public void addToQueue(Runnable r) { - requireNonNull(r); - executorService.addToQueue(() -> { - try { - r.run(); - } catch (Exception e) { - LOG.error("Asynchronous task failed", e); - } - }); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionMBean.java b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionMBean.java deleted file mode 100644 index d908702e818..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionMBean.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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; - -public interface AsyncExecutionMBean { - - String OBJECT_NAME = "SonarQube:name=AsyncExecution"; - - long getQueueSize(); - - long getWorkerCount(); - - long getLargestWorkerCount(); -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionMBeanImpl.java b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionMBeanImpl.java deleted file mode 100644 index f155a95f4a0..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionMBeanImpl.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.picocontainer.Startable; -import org.sonar.process.Jmx; - -public class AsyncExecutionMBeanImpl implements AsyncExecutionMBean, Startable { - - private final AsyncExecutionMonitoring asyncExecutionMonitoring; - - public AsyncExecutionMBeanImpl(AsyncExecutionMonitoring asyncExecutionMonitoring) { - this.asyncExecutionMonitoring = asyncExecutionMonitoring; - } - - @Override - public void start() { - Jmx.register(OBJECT_NAME, this); - } - - @Override - public void stop() { - Jmx.unregister(OBJECT_NAME); - } - - @Override - public long getQueueSize() { - return asyncExecutionMonitoring.getQueueSize(); - } - - @Override - public long getWorkerCount() { - return asyncExecutionMonitoring.getWorkerCount(); - } - - @Override - public long getLargestWorkerCount() { - return asyncExecutionMonitoring.getLargestWorkerCount(); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionModule.java b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionModule.java deleted file mode 100644 index d1ef08628db..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionModule.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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.sonar.core.platform.Module; - -public class AsyncExecutionModule extends Module { - @Override - protected void configureModule() { - add( - AsyncExecutionMBeanImpl.class, - AsyncExecutionExecutorServiceImpl.class, - AsyncExecutionImpl.class); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionMonitoring.java b/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionMonitoring.java deleted file mode 100644 index ecc6e6782c6..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/async/AsyncExecutionMonitoring.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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; - -public interface AsyncExecutionMonitoring { - int getQueueSize(); - - int getWorkerCount(); - - int getLargestWorkerCount(); -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/async/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/async/package-info.java deleted file mode 100644 index a940f445dbe..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/async/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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. - */ -@ParametersAreNonnullByDefault -package org.sonar.server.async; - -import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index 0b1e7bcfbab..cf5e8b244cc 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -28,7 +28,6 @@ import org.sonar.api.resources.ResourceTypes; import org.sonar.api.rules.AnnotationRuleParser; import org.sonar.api.rules.XMLRuleParser; import org.sonar.api.server.rule.RulesDefinitionXmlLoader; -import org.sonar.server.ce.CeModule; import org.sonar.ce.notification.ReportAnalysisFailureNotificationModule; import org.sonar.core.component.DefaultResourceTypes; import org.sonar.core.extension.CoreExtensionsInstaller; @@ -42,6 +41,7 @@ import org.sonar.server.batch.BatchWsModule; import org.sonar.server.branch.BranchFeatureProxyImpl; import org.sonar.server.branch.pr.ws.PullRequestWsModule; import org.sonar.server.branch.ws.BranchWsModule; +import org.sonar.server.ce.CeModule; import org.sonar.server.ce.ws.CeWsModule; import org.sonar.server.component.ComponentCleanerService; import org.sonar.server.component.ComponentFinder; diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/Project.java b/server/sonar-server/src/main/java/org/sonar/server/project/Project.java deleted file mode 100644 index af220370e34..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/project/Project.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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 java.util.Objects; -import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; -import org.sonar.db.component.ComponentDto; - -@Immutable -public class Project { - - private final String uuid; - private final String key; - private final String name; - private final String description; - - public Project(String uuid, String key, String name) { - this(uuid, key, name, null); - } - - public Project(String uuid, String key, String name, @Nullable String description) { - this.uuid = uuid; - this.key = key; - this.name = name; - this.description = description; - } - - public static Project from(ComponentDto project) { - return new Project(project.uuid(), project.getDbKey(), project.name(), project.description()); - } - - /** - * Always links to a row that exists in database. - */ - public String getUuid() { - return uuid; - } - - /** - * Always links to a row that exists in database. - */ - public String getKey() { - return key; - } - - public String getName() { - return name; - } - - public String getDescription() { - return description; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Project project = (Project) o; - return uuid.equals(project.uuid) - && key.equals(project.key) - && name.equals(project.name); - } - - @Override - public int hashCode() { - return Objects.hash(uuid, key, name); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder("Project{"); - sb.append("uuid='").append(uuid).append('\''); - sb.append(", key='").append(key).append('\''); - sb.append(", name='").append(name).append('\''); - sb.append(", description=").append(toString(this.description)); - sb.append('}'); - return sb.toString(); - } - - private static String toString(@Nullable String s) { - if (s == null) { - return null; - } - return '\'' + s + '\''; - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/Condition.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/Condition.java deleted file mode 100644 index 3319b92dd4a..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/Condition.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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.Objects; -import java.util.Optional; -import java.util.stream.Stream; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; -import org.sonar.db.qualitygate.QualityGateConditionDto; - -import static com.google.common.base.Strings.emptyToNull; -import static java.util.Objects.requireNonNull; - -@Immutable -public class Condition { - - private final String metricKey; - private final Operator operator; - @CheckForNull - private final String warningThreshold; - @CheckForNull - private final String errorThreshold; - private final boolean onLeakPeriod; - - public Condition(String metricKey, Operator operator, - @Nullable String errorThreshold, @Nullable String warningThreshold, - boolean onLeakPeriod) { - this.metricKey = requireNonNull(metricKey, "metricKey can't be null"); - this.operator = requireNonNull(operator, "operator can't be null"); - this.onLeakPeriod = onLeakPeriod; - this.errorThreshold = emptyToNull(errorThreshold); - this.warningThreshold = emptyToNull(warningThreshold); - } - - public String getMetricKey() { - return metricKey; - } - - public boolean isOnLeakPeriod() { - return onLeakPeriod; - } - - public Operator getOperator() { - return operator; - } - - public Optional getWarningThreshold() { - return Optional.ofNullable(warningThreshold); - } - - public Optional getErrorThreshold() { - return Optional.ofNullable(errorThreshold); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Condition condition = (Condition) o; - return onLeakPeriod == condition.onLeakPeriod && - Objects.equals(metricKey, condition.metricKey) && - operator == condition.operator && - Objects.equals(warningThreshold, condition.warningThreshold) && - Objects.equals(errorThreshold, condition.errorThreshold); - } - - @Override - public int hashCode() { - return Objects.hash(metricKey, operator, warningThreshold, errorThreshold, onLeakPeriod); - } - - @Override - public String toString() { - return "Condition{" + - "metricKey='" + metricKey + '\'' + - ", operator=" + operator + - ", warningThreshold=" + toString(warningThreshold) + - ", errorThreshold=" + toString(errorThreshold) + - ", onLeakPeriod=" + onLeakPeriod + - '}'; - } - - private static String toString(@Nullable String errorThreshold) { - if (errorThreshold == null) { - return null; - } - return '\'' + errorThreshold + '\''; - } - - public enum Operator { - EQUALS(QualityGateConditionDto.OPERATOR_EQUALS), - NOT_EQUALS(QualityGateConditionDto.OPERATOR_NOT_EQUALS), - GREATER_THAN(QualityGateConditionDto.OPERATOR_GREATER_THAN), - LESS_THAN(QualityGateConditionDto.OPERATOR_LESS_THAN); - - private final String dbValue; - - Operator(String dbValue) { - this.dbValue = dbValue; - } - - public String getDbValue() { - return dbValue; - } - - public static Operator fromDbValue(String s) { - return Stream.of(values()) - .filter(o -> o.getDbValue().equals(s)) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("Unsupported operator db value: " + s)); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java deleted file mode 100644 index b47c176ac92..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * 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.Objects; -import java.util.Optional; -import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; - -import static java.util.Objects.requireNonNull; - -@Immutable -public class EvaluatedCondition { - private final Condition condition; - private final EvaluationStatus status; - @Nullable - private final String value; - - public EvaluatedCondition(Condition condition, EvaluationStatus status, @Nullable String value) { - this.condition = requireNonNull(condition, "condition can't be null"); - this.status = requireNonNull(status, "status can't be null"); - this.value = value; - } - - public Condition getCondition() { - return condition; - } - - public EvaluationStatus getStatus() { - return status; - } - - public Optional getValue() { - return Optional.ofNullable(value); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - EvaluatedCondition that = (EvaluatedCondition) o; - return Objects.equals(condition, that.condition) && - status == that.status && - Objects.equals(value, that.value); - } - - @Override - public int hashCode() { - return Objects.hash(condition, status, value); - } - - @Override - public String toString() { - return "EvaluatedCondition{" + - "condition=" + condition + - ", status=" + status + - ", value=" + (value == null ? null : ('\'' + value + '\'')) + - '}'; - } - - /** - * Quality gate condition evaluation status. - */ - public enum EvaluationStatus { - /** - * No measure found or measure had no value. The condition has not been evaluated and therefor ignored in - * the computation of the Quality Gate status. - */ - NO_VALUE, - /** - * Condition evaluated as OK, neither error nor warning thresholds have been reached. - */ - OK, - /** - * Condition evaluated as WARN, only warning thresholds has been reached. - */ - WARN, - /** - * Condition evaluated as ERROR, error thresholds has been reached (and most likely warning thresholds too). - */ - ERROR - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java deleted file mode 100644 index f568d242c76..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * 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.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; -import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; -import org.sonar.api.measures.Metric; -import org.sonar.server.qualitygate.EvaluatedCondition.EvaluationStatus; - -import static com.google.common.base.Preconditions.checkArgument; -import static java.util.Objects.requireNonNull; - -@Immutable -public class EvaluatedQualityGate { - private final QualityGate qualityGate; - private final Metric.Level status; - private final Set evaluatedConditions; - private final boolean ignoredConditionsOnSmallChangeset; - - private EvaluatedQualityGate(QualityGate qualityGate, Metric.Level status, Set evaluatedConditions, boolean ignoredConditionsOnSmallChangeset) { - this.qualityGate = requireNonNull(qualityGate, "qualityGate can't be null"); - this.status = requireNonNull(status, "status can't be null"); - this.evaluatedConditions = evaluatedConditions; - this.ignoredConditionsOnSmallChangeset = ignoredConditionsOnSmallChangeset; - } - - public QualityGate getQualityGate() { - return qualityGate; - } - - public Metric.Level getStatus() { - return status; - } - - public Set getEvaluatedConditions() { - return evaluatedConditions; - } - - public boolean hasIgnoredConditionsOnSmallChangeset() { - return ignoredConditionsOnSmallChangeset; - } - - public static Builder newBuilder() { - return new Builder(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - EvaluatedQualityGate that = (EvaluatedQualityGate) o; - return Objects.equals(qualityGate, that.qualityGate) && - status == that.status && - Objects.equals(evaluatedConditions, that.evaluatedConditions); - } - - @Override - public int hashCode() { - return Objects.hash(qualityGate, status, evaluatedConditions); - } - - @Override - public String toString() { - return "EvaluatedQualityGate{" + - "qualityGate=" + qualityGate + - ", status=" + status + - ", evaluatedConditions=" + evaluatedConditions + - '}'; - } - - public static final class Builder { - private QualityGate qualityGate; - private Metric.Level status; - private final Map evaluatedConditions = new HashMap<>(); - private boolean ignoredConditionsOnSmallChangeset = false; - - private Builder() { - // use static factory method - } - - public Builder setQualityGate(QualityGate qualityGate) { - this.qualityGate = qualityGate; - return this; - } - - public Builder setStatus(Metric.Level status) { - this.status = status; - return this; - } - - public Builder setIgnoredConditionsOnSmallChangeset(boolean b) { - this.ignoredConditionsOnSmallChangeset = b; - return this; - } - - public Builder addCondition(Condition condition, EvaluationStatus status, @Nullable String value) { - evaluatedConditions.put(condition, new EvaluatedCondition(condition, status, value)); - return this; - } - - public Builder addCondition(EvaluatedCondition c) { - evaluatedConditions.put(c.getCondition(), c); - return this; - } - - public Set getEvaluatedConditions() { - return ImmutableSet.copyOf(evaluatedConditions.values()); - } - - public EvaluatedQualityGate build() { - return new EvaluatedQualityGate( - this.qualityGate, - this.status, - checkEvaluatedConditions(qualityGate, evaluatedConditions), - ignoredConditionsOnSmallChangeset); - } - - private static Set checkEvaluatedConditions(QualityGate qualityGate, Map evaluatedConditions) { - Set conditions = qualityGate.getConditions(); - - Set conditionsNotEvaluated = conditions.stream() - .filter(c -> !evaluatedConditions.containsKey(c)) - .collect(Collectors.toSet()); - checkArgument(conditionsNotEvaluated.isEmpty(), "Evaluation missing for the following conditions: %s", conditionsNotEvaluated); - - Set unknownConditions = evaluatedConditions.keySet().stream() - .filter(c -> !conditions.contains(c)) - .collect(Collectors.toSet()); - checkArgument(unknownConditions.isEmpty(), "Evaluation provided for unknown conditions: %s", unknownConditions); - - return ImmutableSet.copyOf(evaluatedConditions.values()); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGate.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGate.java deleted file mode 100644 index d04742347d7..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGate.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.Objects; -import java.util.Set; -import javax.annotation.concurrent.Immutable; - -import static java.util.Objects.requireNonNull; -import static org.sonar.core.util.stream.MoreCollectors.toSet; - -@Immutable -public class QualityGate { - private final String id; - private final String name; - private final Set conditions; - - public QualityGate(String id, String name, Set conditions) { - this.id = requireNonNull(id, "id can't be null"); - this.name = requireNonNull(name, "name can't be null"); - this.conditions = requireNonNull(conditions, "conditions can't be null") - .stream() - .map(c -> requireNonNull(c, "condition can't be null")) - .collect(toSet(conditions.size())); - } - - public String getId() { - return id; - } - - public String getName() { - return name; - } - - public Set getConditions() { - return conditions; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - QualityGate that = (QualityGate) o; - return Objects.equals(id, that.id) && - Objects.equals(name, that.name) && - Objects.equals(conditions, that.conditions); - } - - @Override - public int hashCode() { - return Objects.hash(id, name, conditions); - } - - @Override - public String toString() { - return "QualityGate{" + - "id=" + id + - ", name='" + name + '\'' + - ", conditions=" + conditions + - '}'; - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/util/OkHttpClientProvider.java b/server/sonar-server/src/main/java/org/sonar/server/util/OkHttpClientProvider.java deleted file mode 100644 index d28be2041ad..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/util/OkHttpClientProvider.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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 okhttp3.OkHttpClient; -import org.picocontainer.injectors.ProviderAdapter; -import org.sonar.api.SonarRuntime; -import org.sonar.api.ce.ComputeEngineSide; -import org.sonar.api.config.Configuration; -import org.sonar.api.server.ServerSide; -import org.sonarqube.ws.client.OkHttpClientBuilder; - -import static java.lang.String.format; -import static org.sonar.process.ProcessProperties.Property.HTTP_PROXY_PASSWORD; -import static org.sonar.process.ProcessProperties.Property.HTTP_PROXY_USER; - -/** - * Provide a unique instance of {@link OkHttpClient} which configuration: - *

    - *
  • supports HTTPS
  • - *
  • supports proxy, including authentication, as defined by the properties like "http.proxyHost" in - * conf/sonar.properties
  • - *
  • has connect and read timeouts of 10 seconds each
  • - *
  • sends automatically the HTTP header "User-Agent" with value "SonarQube/{version}", for instance "SonarQube/6.2"
  • - *
- */ -@ServerSide -@ComputeEngineSide -public class OkHttpClientProvider extends ProviderAdapter { - - private static final int DEFAULT_CONNECT_TIMEOUT_IN_MS = 10_000; - private static final int DEFAULT_READ_TIMEOUT_IN_MS = 10_000; - - private okhttp3.OkHttpClient okHttpClient; - - /** - * @return a {@link OkHttpClient} singleton - */ - public OkHttpClient provide(Configuration config, SonarRuntime runtime) { - if (okHttpClient == null) { - OkHttpClientBuilder builder = new OkHttpClientBuilder(); - builder.setConnectTimeoutMs(DEFAULT_CONNECT_TIMEOUT_IN_MS); - builder.setReadTimeoutMs(DEFAULT_READ_TIMEOUT_IN_MS); - // no need to define proxy URL as system-wide proxy is used and properly - // configured by bootstrap process. - builder.setProxyLogin(config.get(HTTP_PROXY_USER.getKey()).orElse(null)); - builder.setProxyPassword(config.get(HTTP_PROXY_PASSWORD.getKey()).orElse(null)); - builder.setUserAgent(format("SonarQube/%s", runtime.getApiVersion().toString())); - okHttpClient = builder.build(); - } - return okHttpClient; - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/Analysis.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/Analysis.java deleted file mode 100644 index 5d1c24b1f26..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/webhook/Analysis.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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.base.Objects; -import java.util.Date; - -import static java.util.Objects.requireNonNull; - -public final class Analysis { - private final String uuid; - private final long date; - - public Analysis(String uuid, long date) { - requireNonNull(uuid, "uuid must not be null"); - this.uuid = uuid; - this.date = date; - } - - public String getUuid() { - return uuid; - } - - public Date getDate() { - return new Date(date); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof Analysis)) { - return false; - } - Analysis analysis = (Analysis) o; - return Objects.equal(uuid, analysis.uuid) && - Objects.equal(date, analysis.date); - } - - @Override - public int hashCode() { - return Objects.hashCode(uuid, date); - } - - @Override - public String toString() { - return "Analysis{" + - "uuid='" + uuid + '\'' + - ", date=" + date + - '}'; - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/Branch.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/Branch.java deleted file mode 100644 index 2d62e584785..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/webhook/Branch.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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.Objects; -import java.util.Optional; -import javax.annotation.Nullable; - -import static java.util.Objects.requireNonNull; - -public final class Branch { - private final boolean main; - private final String name; - private final Type type; - - public Branch(boolean main, @Nullable String name, Type type) { - this.main = main; - this.name = name; - this.type = requireNonNull(type, "type can't be null"); - } - - public boolean isMain() { - return main; - } - - public Optional getName() { - return Optional.ofNullable(name); - } - - public Type getType() { - return type; - } - - public enum Type { - LONG, SHORT, PULL_REQUEST - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Branch branch = (Branch) o; - return main == branch.main && - Objects.equals(name, branch.name) && - type == branch.type; - } - - @Override - public int hashCode() { - return Objects.hash(main, name, type); - } - - @Override - public String toString() { - return "Branch{" + - "main=" + main + - ", name='" + name + '\'' + - ", type=" + type + - '}'; - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/CeTask.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/CeTask.java deleted file mode 100644 index 226e1f800c6..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/webhook/CeTask.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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.Objects; - -import static java.util.Objects.requireNonNull; - -public final class CeTask { - private final String id; - private final Status status; - - public CeTask(String id, Status status) { - this.id = requireNonNull(id, "id can't be null"); - this.status = requireNonNull(status, "status can't be null"); - } - - public String getId() { - return id; - } - - public Status getStatus() { - return status; - } - - public enum Status { - SUCCESS, FAILED - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - CeTask task = (CeTask) o; - return Objects.equals(id, task.id) && - status == task.status; - } - - @Override - public int hashCode() { - return Objects.hash(id, status); - } - - @Override - public String toString() { - return "CeTask{" + - "id='" + id + '\'' + - ", status=" + status + - '}'; - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/ProjectAnalysis.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/ProjectAnalysis.java deleted file mode 100644 index 3a9175adbfb..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/webhook/ProjectAnalysis.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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.Map; -import java.util.Objects; -import java.util.Optional; -import javax.annotation.Nullable; -import org.sonar.server.project.Project; -import org.sonar.server.qualitygate.EvaluatedQualityGate; - -import static com.google.common.collect.ImmutableMap.copyOf; -import static java.util.Objects.requireNonNull; - -public class ProjectAnalysis { - private final Project project; - private final CeTask ceTask; - private final Branch branch; - private final EvaluatedQualityGate qualityGate; - private final Long updatedAt; - private final Map properties; - private final Analysis analysis; - - public ProjectAnalysis(Project project, @Nullable CeTask ceTask, @Nullable Analysis analysis, - @Nullable Branch branch, @Nullable EvaluatedQualityGate qualityGate, @Nullable Long updatedAt, - Map properties) { - this.project = requireNonNull(project, "project can't be null"); - this.ceTask = ceTask; - this.branch = branch; - this.qualityGate = qualityGate; - this.updatedAt = updatedAt; - this.properties = copyOf(requireNonNull(properties, "properties can't be null")); - this.analysis = analysis; - } - - public Optional getCeTask() { - return Optional.ofNullable(ceTask); - } - - public Project getProject() { - return project; - } - - public Optional getBranch() { - return Optional.ofNullable(branch); - } - - public Optional getQualityGate() { - return Optional.ofNullable(qualityGate); - } - - public Map getProperties() { - return properties; - } - - public Optional getAnalysis() { - return Optional.ofNullable(analysis); - } - - public Optional getUpdatedAt() { - return Optional.ofNullable(updatedAt); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ProjectAnalysis that = (ProjectAnalysis) o; - return Objects.equals(project, that.project) && - Objects.equals(ceTask, that.ceTask) && - Objects.equals(branch, that.branch) && - Objects.equals(qualityGate, that.qualityGate) && - Objects.equals(updatedAt, that.updatedAt) && - Objects.equals(properties, that.properties) && - Objects.equals(analysis, that.analysis); - } - - @Override - public int hashCode() { - return Objects.hash(project, ceTask, branch, qualityGate, updatedAt, properties, analysis); - } - - @Override - public String toString() { - return "ProjectAnalysis{" + - "project=" + project + - ", ceTask=" + ceTask + - ", branch=" + branch + - ", qualityGate=" + qualityGate + - ", updatedAt=" + updatedAt + - ", properties=" + properties + - ", analysis=" + analysis + - '}'; - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebHooks.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebHooks.java deleted file mode 100644 index b1c339f7b12..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebHooks.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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.Objects; -import java.util.function.Supplier; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; -import org.sonar.api.config.Configuration; -import org.sonar.db.component.ComponentDto; - -import static java.util.Objects.requireNonNull; - -public interface WebHooks { - - /** - * Tells whether any webHook is configured for the specified {@link Configuration}. - * - *

- * This can be used to not do consuming operations before calling - * {@link #sendProjectAnalysisUpdate(Analysis, Supplier)} - */ - boolean isEnabled(ComponentDto projectDto); - - /** - * Calls all WebHooks configured in the specified {@link Configuration} for the specified analysis with the - * {@link WebhookPayload} provided by the specified Supplier. - */ - void sendProjectAnalysisUpdate(Analysis analysis, Supplier payloadSupplier); - - final class Analysis { - private final String projectUuid; - private final String ceTaskUuid; - private final String analysisUuid; - - public Analysis(String projectUuid, @Nullable String analysisUuid, @Nullable String ceTaskUuid) { - this.projectUuid = requireNonNull(projectUuid, "projectUuid can't be null"); - this.analysisUuid = analysisUuid; - this.ceTaskUuid = ceTaskUuid; - } - - public String getProjectUuid() { - return projectUuid; - } - - @CheckForNull - public String getCeTaskUuid() { - return ceTaskUuid; - } - - @CheckForNull - public String getAnalysisUuid() { - return analysisUuid; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Analysis analysis = (Analysis) o; - return Objects.equals(projectUuid, analysis.projectUuid) && - Objects.equals(ceTaskUuid, analysis.ceTaskUuid) && - Objects.equals(analysisUuid, analysis.analysisUuid); - } - - @Override - public int hashCode() { - return Objects.hash(projectUuid, ceTaskUuid, analysisUuid); - } - - @Override - public String toString() { - return "Analysis{" + - "projectUuid='" + projectUuid + '\'' + - ", ceTaskUuid='" + ceTaskUuid + '\'' + - '}'; - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebHooksImpl.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebHooksImpl.java deleted file mode 100644 index 5036e81a92c..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebHooksImpl.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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.List; -import java.util.Optional; -import java.util.function.Supplier; -import java.util.stream.Stream; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; -import org.sonar.core.util.stream.MoreCollectors; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.component.ComponentDto; -import org.sonar.db.webhook.WebhookDao; -import org.sonar.db.webhook.WebhookDto; -import org.sonar.server.async.AsyncExecution; - -import static java.util.Optional.ofNullable; -import static org.sonar.server.ws.WsUtils.checkStateWithOptional; - -public class WebHooksImpl implements WebHooks { - - private static final Logger LOGGER = Loggers.get(WebHooksImpl.class); - - private final WebhookCaller caller; - private final WebhookDeliveryStorage deliveryStorage; - private final AsyncExecution asyncExecution; - private final DbClient dbClient; - - public WebHooksImpl(WebhookCaller caller, WebhookDeliveryStorage deliveryStorage, AsyncExecution asyncExecution, DbClient dbClient) { - this.caller = caller; - this.deliveryStorage = deliveryStorage; - this.asyncExecution = asyncExecution; - this.dbClient = dbClient; - } - - @Override - public boolean isEnabled(ComponentDto projectDto) { - return readWebHooksFrom(projectDto.uuid()) - .findAny() - .isPresent(); - } - - private Stream readWebHooksFrom(String projectUuid) { - try (DbSession dbSession = dbClient.openSession(false)) { - - Optional optionalComponentDto = ofNullable(dbClient.componentDao().selectByUuid(dbSession, projectUuid).orNull()); - ComponentDto componentDto = checkStateWithOptional(optionalComponentDto, "the requested project '%s' was not found", projectUuid); - - if (componentDto.getMainBranchProjectUuid() != null && !componentDto.uuid().equals(componentDto.getMainBranchProjectUuid())) { - Optional mainBranchComponentDto = ofNullable(dbClient.componentDao().selectByUuid(dbSession, componentDto.getMainBranchProjectUuid()).orNull()); - componentDto = checkStateWithOptional(mainBranchComponentDto, "the requested project '%s' was not found", projectUuid); - } - - WebhookDao dao = dbClient.webhookDao(); - return Stream.concat( - dao.selectByProject(dbSession, componentDto).stream(), - dao.selectByOrganizationUuid(dbSession, componentDto.getOrganizationUuid()).stream()); - } - } - - @Override - public void sendProjectAnalysisUpdate(Analysis analysis, Supplier payloadSupplier) { - List webhooks = readWebHooksFrom(analysis.getProjectUuid()) - .map(dto -> new Webhook(dto.getUuid(), analysis.getProjectUuid(), analysis.getCeTaskUuid(), analysis.getAnalysisUuid(), dto.getName(), dto.getUrl())) - .collect(MoreCollectors.toList()); - if (webhooks.isEmpty()) { - return; - } - - WebhookPayload payload = payloadSupplier.get(); - webhooks.forEach(webhook -> asyncExecution.addToQueue(() -> { - WebhookDelivery delivery = caller.call(webhook, payload); - log(delivery); - deliveryStorage.persist(delivery); - })); - asyncExecution.addToQueue(() -> deliveryStorage.purge(analysis.getProjectUuid())); - } - - private static void log(WebhookDelivery delivery) { - Optional error = delivery.getErrorMessage(); - if (error.isPresent()) { - LOGGER.debug("Failed to send webhook '{}' | url={} | message={}", - delivery.getWebhook().getName(), delivery.getWebhook().getUrl(), error.get()); - } else { - LOGGER.debug("Sent webhook '{}' | url={} | time={}ms | status={}", - delivery.getWebhook().getName(), delivery.getWebhook().getUrl(), delivery.getDurationInMs().orElse(-1), delivery.getHttpStatus().orElse(-1)); - } - } - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/Webhook.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/Webhook.java deleted file mode 100644 index 8ac3b4c75a8..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/webhook/Webhook.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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 javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; - -import static java.util.Objects.requireNonNull; -import static java.util.Optional.ofNullable; - -@Immutable -public class Webhook { - - private final String uuid; - private final String componentUuid; - private final String ceTaskUuid; - private final String analysisUuid; - private final String name; - private final String url; - - public Webhook(String uuid, String componentUuid, @Nullable String ceTaskUuid, @Nullable String analysisUuid, String name, String url) { - this.uuid = uuid; - this.componentUuid = requireNonNull(componentUuid); - this.ceTaskUuid = ceTaskUuid; - this.analysisUuid = analysisUuid; - this.name = requireNonNull(name); - this.url = requireNonNull(url); - } - - public String getComponentUuid() { - return componentUuid; - } - - public Optional getCeTaskUuid() { - return ofNullable(ceTaskUuid); - } - - public String getName() { - return name; - } - - public String getUrl() { - return url; - } - - public String getUuid() { - return uuid; - } - - public Optional getAnalysisUuid() { - return ofNullable(analysisUuid); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookCaller.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookCaller.java deleted file mode 100644 index 979386ac1c4..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookCaller.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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; - -public interface WebhookCaller { - - /** - * Call webhook by sending a HTTP(S) POST request containing - * the JSON payload. - *
- * Errors are silently ignored. They don't generate logs or - * throw exceptions. The error status is stored in the - * returned {@link WebhookDelivery}. - */ - WebhookDelivery call(Webhook webhook, WebhookPayload payload); - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookCallerImpl.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookCallerImpl.java deleted file mode 100644 index e9e70ca09c1..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookCallerImpl.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * 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 okhttp3.Credentials; -import okhttp3.HttpUrl; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import org.sonar.api.ce.ComputeEngineSide; -import org.sonar.api.utils.System2; - -import static java.lang.String.format; -import static java.net.HttpURLConnection.HTTP_MOVED_PERM; -import static java.net.HttpURLConnection.HTTP_MOVED_TEMP; -import static java.nio.charset.StandardCharsets.UTF_8; -import static okhttp3.internal.http.StatusLine.HTTP_PERM_REDIRECT; -import static okhttp3.internal.http.StatusLine.HTTP_TEMP_REDIRECT; -import static org.apache.commons.lang.StringUtils.isNotEmpty; - -@ComputeEngineSide -public class WebhookCallerImpl implements WebhookCaller { - - private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); - private static final String PROJECT_KEY_HEADER = "X-SonarQube-Project"; - - private final System2 system; - private final OkHttpClient okHttpClient; - - public WebhookCallerImpl(System2 system, OkHttpClient okHttpClient) { - this.system = system; - this.okHttpClient = newClientWithoutRedirect(okHttpClient); - } - - @Override - public WebhookDelivery call(Webhook webhook, WebhookPayload payload) { - WebhookDelivery.Builder builder = new WebhookDelivery.Builder(); - long startedAt = system.now(); - builder - .setAt(startedAt) - .setPayload(payload) - .setWebhook(webhook); - - try { - Request request = buildHttpRequest(webhook, payload); - try (Response response = execute(request)) { - builder.setHttpStatus(response.code()); - } - } catch (Exception e) { - builder.setError(e); - } - - return builder - .setDurationInMs((int) (system.now() - startedAt)) - .build(); - } - - private static Request buildHttpRequest(Webhook webhook, WebhookPayload payload) { - HttpUrl url = HttpUrl.parse(webhook.getUrl()); - if (url == null) { - throw new IllegalArgumentException("Webhook URL is not valid: " + webhook.getUrl()); - } - Request.Builder request = new Request.Builder(); - request.url(url); - request.header(PROJECT_KEY_HEADER, payload.getProjectKey()); - if (isNotEmpty(url.username())) { - request.header("Authorization", Credentials.basic(url.username(), url.password(), UTF_8)); - } - - RequestBody body = RequestBody.create(JSON, payload.getJson()); - request.post(body); - return request.build(); - } - - private Response execute(Request request) throws IOException { - Response response = okHttpClient.newCall(request).execute(); - switch (response.code()) { - case HTTP_MOVED_PERM: - case HTTP_MOVED_TEMP: - case HTTP_TEMP_REDIRECT: - case HTTP_PERM_REDIRECT: - // OkHttpClient does not follow the redirect with the same HTTP method. A POST is - // redirected to a GET. Because of that the redirect must be manually - // implemented. - // See: - // https://github.com/square/okhttp/blob/07309c1c7d9e296014268ebd155ebf7ef8679f6c/okhttp/src/main/java/okhttp3/internal/http/RetryAndFollowUpInterceptor.java#L316 - // https://github.com/square/okhttp/issues/936#issuecomment-266430151 - return followPostRedirect(response); - default: - return response; - } - } - - /** - * Inspired by https://github.com/square/okhttp/blob/parent-3.6.0/okhttp/src/main/java/okhttp3/internal/http/RetryAndFollowUpInterceptor.java#L286 - */ - private Response followPostRedirect(Response response) throws IOException { - String location = response.header("Location"); - if (location == null) { - throw new IllegalStateException(format("Missing HTTP header 'Location' in redirect of %s", response.request().url())); - } - HttpUrl url = response.request().url().resolve(location); - - // Don't follow redirects to unsupported protocols. - if (url == null) { - throw new IllegalStateException(format("Unsupported protocol in redirect of %s to %s", response.request().url(), location)); - } - - Request.Builder redirectRequest = response.request().newBuilder(); - redirectRequest.post(response.request().body()); - response.body().close(); - return okHttpClient.newCall(redirectRequest.url(url).build()).execute(); - } - - private static OkHttpClient newClientWithoutRedirect(OkHttpClient client) { - return client.newBuilder() - .followRedirects(false) - .followSslRedirects(false) - .build(); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookDelivery.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookDelivery.java deleted file mode 100644 index 38194e05d80..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookDelivery.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * 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 javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; - -import static com.google.common.base.Throwables.getRootCause; -import static java.util.Objects.requireNonNull; - -/** - * A {@link WebhookDelivery} represents the result of a webhook call. - */ -@Immutable -public class WebhookDelivery { - - private final Webhook webhook; - private final WebhookPayload payload; - private final Integer httpStatus; - private final Integer durationInMs; - private final long at; - private final Throwable error; - - private WebhookDelivery(Builder builder) { - this.webhook = requireNonNull(builder.webhook); - this.payload = requireNonNull(builder.payload); - this.httpStatus = builder.httpStatus; - this.durationInMs = builder.durationInMs; - this.at = builder.at; - this.error = builder.error; - } - - public Webhook getWebhook() { - return webhook; - } - - public WebhookPayload getPayload() { - return payload; - } - - /** - * @return the HTTP status if {@link #getError()} is empty, else returns - * {@link Optional#empty()} - */ - public Optional getHttpStatus() { - return Optional.ofNullable(httpStatus); - } - - /** - * @return the duration in milliseconds if {@link #getError()} is empty, - * else returns {@link Optional#empty()} - */ - public Optional getDurationInMs() { - return Optional.ofNullable(durationInMs); - } - - /** - * @return the date of sending - */ - public long getAt() { - return at; - } - - /** - * @return the error raised if the request could not be executed due to a connectivity - * problem or timeout - */ - public Optional getError() { - return Optional.ofNullable(error); - } - - /** - * @return the cause message of {@link #getError()}, Optional.empty() is error is not set. - */ - public Optional getErrorMessage() { - return error != null ? Optional.ofNullable(getRootCause(error).getMessage()) : Optional.empty(); - } - - public boolean isSuccess() { - return httpStatus != null && httpStatus >= 200 && httpStatus < 300; - } - - public static class Builder { - private Webhook webhook; - private WebhookPayload payload; - private Integer httpStatus; - private Integer durationInMs; - private long at; - private Throwable error; - - public Builder setWebhook(Webhook w) { - this.webhook = w; - return this; - } - - public Builder setPayload(WebhookPayload payload) { - this.payload = payload; - return this; - } - - public Builder setHttpStatus(@Nullable Integer httpStatus) { - this.httpStatus = httpStatus; - return this; - } - - public Builder setDurationInMs(@Nullable Integer durationInMs) { - this.durationInMs = durationInMs; - return this; - } - - public Builder setAt(long at) { - this.at = at; - return this; - } - - public Builder setError(@Nullable Throwable t) { - this.error = t; - return this; - } - - public WebhookDelivery build() { - return new WebhookDelivery(this); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookDeliveryStorage.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookDeliveryStorage.java deleted file mode 100644 index 0ba9bb95c75..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookDeliveryStorage.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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.base.Throwables; -import org.sonar.api.ce.ComputeEngineSide; -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.webhook.WebhookDeliveryDao; -import org.sonar.db.webhook.WebhookDeliveryDto; - -/** - * Persist and purge {@link WebhookDelivery} into database - */ -@ComputeEngineSide -public class WebhookDeliveryStorage { - - private static final long ALIVE_DELAY_MS = 30L * 24 * 60 * 60 * 1000; - - private final DbClient dbClient; - private final System2 system; - private final UuidFactory uuidFactory; - - public WebhookDeliveryStorage(DbClient dbClient, System2 system, UuidFactory uuidFactory) { - this.dbClient = dbClient; - this.system = system; - this.uuidFactory = uuidFactory; - } - - public void persist(WebhookDelivery delivery) { - WebhookDeliveryDao dao = dbClient.webhookDeliveryDao(); - try (DbSession dbSession = dbClient.openSession(false)) { - dao.insert(dbSession, toDto(delivery)); - dbSession.commit(); - } - } - - public void purge(String componentUuid) { - long beforeDate = system.now() - ALIVE_DELAY_MS; - try (DbSession dbSession = dbClient.openSession(false)) { - dbClient.webhookDeliveryDao().deleteComponentBeforeDate(dbSession, componentUuid, beforeDate); - dbSession.commit(); - } - } - - private WebhookDeliveryDto toDto(WebhookDelivery delivery) { - WebhookDeliveryDto dto = new WebhookDeliveryDto(); - dto.setUuid(uuidFactory.create()); - dto.setWebhookUuid(delivery.getWebhook().getUuid()); - dto.setComponentUuid(delivery.getWebhook().getComponentUuid()); - delivery.getWebhook().getCeTaskUuid().ifPresent(dto::setCeTaskUuid); - delivery.getWebhook().getAnalysisUuid().ifPresent(dto::setAnalysisUuid); - dto.setName(delivery.getWebhook().getName()); - dto.setUrl(delivery.getWebhook().getUrl()); - dto.setSuccess(delivery.isSuccess()); - dto.setHttpStatus(delivery.getHttpStatus().orElse(null)); - dto.setDurationMs(delivery.getDurationInMs().orElse(null)); - dto.setErrorStacktrace(delivery.getError().map(Throwables::getStackTraceAsString).orElse(null)); - dto.setPayload(delivery.getPayload().getJson()); - dto.setCreatedAt(delivery.getAt()); - return dto; - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookModule.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookModule.java deleted file mode 100644 index 1768b2739f1..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookModule.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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.sonar.core.platform.Module; - -public class WebhookModule extends Module { - @Override - protected void configureModule() { - add( - WebhookCallerImpl.class, - WebhookDeliveryStorage.class, - WebHooksImpl.class, - WebhookPayloadFactoryImpl.class); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookPayload.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookPayload.java deleted file mode 100644 index 197f472c8a4..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookPayload.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 javax.annotation.concurrent.Immutable; - -import static java.util.Objects.requireNonNull; - -@Immutable -public class WebhookPayload { - - private final String projectKey; - private final String json; - - public WebhookPayload(String projectKey, String json) { - this.projectKey = requireNonNull(projectKey); - this.json = requireNonNull(json); - } - - public String getProjectKey() { - return projectKey; - } - - public String getJson() { - return json; - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookPayloadFactory.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookPayloadFactory.java deleted file mode 100644 index cf2751f3243..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookPayloadFactory.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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; - -@FunctionalInterface -public interface WebhookPayloadFactory { - - WebhookPayload create(ProjectAnalysis analysis); - -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookPayloadFactoryImpl.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookPayloadFactoryImpl.java deleted file mode 100644 index 32440c4b2cb..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookPayloadFactoryImpl.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * 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.StringWriter; -import java.io.UnsupportedEncodingException; -import java.io.Writer; -import java.net.URLEncoder; -import java.util.Date; -import java.util.Map; -import java.util.Optional; -import org.sonar.api.ce.ComputeEngineSide; -import org.sonar.api.platform.Server; -import org.sonar.api.utils.System2; -import org.sonar.api.utils.text.JsonWriter; -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 static java.lang.String.format; -import static org.sonar.core.config.CorePropertyDefinitions.SONAR_ANALYSIS; - -@ComputeEngineSide -public class WebhookPayloadFactoryImpl implements WebhookPayloadFactory { - - private static final String PROPERTY_STATUS = "status"; - private final Server server; - private final System2 system2; - - public WebhookPayloadFactoryImpl(Server server, System2 system2) { - this.server = server; - this.system2 = system2; - } - - @Override - public WebhookPayload create(ProjectAnalysis analysis) { - Writer string = new StringWriter(); - try (JsonWriter writer = JsonWriter.of(string)) { - writer.beginObject(); - writeServer(writer); - writeTask(writer, analysis.getCeTask()); - writeDates(writer, analysis, system2); - writeProject(analysis, writer, analysis.getProject()); - analysis.getBranch().ifPresent(b -> writeBranch(writer, analysis.getProject(), b)); - analysis.getQualityGate().ifPresent(qualityGate -> writeQualityGate(writer, qualityGate)); - writeAnalysisProperties(writer, analysis.getProperties()); - writer.endObject().close(); - return new WebhookPayload(analysis.getProject().getKey(), string.toString()); - } - } - - private void writeServer(JsonWriter writer) { - writer.prop("serverUrl", server.getPublicRootUrl()); - } - - private static void writeDates(JsonWriter writer, ProjectAnalysis analysis, System2 system2) { - analysis.getAnalysis().ifPresent(a -> writer.propDateTime("analysedAt", a.getDate())); - writer.propDateTime("changedAt", new Date(analysis.getUpdatedAt().orElse(system2.now()))); - } - - private void writeProject(ProjectAnalysis analysis, JsonWriter writer, Project project) { - writer - .name("project") - .beginObject() - .prop("key", project.getKey()) - .prop("name", analysis.getProject().getName()) - .prop("url", projectUrlOf(project)) - .endObject(); - } - - private static void writeAnalysisProperties(JsonWriter writer, Map properties) { - writer - .name("properties") - .beginObject(); - properties.entrySet() - .stream() - .filter(prop -> prop.getKey().startsWith(SONAR_ANALYSIS)) - .forEach(prop -> writer.prop(prop.getKey(), prop.getValue())); - writer.endObject(); - } - - private static void writeTask(JsonWriter writer, Optional ceTask) { - ceTask.ifPresent(ceTask1 -> writer.prop("taskId", ceTask1.getId())); - writer.prop(PROPERTY_STATUS, ceTask.map(CeTask::getStatus).orElse(CeTask.Status.SUCCESS).toString()); - } - - private void writeBranch(JsonWriter writer, Project project, Branch branch) { - writer - .name("branch") - .beginObject() - .prop("name", branch.getName().orElse(null)) - .prop("type", branch.getType().name()) - .prop("isMain", branch.isMain()) - .prop("url", branchUrlOf(project, branch)) - .endObject(); - } - - private String projectUrlOf(Project project) { - return format("%s/dashboard?id=%s", server.getPublicRootUrl(), encode(project.getKey())); - } - - private String branchUrlOf(Project project, Branch branch) { - if (branch.getType() == Branch.Type.LONG) { - if (branch.isMain()) { - return projectUrlOf(project); - } - return format("%s/dashboard?branch=%s&id=%s", - server.getPublicRootUrl(), encode(branch.getName().orElse("")), encode(project.getKey())); - } - if (branch.getType() == Branch.Type.SHORT) { - return format("%s/project/issues?branch=%s&id=%s&resolved=false", - server.getPublicRootUrl(), encode(branch.getName().orElse("")), encode(project.getKey())); - } - if (branch.getType() == Branch.Type.PULL_REQUEST) { - return format("%s/project/issues?pullRequest=%s&id=%s&resolved=false", - server.getPublicRootUrl(), encode(branch.getName().orElse("")), encode(project.getKey())); - } - return projectUrlOf(project); - } - - private static void writeQualityGate(JsonWriter writer, EvaluatedQualityGate gate) { - writer - .name("qualityGate") - .beginObject() - .prop("name", gate.getQualityGate().getName()) - .prop(PROPERTY_STATUS, gate.getStatus().toString()) - .name("conditions") - .beginArray(); - for (EvaluatedCondition evaluatedCondition : gate.getEvaluatedConditions()) { - Condition condition = evaluatedCondition.getCondition(); - writer - .beginObject() - .prop("metric", condition.getMetricKey()) - .prop("operator", condition.getOperator().name()); - evaluatedCondition.getValue().ifPresent(t -> writer.prop("value", t)); - writer - .prop(PROPERTY_STATUS, evaluatedCondition.getStatus().name()) - .prop("onLeakPeriod", condition.isOnLeakPeriod()) - .prop("errorThreshold", condition.getErrorThreshold().orElse(null)) - .prop("warningThreshold", condition.getWarningThreshold().orElse(null)) - .endObject(); - } - writer - .endArray() - .endObject(); - } - - private static String encode(String toEncode) { - try { - return URLEncoder.encode(toEncode, "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new IllegalStateException("Encoding not supported", e); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/package-info.java deleted file mode 100644 index e4048c7aa0c..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/webhook/package-info.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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. - */ -@ParametersAreNonnullByDefault -package org.sonar.server.webhook; - -import javax.annotation.ParametersAreNonnullByDefault; - diff --git a/server/sonar-server/src/test/java/org/sonar/server/async/AsyncExecutionExecutorServiceImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/async/AsyncExecutionExecutorServiceImplTest.java deleted file mode 100644 index 8aa2ee5165b..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/async/AsyncExecutionExecutorServiceImplTest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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/src/test/java/org/sonar/server/async/AsyncExecutionImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/async/AsyncExecutionImplTest.java deleted file mode 100644 index 0ff2d3cec07..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/async/AsyncExecutionImplTest.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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/src/test/java/org/sonar/server/async/AsyncExecutionMBeanImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/async/AsyncExecutionMBeanImplTest.java deleted file mode 100644 index e534273abfb..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/async/AsyncExecutionMBeanImplTest.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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/src/test/java/org/sonar/server/project/ProjectTest.java b/server/sonar-server/src/test/java/org/sonar/server/project/ProjectTest.java deleted file mode 100644 index 4f2a351e844..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/project/ProjectTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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/src/test/java/org/sonar/server/qualitygate/ConditionTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ConditionTest.java deleted file mode 100644 index ba0b79a7fef..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ConditionTest.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * 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/src/test/java/org/sonar/server/qualitygate/EvaluatedConditionTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedConditionTest.java deleted file mode 100644 index 7cadd61e89f..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedConditionTest.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java deleted file mode 100644 index b02d5dd2cd5..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * 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/src/test/java/org/sonar/server/qualitygate/QualityGateTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateTest.java deleted file mode 100644 index d1fe1dde3ca..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateTest.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * 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 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/src/test/java/org/sonar/server/util/OkHttpClientProviderTest.java b/server/sonar-server/src/test/java/org/sonar/server/util/OkHttpClientProviderTest.java deleted file mode 100644 index b6f2f037a98..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/util/OkHttpClientProviderTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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/src/test/java/org/sonar/server/webhook/AnalysisTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/AnalysisTest.java deleted file mode 100644 index 5f307d904d4..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/webhook/AnalysisTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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/src/test/java/org/sonar/server/webhook/AsynchronousWebHooksImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/AsynchronousWebHooksImplTest.java deleted file mode 100644 index b7ca6d75fde..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/webhook/AsynchronousWebHooksImplTest.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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 runnableList = new ArrayList<>(); - - @Override - public void addToQueue(Runnable r) { - runnableList.add(requireNonNull(r)); - } - - public void executeRecorded() { - ArrayList runnables = new ArrayList<>(runnableList); - runnableList.clear(); - runnables.forEach(Runnable::run); - } - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/BranchTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/BranchTest.java deleted file mode 100644 index b935d89b248..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/webhook/BranchTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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/src/test/java/org/sonar/server/webhook/CeTaskTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/CeTaskTest.java deleted file mode 100644 index 0ed58b7f00a..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/webhook/CeTaskTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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/src/test/java/org/sonar/server/webhook/ProjectAnalysisTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/ProjectAnalysisTest.java deleted file mode 100644 index 0a453709b67..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/webhook/ProjectAnalysisTest.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * 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 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/src/test/java/org/sonar/server/webhook/SynchronousWebHooksImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/SynchronousWebHooksImplTest.java deleted file mode 100644 index de8d298052f..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/webhook/SynchronousWebHooksImplTest.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * 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/src/test/java/org/sonar/server/webhook/TestWebhookCaller.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/TestWebhookCaller.java deleted file mode 100644 index 0634f160622..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/webhook/TestWebhookCaller.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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 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/src/test/java/org/sonar/server/webhook/WebhookCallerImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookCallerImplTest.java deleted file mode 100644 index 60ac24ee262..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookCallerImplTest.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * 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/src/test/java/org/sonar/server/webhook/WebhookDeliveryStorageTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookDeliveryStorageTest.java deleted file mode 100644 index 1fac932cbc9..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookDeliveryStorageTest.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * 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.newDto; -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/src/test/java/org/sonar/server/webhook/WebhookDeliveryTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookDeliveryTest.java deleted file mode 100644 index 534702838c3..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookDeliveryTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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 org.sonar.server.webhook.Webhook; -import org.sonar.server.webhook.WebhookDelivery; -import org.sonar.server.webhook.WebhookPayload; - -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/src/test/java/org/sonar/server/webhook/WebhookModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookModuleTest.java deleted file mode 100644 index 2615c87ade7..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookModuleTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 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/src/test/java/org/sonar/server/webhook/WebhookPayloadFactoryImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookPayloadFactoryImplTest.java deleted file mode 100644 index a494b9a86e7..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookPayloadFactoryImplTest.java +++ /dev/null @@ -1,329 +0,0 @@ -/* - * 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 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 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/src/test/java/org/sonar/server/webhook/WebhookTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookTest.java deleted file mode 100644 index 64778c0dfa4..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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); - } -}