import org.sonar.server.issue.TransitionAction;
import org.sonar.server.issue.notification.IssueChangeNotification;
import org.sonar.server.notification.NotificationManager;
-import org.sonar.server.qualitygate.changeevent.IssueChangeTrigger;
+import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
+import org.sonar.server.qualitygate.changeevent.QGChangeEventFactory;
+import org.sonar.server.qualitygate.changeevent.QGChangeEventListeners;
import org.sonar.server.user.UserSession;
import org.sonarqube.ws.Issues;
private final IssueStorage issueStorage;
private final NotificationManager notificationService;
private final List<Action> actions;
- private final IssueChangeTrigger issueChangeTrigger;
+ private final QGChangeEventFactory qgChangeEventFactory;
+ private final QGChangeEventListeners qgChangeEventListeners;
- public BulkChangeAction(System2 system2, UserSession userSession, DbClient dbClient, IssueStorage issueStorage, NotificationManager notificationService, List<Action> actions,
- IssueChangeTrigger issueChangeTrigger) {
+ public BulkChangeAction(System2 system2, UserSession userSession, DbClient dbClient, IssueStorage issueStorage,
+ NotificationManager notificationService, List<Action> actions,
+ QGChangeEventFactory qgChangeEventFactory, QGChangeEventListeners qgChangeEventListeners) {
this.system2 = system2;
this.userSession = userSession;
this.dbClient = dbClient;
this.issueStorage = issueStorage;
this.notificationService = notificationService;
this.actions = actions;
- this.issueChangeTrigger = issueChangeTrigger;
+ this.qgChangeEventFactory = qgChangeEventFactory;
+ this.qgChangeEventListeners = qgChangeEventListeners;
}
@Override
issueStorage.save(items);
items.forEach(sendNotification(issueChangeContext, bulkChangeData));
buildWebhookIssueChange(bulkChangeData.propertiesByActions)
- .ifPresent(issueChange -> issueChangeTrigger.onChange(
- new IssueChangeTrigger.IssueChangeData(
+ .ifPresent(issueChange -> {
+ QGChangeEventFactory.IssueChangeData issueChangeData = new QGChangeEventFactory.IssueChangeData(
bulkChangeData.issues.stream().filter(i -> result.success.contains(i.key())).collect(MoreCollectors.toList()),
- copyOf(bulkChangeData.componentsByUuid.values())),
- issueChange,
- issueChangeContext));
+ copyOf(bulkChangeData.componentsByUuid.values()));
+ List<QGChangeEvent> qgChangeEvents = qgChangeEventFactory.from(issueChangeData, issueChange, issueChangeContext);
+ qgChangeEventListeners.broadcastOnIssueChange(issueChangeData, qgChangeEvents);
+ });
return result;
};
}
- private static Optional<IssueChangeTrigger.IssueChange> buildWebhookIssueChange(Map<String, Map<String, Object>> propertiesByActions) {
+ private static Optional<QGChangeEventFactory.IssueChange> buildWebhookIssueChange(Map<String, Map<String, Object>> propertiesByActions) {
RuleType ruleType = Optional.ofNullable(propertiesByActions.get(SetTypeAction.SET_TYPE_KEY))
.map(t -> (String) t.get(SetTypeAction.TYPE_PARAMETER))
.map(RuleType::valueOf)
if (ruleType == null && transitionKey == null) {
return Optional.empty();
}
- return Optional.of(new IssueChangeTrigger.IssueChange(ruleType, transitionKey));
+ return Optional.of(new QGChangeEventFactory.IssueChange(ruleType, transitionKey));
}
private static Predicate<DefaultIssue> bulkChange(IssueChangeContext issueChangeContext, BulkChangeData bulkChangeData, BulkChangeResult result) {
import com.google.common.io.Resources;
import java.util.Date;
+import java.util.List;
import org.sonar.api.issue.DefaultTransitions;
import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.core.util.Uuids;
-import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.issue.IssueDto;
import org.sonar.server.issue.IssueFinder;
import org.sonar.server.issue.IssueUpdater;
import org.sonar.server.issue.TransitionService;
-import org.sonar.server.qualitygate.changeevent.IssueChangeTrigger;
+import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
+import org.sonar.server.qualitygate.changeevent.QGChangeEventFactory;
+import org.sonar.server.qualitygate.changeevent.QGChangeEventListeners;
import org.sonar.server.user.UserSession;
import static com.google.common.collect.ImmutableList.copyOf;
+import static org.sonar.core.util.stream.MoreCollectors.toList;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_DO_TRANSITION;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUE;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TRANSITION;
private final TransitionService transitionService;
private final OperationResponseWriter responseWriter;
private final System2 system2;
- private final IssueChangeTrigger issueChangeTrigger;
+ private final QGChangeEventFactory qgChangeEventFactory;
+ private final QGChangeEventListeners qgChangeEventListeners;
public DoTransitionAction(DbClient dbClient, UserSession userSession, IssueFinder issueFinder, IssueUpdater issueUpdater, TransitionService transitionService,
- OperationResponseWriter responseWriter, System2 system2, IssueChangeTrigger issueChangeTrigger) {
+ OperationResponseWriter responseWriter, System2 system2, QGChangeEventFactory qgChangeEventFactory, QGChangeEventListeners qgChangeEventListeners) {
this.dbClient = dbClient;
this.userSession = userSession;
this.issueFinder = issueFinder;
this.transitionService = transitionService;
this.responseWriter = responseWriter;
this.system2 = system2;
- this.issueChangeTrigger = issueChangeTrigger;
+ this.qgChangeEventFactory = qgChangeEventFactory;
+ this.qgChangeEventListeners = qgChangeEventListeners;
}
@Override
transitionService.checkTransitionPermission(transitionKey, defaultIssue);
if (transitionService.doTransition(defaultIssue, context, transitionKey)) {
SearchResponseData searchResponseData = issueUpdater.saveIssueAndPreloadSearchResponseData(session, defaultIssue, context, null);
- issueChangeTrigger.onChange(
- new IssueChangeTrigger.IssueChangeData(
- searchResponseData.getIssues().stream().map(IssueDto::toDefaultIssue).collect(MoreCollectors.toList(searchResponseData.getIssues().size())),
- copyOf(searchResponseData.getComponents())),
- new IssueChangeTrigger.IssueChange(transitionKey),
- context);
+ QGChangeEventFactory.IssueChangeData issueChangeData = new QGChangeEventFactory.IssueChangeData(
+ searchResponseData.getIssues().stream().map(IssueDto::toDefaultIssue).collect(toList(searchResponseData.getIssues().size())),
+ copyOf(searchResponseData.getComponents()));
+ List<QGChangeEvent> qgChangeEvents = qgChangeEventFactory.from(issueChangeData, new QGChangeEventFactory.IssueChange(transitionKey), context);
+ qgChangeEventListeners.broadcastOnIssueChange(issueChangeData, qgChangeEvents);
return searchResponseData;
}
return new SearchResponseData(issueDto);
import org.sonar.server.issue.workflow.FunctionExecutor;
import org.sonar.server.issue.workflow.IssueWorkflow;
import org.sonar.server.qualitygate.LiveQualityGateFactoryImpl;
-import org.sonar.server.qualitygate.changeevent.IssueChangeTriggerImpl;
+import org.sonar.server.qualitygate.changeevent.QGChangeEventFactoryImpl;
import org.sonar.server.qualitygate.changeevent.QGChangeEventListenersImpl;
import org.sonar.server.settings.ProjectConfigurationLoaderImpl;
import org.sonar.server.webhook.WebhookQGChangeEventListener;
BulkChangeAction.class,
ProjectConfigurationLoaderImpl.class,
LiveQualityGateFactoryImpl.class,
- IssueChangeTriggerImpl.class,
+ QGChangeEventFactoryImpl.class,
WebhookQGChangeEventListener.class,
QGChangeEventListenersImpl.class);
}
import com.google.common.io.Resources;
import java.util.Date;
+import java.util.List;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
import org.sonar.server.issue.IssueFieldsSetter;
import org.sonar.server.issue.IssueFinder;
import org.sonar.server.issue.IssueUpdater;
-import org.sonar.server.qualitygate.changeevent.IssueChangeTrigger;
+import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
+import org.sonar.server.qualitygate.changeevent.QGChangeEventFactory;
+import org.sonar.server.qualitygate.changeevent.QGChangeEventListeners;
import org.sonar.server.user.UserSession;
import static com.google.common.collect.ImmutableList.copyOf;
private final IssueUpdater issueUpdater;
private final OperationResponseWriter responseWriter;
private final System2 system2;
- private final IssueChangeTrigger issueChangeTrigger;
+ private final QGChangeEventFactory qgChangeEventFactory;
+ private final QGChangeEventListeners qgChangeEventListeners;
public SetTypeAction(UserSession userSession, DbClient dbClient, IssueFinder issueFinder, IssueFieldsSetter issueFieldsSetter, IssueUpdater issueUpdater,
- OperationResponseWriter responseWriter, System2 system2, IssueChangeTrigger issueChangeTrigger) {
+ OperationResponseWriter responseWriter, System2 system2,
+ QGChangeEventFactory qgChangeEventFactory, QGChangeEventListeners qgChangeEventListeners) {
this.userSession = userSession;
this.dbClient = dbClient;
this.issueFinder = issueFinder;
this.issueUpdater = issueUpdater;
this.responseWriter = responseWriter;
this.system2 = system2;
- this.issueChangeTrigger = issueChangeTrigger;
+ this.qgChangeEventFactory = qgChangeEventFactory;
+ this.qgChangeEventListeners = qgChangeEventListeners;
}
@Override
IssueChangeContext context = IssueChangeContext.createUser(new Date(system2.now()), userSession.getLogin());
if (issueFieldsSetter.setType(issue, ruleType, context)) {
SearchResponseData searchResponseData = issueUpdater.saveIssueAndPreloadSearchResponseData(session, issue, context, null);
- issueChangeTrigger.onChange(
- new IssueChangeTrigger.IssueChangeData(
- searchResponseData.getIssues().stream().map(IssueDto::toDefaultIssue).collect(MoreCollectors.toList(searchResponseData.getIssues().size())),
- copyOf(searchResponseData.getComponents())),
- new IssueChangeTrigger.IssueChange(ruleType),
- context);
+ QGChangeEventFactory.IssueChangeData issueChangeData = new QGChangeEventFactory.IssueChangeData(
+ searchResponseData.getIssues().stream().map(IssueDto::toDefaultIssue).collect(MoreCollectors.toList(searchResponseData.getIssues().size())),
+ copyOf(searchResponseData.getComponents()));
+ List<QGChangeEvent> qgChangeEvents = qgChangeEventFactory.from(issueChangeData, new QGChangeEventFactory.IssueChange(ruleType), context);
+ qgChangeEventListeners.broadcastOnIssueChange(issueChangeData, qgChangeEvents);
return searchResponseData;
}
return new SearchResponseData(issueDto);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.qualitygate.changeevent;
-
-import com.google.common.collect.ImmutableList;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-import javax.annotation.Nullable;
-import org.sonar.api.rules.RuleType;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.IssueChangeContext;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.server.issue.ws.SearchResponseData;
-
-import static com.google.common.base.Preconditions.checkArgument;
-
-public interface IssueChangeTrigger {
- /**
- * Will call webhooks once for any short living branch which has at least one issue in {@link SearchResponseData} and
- * if change described in {@link IssueChange} can alter the status of the short living branch.
- */
- void onChange(IssueChangeData issueChangeData, IssueChange issueChange, IssueChangeContext context);
-
- final class IssueChange {
- private final RuleType ruleType;
- private final String transitionKey;
-
- public IssueChange(RuleType ruleType) {
- this(ruleType, null);
- }
-
- public IssueChange(String transitionKey) {
- this(null, transitionKey);
- }
-
- public IssueChange(@Nullable RuleType ruleType, @Nullable String transitionKey) {
- checkArgument(ruleType != null || transitionKey != null, "At least one of ruleType and transitionKey must be non null");
- this.ruleType = ruleType;
- this.transitionKey = transitionKey;
- }
-
- public Optional<RuleType> getRuleType() {
- return Optional.ofNullable(ruleType);
- }
-
- public Optional<String> getTransitionKey() {
- return Optional.ofNullable(transitionKey);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- IssueChange that = (IssueChange) o;
- return ruleType == that.ruleType &&
- Objects.equals(transitionKey, that.transitionKey);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(ruleType, transitionKey);
- }
-
- @Override
- public String toString() {
- return "IssueChange{" +
- "ruleType=" + ruleType +
- ", transitionKey='" + transitionKey + '\'' +
- '}';
- }
- }
-
- final class IssueChangeData {
- private final List<DefaultIssue> issues;
- private final List<ComponentDto> components;
-
- public IssueChangeData(List<DefaultIssue> issues, List<ComponentDto> components) {
- this.issues = ImmutableList.copyOf(issues);
- this.components = ImmutableList.copyOf(components);
- }
-
- public List<DefaultIssue> getIssues() {
- return issues;
- }
-
- public List<ComponentDto> getComponents() {
- return components;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- IssueChangeData that = (IssueChangeData) o;
- return Objects.equals(issues, that.issues) &&
- Objects.equals(components, that.components);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(issues, components);
- }
-
- @Override
- public String toString() {
- return "IssueChangeData{" +
- "issues=" + issues +
- ", components=" + components +
- '}';
- }
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.qualitygate.changeevent;
-
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.issue.DefaultTransitions;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.IssueChangeContext;
-import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.component.BranchDto;
-import org.sonar.db.component.BranchType;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.SnapshotDto;
-import org.sonar.server.qualitygate.LiveQualityGateFactory;
-import org.sonar.server.settings.ProjectConfigurationLoader;
-
-import static org.sonar.core.util.stream.MoreCollectors.toSet;
-import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
-
-public class IssueChangeTriggerImpl implements IssueChangeTrigger {
- private static final Set<String> MEANINGFUL_TRANSITIONS = ImmutableSet.of(
- DefaultTransitions.RESOLVE, DefaultTransitions.FALSE_POSITIVE, DefaultTransitions.WONT_FIX, DefaultTransitions.REOPEN);
- private final DbClient dbClient;
- private final ProjectConfigurationLoader projectConfigurationLoader;
- private final QGChangeEventListeners qgEventListeners;
- private final LiveQualityGateFactory liveQualityGateFactory;
-
- public IssueChangeTriggerImpl(DbClient dbClient, ProjectConfigurationLoader projectConfigurationLoader,
- QGChangeEventListeners qgEventListeners, LiveQualityGateFactory liveQualityGateFactory) {
- this.dbClient = dbClient;
- this.projectConfigurationLoader = projectConfigurationLoader;
- this.qgEventListeners = qgEventListeners;
- this.liveQualityGateFactory = liveQualityGateFactory;
- }
-
- @Override
- public void onChange(IssueChangeData issueChangeData, IssueChange issueChange, IssueChangeContext context) {
- if (isEmpty(issueChangeData) || !isUserChangeContext(context) || !isRelevant(issueChange) || qgEventListeners.isEmpty()) {
- return;
- }
-
- broadcastToListeners(issueChangeData);
- }
-
- private static boolean isRelevant(IssueChange issueChange) {
- return issueChange.getTransitionKey().map(IssueChangeTriggerImpl::isMeaningfulTransition).orElse(true);
- }
-
- private static boolean isEmpty(IssueChangeData issueChangeData) {
- return issueChangeData.getIssues().isEmpty();
- }
-
- private static boolean isUserChangeContext(IssueChangeContext context) {
- return context.login() != null;
- }
-
- private static boolean isMeaningfulTransition(String transitionKey) {
- return MEANINGFUL_TRANSITIONS.contains(transitionKey);
- }
-
- private void broadcastToListeners(IssueChangeData issueChangeData) {
- try (DbSession dbSession = dbClient.openSession(false)) {
- Map<String, ComponentDto> branchesByUuid = getBranchComponents(dbSession, issueChangeData);
- if (branchesByUuid.isEmpty()) {
- return;
- }
-
- Set<String> branchProjectUuids = branchesByUuid.values().stream()
- .map(ComponentDto::uuid)
- .collect(toSet(branchesByUuid.size()));
- Set<BranchDto> shortBranches = dbClient.branchDao().selectByUuids(dbSession, branchProjectUuids)
- .stream()
- .filter(branchDto -> branchDto.getBranchType() == BranchType.SHORT)
- .collect(toSet(branchesByUuid.size()));
- if (shortBranches.isEmpty()) {
- return;
- }
-
- Map<String, Configuration> configurationByUuid = projectConfigurationLoader.loadProjectConfigurations(dbSession,
- shortBranches.stream().map(shortBranch -> branchesByUuid.get(shortBranch.getUuid())).collect(Collectors.toSet()));
- Set<String> shortBranchesComponentUuids = shortBranches.stream().map(BranchDto::getUuid).collect(toSet(shortBranches.size()));
- Map<String, SnapshotDto> analysisByProjectUuid = dbClient.snapshotDao().selectLastAnalysesByRootComponentUuids(
- dbSession,
- shortBranchesComponentUuids)
- .stream()
- .collect(uniqueIndex(SnapshotDto::getComponentUuid));
-
- List<QGChangeEvent> qgChangeEvents = shortBranches
- .stream()
- .map(shortBranch -> {
- ComponentDto branch = branchesByUuid.get(shortBranch.getUuid());
- SnapshotDto analysis = analysisByProjectUuid.get(shortBranch.getUuid());
- if (branch != null && analysis != null) {
- Configuration configuration = configurationByUuid.get(shortBranch.getUuid());
-
- return new QGChangeEvent(branch, shortBranch, analysis, configuration,
- () -> Optional.of(liveQualityGateFactory.buildForShortLivedBranch(branch)));
- }
- return null;
- })
- .filter(Objects::nonNull)
- .collect(MoreCollectors.toList(shortBranches.size()));
- qgEventListeners.broadcast(Trigger.ISSUE_CHANGE, qgChangeEvents);
- }
- }
-
- private Map<String, ComponentDto> getBranchComponents(DbSession dbSession, IssueChangeData issueChangeData) {
- Set<String> projectUuids = issueChangeData.getIssues().stream()
- .map(DefaultIssue::projectUuid)
- .collect(toSet());
- Set<String> missingProjectUuids = ImmutableSet.copyOf(Sets.difference(
- projectUuids,
- issueChangeData.getComponents()
- .stream()
- .map(ComponentDto::uuid)
- .collect(Collectors.toSet())));
- if (missingProjectUuids.isEmpty()) {
- return issueChangeData.getComponents()
- .stream()
- .filter(c -> projectUuids.contains(c.uuid()))
- .filter(componentDto -> componentDto.getMainBranchProjectUuid() != null)
- .collect(uniqueIndex(ComponentDto::uuid));
- }
- return Stream.concat(
- issueChangeData.getComponents().stream().filter(c -> projectUuids.contains(c.uuid())),
- dbClient.componentDao().selectByUuids(dbSession, missingProjectUuids).stream())
- .filter(componentDto -> componentDto.getMainBranchProjectUuid() != null)
- .collect(uniqueIndex(ComponentDto::uuid));
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.qualitygate.changeevent;
+
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import javax.annotation.Nullable;
+import org.sonar.api.rules.RuleType;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.server.issue.ws.SearchResponseData;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+public interface QGChangeEventFactory {
+ /**
+ * Will call webhooks once for any short living branch which has at least one issue in {@link SearchResponseData} and
+ * if change described in {@link IssueChange} can alter the status of the short living branch.
+ */
+ List<QGChangeEvent> from(IssueChangeData issueChangeData, IssueChange issueChange, IssueChangeContext context);
+
+ final class IssueChange {
+ private final RuleType ruleType;
+ private final String transitionKey;
+
+ public IssueChange(RuleType ruleType) {
+ this(ruleType, null);
+ }
+
+ public IssueChange(String transitionKey) {
+ this(null, transitionKey);
+ }
+
+ public IssueChange(@Nullable RuleType ruleType, @Nullable String transitionKey) {
+ checkArgument(ruleType != null || transitionKey != null, "At least one of ruleType and transitionKey must be non null");
+ this.ruleType = ruleType;
+ this.transitionKey = transitionKey;
+ }
+
+ public Optional<RuleType> getRuleType() {
+ return Optional.ofNullable(ruleType);
+ }
+
+ public Optional<String> getTransitionKey() {
+ return Optional.ofNullable(transitionKey);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ IssueChange that = (IssueChange) o;
+ return ruleType == that.ruleType &&
+ Objects.equals(transitionKey, that.transitionKey);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(ruleType, transitionKey);
+ }
+
+ @Override
+ public String toString() {
+ return "IssueChange{" +
+ "ruleType=" + ruleType +
+ ", transitionKey='" + transitionKey + '\'' +
+ '}';
+ }
+ }
+
+ final class IssueChangeData {
+ private final List<DefaultIssue> issues;
+ private final List<ComponentDto> components;
+
+ public IssueChangeData(List<DefaultIssue> issues, List<ComponentDto> components) {
+ this.issues = ImmutableList.copyOf(issues);
+ this.components = ImmutableList.copyOf(components);
+ }
+
+ public List<DefaultIssue> getIssues() {
+ return issues;
+ }
+
+ public List<ComponentDto> getComponents() {
+ return components;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ IssueChangeData that = (IssueChangeData) o;
+ return Objects.equals(issues, that.issues) &&
+ Objects.equals(components, that.components);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(issues, components);
+ }
+
+ @Override
+ public String toString() {
+ return "IssueChangeData{" +
+ "issues=" + issues +
+ ", components=" + components +
+ '}';
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.qualitygate.changeevent;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.issue.DefaultTransitions;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.BranchDto;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.server.qualitygate.LiveQualityGateFactory;
+import org.sonar.server.settings.ProjectConfigurationLoader;
+
+import static java.util.Collections.emptyList;
+import static org.sonar.core.util.stream.MoreCollectors.toSet;
+import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
+
+public class QGChangeEventFactoryImpl implements QGChangeEventFactory {
+ private static final Set<String> MEANINGFUL_TRANSITIONS = ImmutableSet.of(
+ DefaultTransitions.RESOLVE, DefaultTransitions.FALSE_POSITIVE, DefaultTransitions.WONT_FIX, DefaultTransitions.REOPEN);
+ private final DbClient dbClient;
+ private final ProjectConfigurationLoader projectConfigurationLoader;
+ private final LiveQualityGateFactory liveQualityGateFactory;
+
+ public QGChangeEventFactoryImpl(DbClient dbClient, ProjectConfigurationLoader projectConfigurationLoader,
+ LiveQualityGateFactory liveQualityGateFactory) {
+ this.dbClient = dbClient;
+ this.projectConfigurationLoader = projectConfigurationLoader;
+ this.liveQualityGateFactory = liveQualityGateFactory;
+ }
+
+ @Override
+ public List<QGChangeEvent> from(IssueChangeData issueChangeData, IssueChange issueChange, IssueChangeContext context) {
+ if (isEmpty(issueChangeData) || !isUserChangeContext(context) || !isRelevant(issueChange)) {
+ return emptyList();
+ }
+
+ return from(issueChangeData);
+ }
+
+ private static boolean isRelevant(IssueChange issueChange) {
+ return issueChange.getTransitionKey().map(QGChangeEventFactoryImpl::isMeaningfulTransition).orElse(true);
+ }
+
+ private static boolean isEmpty(IssueChangeData issueChangeData) {
+ return issueChangeData.getIssues().isEmpty();
+ }
+
+ private static boolean isUserChangeContext(IssueChangeContext context) {
+ return context.login() != null;
+ }
+
+ private static boolean isMeaningfulTransition(String transitionKey) {
+ return MEANINGFUL_TRANSITIONS.contains(transitionKey);
+ }
+
+ private List<QGChangeEvent> from(IssueChangeData issueChangeData) {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ Map<String, ComponentDto> branchesByUuid = getBranchComponents(dbSession, issueChangeData);
+ if (branchesByUuid.isEmpty()) {
+ return emptyList();
+ }
+
+ Set<String> branchProjectUuids = branchesByUuid.values().stream()
+ .map(ComponentDto::uuid)
+ .collect(toSet(branchesByUuid.size()));
+ Set<BranchDto> shortBranches = dbClient.branchDao().selectByUuids(dbSession, branchProjectUuids)
+ .stream()
+ .filter(branchDto -> branchDto.getBranchType() == BranchType.SHORT)
+ .collect(toSet(branchesByUuid.size()));
+ if (shortBranches.isEmpty()) {
+ return emptyList();
+ }
+
+ Map<String, Configuration> configurationByUuid = projectConfigurationLoader.loadProjectConfigurations(dbSession,
+ shortBranches.stream().map(shortBranch -> branchesByUuid.get(shortBranch.getUuid())).collect(Collectors.toSet()));
+ Set<String> shortBranchesComponentUuids = shortBranches.stream().map(BranchDto::getUuid).collect(toSet(shortBranches.size()));
+ Map<String, SnapshotDto> analysisByProjectUuid = dbClient.snapshotDao().selectLastAnalysesByRootComponentUuids(
+ dbSession,
+ shortBranchesComponentUuids)
+ .stream()
+ .collect(uniqueIndex(SnapshotDto::getComponentUuid));
+
+ return shortBranches
+ .stream()
+ .map(shortBranch -> {
+ ComponentDto branch = branchesByUuid.get(shortBranch.getUuid());
+ SnapshotDto analysis = analysisByProjectUuid.get(shortBranch.getUuid());
+ if (branch != null && analysis != null) {
+ Configuration configuration = configurationByUuid.get(shortBranch.getUuid());
+
+ return new QGChangeEvent(branch, shortBranch, analysis, configuration,
+ () -> Optional.of(liveQualityGateFactory.buildForShortLivedBranch(branch)));
+ }
+ return null;
+ })
+ .filter(Objects::nonNull)
+ .collect(MoreCollectors.toList(shortBranches.size()));
+ }
+ }
+
+ private Map<String, ComponentDto> getBranchComponents(DbSession dbSession, IssueChangeData issueChangeData) {
+ Set<String> projectUuids = issueChangeData.getIssues().stream()
+ .map(DefaultIssue::projectUuid)
+ .collect(toSet());
+ Set<String> missingProjectUuids = ImmutableSet.copyOf(Sets.difference(
+ projectUuids,
+ issueChangeData.getComponents()
+ .stream()
+ .map(ComponentDto::uuid)
+ .collect(Collectors.toSet())));
+ if (missingProjectUuids.isEmpty()) {
+ return issueChangeData.getComponents()
+ .stream()
+ .filter(c -> projectUuids.contains(c.uuid()))
+ .filter(componentDto -> componentDto.getMainBranchProjectUuid() != null)
+ .collect(uniqueIndex(ComponentDto::uuid));
+ }
+ return Stream.concat(
+ issueChangeData.getComponents().stream().filter(c -> projectUuids.contains(c.uuid())),
+ dbClient.componentDao().selectByUuids(dbSession, missingProjectUuids).stream())
+ .filter(componentDto -> componentDto.getMainBranchProjectUuid() != null)
+ .collect(uniqueIndex(ComponentDto::uuid));
+ }
+}
public interface QGChangeEventListeners {
boolean isEmpty();
- void broadcast(Trigger trigger, Collection<QGChangeEvent> changeEvents);
+ void broadcastOnIssueChange(QGChangeEventFactory.IssueChangeData issueChangeData, Collection<QGChangeEvent> qgChangeEvents);
}
}
@Override
- public void broadcast(Trigger trigger, Collection<QGChangeEvent> changeEvents) {
- if (changeEvents.isEmpty()) {
+ public void broadcastOnIssueChange(QGChangeEventFactory.IssueChangeData issueChangeData, Collection<QGChangeEvent> changeEvents) {
+ if (listeners.length == 0 || issueChangeData.getComponents().isEmpty() || issueChangeData.getIssues().isEmpty() || changeEvents.isEmpty()) {
return;
}
try {
List<QGChangeEvent> immutableChangeEvents = ImmutableList.copyOf(changeEvents);
- Arrays.stream(listeners).forEach(listener -> broadcastTo(trigger, immutableChangeEvents, listener));
+ Arrays.stream(listeners)
+ .forEach(listener -> broadcastTo(Trigger.ISSUE_CHANGE, immutableChangeEvents, listener));
} catch (Error e) {
LOG.warn(format("Broadcasting to listeners failed for %s events", changeEvents.size()), e);
}
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import org.sonar.server.notification.NotificationManager;
import org.sonar.server.organization.DefaultOrganizationProvider;
import org.sonar.server.organization.TestDefaultOrganizationProvider;
-import org.sonar.server.qualitygate.changeevent.IssueChangeTrigger;
+import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
+import org.sonar.server.qualitygate.changeevent.QGChangeEventFactory;
+import org.sonar.server.qualitygate.changeevent.QGChangeEventListeners;
import org.sonar.server.rule.DefaultRuleFinder;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
import static java.util.Objects.requireNonNull;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.same;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
private IssueStorage issueStorage = new ServerIssueStorage(system2, new DefaultRuleFinder(dbClient, defaultOrganizationProvider), dbClient,
new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient)));
private NotificationManager notificationManager = mock(NotificationManager.class);
- private IssueChangeTrigger issueChangeTrigger = mock(IssueChangeTrigger.class);
+ private QGChangeEventFactory qgChangeEventFactory = mock(QGChangeEventFactory.class);
+ private QGChangeEventListeners qgChangeEventListeners = mock(QGChangeEventListeners.class);
private List<Action> actions = new ArrayList<>();
private RuleDto rule;
private ComponentDto file;
private UserDto user;
- private WsActionTester tester = new WsActionTester(new BulkChangeAction(system2, userSession, dbClient, issueStorage, notificationManager, actions, issueChangeTrigger));
+ private WsActionTester tester = new WsActionTester(
+ new BulkChangeAction(system2, userSession, dbClient, issueStorage, notificationManager, actions, qgChangeEventFactory, qgChangeEventListeners));
@Before
- public void setUp() throws Exception {
+ public void setUp() {
issueWorkflow.start();
rule = db.rules().insertRule(newRuleDto());
organization = db.organizations().insert();
}
@Test
- public void set_type() throws Exception {
+ public void set_type() {
setUserProjectPermissions(USER, ISSUE_ADMIN);
IssueDto issueDto = db.issues().insertIssue(newUnresolvedIssue().setType(BUG));
+ List<QGChangeEvent> qgChangeEvents = mockQGChangeEvents(CODE_SMELL, null);
BulkChangeWsResponse response = call(builder()
.setIssues(singletonList(issueDto.getKey()))
assertThat(reloaded.getType()).isEqualTo(RuleType.CODE_SMELL.getDbConstant());
assertThat(reloaded.getUpdatedAt()).isEqualTo(NOW);
- verifyIssueChangeWebhookCalled(CODE_SMELL, null, new String[] {file.uuid()}, issueDto);
+ QGChangeEventFactory.IssueChangeData issueChangeData = verifyIssueChangeData(CODE_SMELL, null, new String[] {file.uuid()}, issueDto);
+ verifyBroadcastOnIssueChange(issueChangeData, qgChangeEvents);
}
@Test
- public void set_severity() throws Exception {
+ public void set_severity() {
setUserProjectPermissions(USER, ISSUE_ADMIN);
IssueDto issueDto = db.issues().insertIssue(newUnresolvedIssue().setSeverity(MAJOR));
assertThat(reloaded.getSeverity()).isEqualTo(MINOR);
assertThat(reloaded.getUpdatedAt()).isEqualTo(NOW);
- verifyZeroInteractions(issueChangeTrigger);
+ verifyZeroInteractions(qgChangeEventFactory);
}
@Test
- public void add_tags() throws Exception {
+ public void add_tags() {
setUserProjectPermissions(USER, ISSUE_ADMIN);
IssueDto issueDto = db.issues().insertIssue(newUnresolvedIssue().setTags(asList("tag1", "tag2")));
assertThat(reloaded.getTags()).containsOnly("tag1", "tag2", "tag3");
assertThat(reloaded.getUpdatedAt()).isEqualTo(NOW);
- verifyZeroInteractions(issueChangeTrigger);
+ verifyZeroInteractions(qgChangeEventFactory);
}
@Test
- public void remove_assignee() throws Exception {
+ public void remove_assignee() {
setUserProjectPermissions(USER);
IssueDto issueDto = db.issues().insertIssue(newUnresolvedIssue().setAssignee("arthur"));
assertThat(reloaded.getAssignee()).isNull();
assertThat(reloaded.getUpdatedAt()).isEqualTo(NOW);
- verifyZeroInteractions(issueChangeTrigger);
+ verifyZeroInteractions(qgChangeEventFactory);
}
@Test
- public void bulk_change_with_comment() throws Exception {
+ public void bulk_change_with_comment() {
setUserProjectPermissions(USER);
IssueDto issueDto = db.issues().insertIssue(newUnresolvedIssue().setType(BUG));
+ List<QGChangeEvent> qgChangeEvents = mockQGChangeEvents(null, "confirm");
BulkChangeWsResponse response = call(builder()
.setIssues(singletonList(issueDto.getKey()))
assertThat(issueComment.getUserLogin()).isEqualTo("john");
assertThat(issueComment.getChangeData()).isEqualTo("type was badly defined");
- verifyIssueChangeWebhookCalled(null, "confirm", new String[] {file.uuid()}, issueDto);
+ QGChangeEventFactory.IssueChangeData issueChangeData = verifyIssueChangeData(null, "confirm", new String[] {file.uuid()}, issueDto);
+ verifyBroadcastOnIssueChange(issueChangeData, qgChangeEvents);
}
@Test
- public void bulk_change_many_issues() throws Exception {
+ public void bulk_change_many_issues() {
setUserProjectPermissions(USER, ISSUE_ADMIN);
UserDto userToAssign = db.users().insertUser("arthur");
db.organizations().addMember(organization, user);
IssueDto issue1 = db.issues().insertIssue(newUnresolvedIssue().setAssignee(user.getLogin())).setType(BUG).setSeverity(MINOR);
IssueDto issue2 = db.issues().insertIssue(newUnresolvedIssue().setAssignee(userToAssign.getLogin())).setType(BUG).setSeverity(MAJOR);
IssueDto issue3 = db.issues().insertIssue(newUnresolvedIssue().setAssignee(null)).setType(VULNERABILITY).setSeverity(MAJOR);
+ List<QGChangeEvent> qgChangeEvents = mockQGChangeEvents(VULNERABILITY, null);
BulkChangeWsResponse response = call(builder()
.setIssues(asList(issue1.getKey(), issue2.getKey(), issue3.getKey()))
tuple(issue2.getKey(), userToAssign.getLogin(), VULNERABILITY.getDbConstant(), MINOR, NOW),
tuple(issue3.getKey(), userToAssign.getLogin(), VULNERABILITY.getDbConstant(), MINOR, NOW));
- verifyIssueChangeWebhookCalled(VULNERABILITY, null, new String[] {file.uuid()}, issue1, issue2, issue3);
+ QGChangeEventFactory.IssueChangeData issueChangeData = verifyIssueChangeData(VULNERABILITY, null, new String[] {file.uuid()}, issue1, issue2, issue3);
+ verifyBroadcastOnIssueChange(issueChangeData, qgChangeEvents);
}
@Test
- public void send_notification() throws Exception {
+ public void send_notification() {
setUserProjectPermissions(USER);
IssueDto issueDto = db.issues().insertIssue(newUnresolvedIssue().setType(BUG));
+ List<QGChangeEvent> qgChangeEvents = mockQGChangeEvents(null, "confirm");
BulkChangeWsResponse response = call(builder()
.setIssues(singletonList(issueDto.getKey()))
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("changeAuthor")).isEqualTo(user.getLogin());
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("branch")).isNull();
- verifyIssueChangeWebhookCalled(null, "confirm", new String[] {file.uuid()}, issueDto);
+ QGChangeEventFactory.IssueChangeData issueChangeData = verifyIssueChangeData(null, "confirm", new String[] {file.uuid()}, issueDto);
+ verifyBroadcastOnIssueChange(issueChangeData, qgChangeEvents);
}
@Test
- public void send_notification_on_branch() throws Exception {
+ public void send_notification_on_branch() {
setUserProjectPermissions(USER);
String branchName = "feature1";
ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey(branchName));
ComponentDto fileOnBranch = db.components().insertComponent(newFileDto(branch));
IssueDto issueDto = db.issues().insertIssue(newUnresolvedIssue(rule, fileOnBranch, branch).setType(BUG));
+ List<QGChangeEvent> qgChangeEvents = mockQGChangeEvents(null, "confirm");
BulkChangeWsResponse response = call(builder()
.setIssues(singletonList(issueDto.getKey()))
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("changeAuthor")).isEqualTo(user.getLogin());
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("branch")).isEqualTo(branchName);
- verifyIssueChangeWebhookCalled(null, "confirm", new String[] {fileOnBranch.uuid()}, issueDto);
+ QGChangeEventFactory.IssueChangeData issueChangeData = verifyIssueChangeData(null, "confirm", new String[] {fileOnBranch.uuid()}, issueDto);
+ verifyBroadcastOnIssueChange(issueChangeData, qgChangeEvents);
}
@Test
- public void send_notification_only_on_changed_issues() throws Exception {
+ public void send_notification_only_on_changed_issues() {
setUserProjectPermissions(USER, ISSUE_ADMIN);
IssueDto issue1 = db.issues().insertIssue(newUnresolvedIssue().setType(BUG));
IssueDto issue2 = db.issues().insertIssue(newUnresolvedIssue().setType(BUG));
IssueDto issue3 = db.issues().insertIssue(newUnresolvedIssue().setType(VULNERABILITY));
ArgumentCaptor<IssueChangeNotification> issueChangeNotificationCaptor = ArgumentCaptor.forClass(IssueChangeNotification.class);
+ List<QGChangeEvent> qgChangeEvents = mockQGChangeEvents(BUG, null);
BulkChangeWsResponse response = call(builder()
.setIssues(asList(issue1.getKey(), issue2.getKey(), issue3.getKey()))
assertThat(issueChangeNotificationCaptor.getAllValues()).hasSize(1);
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("key")).isEqualTo(issue3.getKey());
- verifyIssueChangeWebhookCalled(BUG, null, new String[] {file.uuid()}, issue3);
+ QGChangeEventFactory.IssueChangeData issueChangeData = verifyIssueChangeData(BUG, null, new String[] {file.uuid()}, issue3);
+ verifyBroadcastOnIssueChange(issueChangeData, qgChangeEvents);
}
@Test
- public void ignore_issues_when_condition_does_not_match() throws Exception {
+ public void ignore_issues_when_condition_does_not_match() {
setUserProjectPermissions(USER, ISSUE_ADMIN);
IssueDto issue1 = db.issues().insertIssue(newUnresolvedIssue().setType(BUG));
// These 2 issues will be ignored as they are resolved, changing type is not possible
IssueDto issue2 = db.issues().insertIssue(newResolvedIssue().setType(BUG));
IssueDto issue3 = db.issues().insertIssue(newResolvedIssue().setType(BUG));
+ List<QGChangeEvent> qgChangeEvents = mockQGChangeEvents(VULNERABILITY, null);
BulkChangeWsResponse response = call(builder()
.setIssues(asList(issue1.getKey(), issue2.getKey(), issue3.getKey()))
tuple(issue3.getKey(), BUG.getDbConstant(), issue2.getUpdatedAt()),
tuple(issue2.getKey(), BUG.getDbConstant(), issue3.getUpdatedAt()));
- verifyIssueChangeWebhookCalled(VULNERABILITY, null, new String[] {file.uuid()}, issue1);
+ QGChangeEventFactory.IssueChangeData issueChangeData = verifyIssueChangeData(VULNERABILITY, null, new String[] {file.uuid()}, issue1);
+ verifyBroadcastOnIssueChange(issueChangeData, qgChangeEvents);
}
@Test
- public void ignore_issues_when_there_is_nothing_to_do() throws Exception {
+ public void ignore_issues_when_there_is_nothing_to_do() {
setUserProjectPermissions(USER, ISSUE_ADMIN);
IssueDto issue1 = db.issues().insertIssue(newUnresolvedIssue().setType(BUG).setSeverity(MINOR));
// These 2 issues will be ignored as there's nothing to do
IssueDto issue2 = db.issues().insertIssue(newUnresolvedIssue().setType(VULNERABILITY));
IssueDto issue3 = db.issues().insertIssue(newUnresolvedIssue().setType(VULNERABILITY));
+ List<QGChangeEvent> qgChangeEvents = mockQGChangeEvents(VULNERABILITY, null);
BulkChangeWsResponse response = call(builder()
.setIssues(asList(issue1.getKey(), issue2.getKey(), issue3.getKey()))
tuple(issue2.getKey(), VULNERABILITY.getDbConstant(), issue2.getUpdatedAt()),
tuple(issue3.getKey(), VULNERABILITY.getDbConstant(), issue3.getUpdatedAt()));
- verifyIssueChangeWebhookCalled(VULNERABILITY, null, new String[] {file.uuid()}, issue1);
+ QGChangeEventFactory.IssueChangeData issueChangeData = verifyIssueChangeData(VULNERABILITY, null, new String[] {file.uuid()}, issue1);
+ verifyBroadcastOnIssueChange(issueChangeData, qgChangeEvents);
}
@Test
- public void add_comment_only_on_changed_issues() throws Exception {
+ public void add_comment_only_on_changed_issues() {
setUserProjectPermissions(USER, ISSUE_ADMIN);
IssueDto issue1 = db.issues().insertIssue(newUnresolvedIssue().setType(BUG).setSeverity(MINOR));
// These 2 issues will be ignored as there's nothing to do
IssueDto issue2 = db.issues().insertIssue(newUnresolvedIssue().setType(VULNERABILITY));
IssueDto issue3 = db.issues().insertIssue(newUnresolvedIssue().setType(VULNERABILITY));
+ List<QGChangeEvent> qgChangeEvents = mockQGChangeEvents(VULNERABILITY, null);
BulkChangeWsResponse response = call(builder()
.setIssues(asList(issue1.getKey(), issue2.getKey(), issue3.getKey()))
assertThat(dbClient.issueChangeDao().selectByTypeAndIssueKeys(db.getSession(), singletonList(issue2.getKey()), TYPE_COMMENT)).isEmpty();
assertThat(dbClient.issueChangeDao().selectByTypeAndIssueKeys(db.getSession(), singletonList(issue3.getKey()), TYPE_COMMENT)).isEmpty();
- verifyIssueChangeWebhookCalled(VULNERABILITY, null, new String[] {file.uuid()}, issue1);
+ QGChangeEventFactory.IssueChangeData issueChangeData = verifyIssueChangeData(VULNERABILITY, null, new String[] {file.uuid()}, issue1);
+ verifyBroadcastOnIssueChange(issueChangeData, qgChangeEvents);
}
@Test
- public void issues_on_which_user_has_not_browse_permission_are_ignored() throws Exception {
+ public void issues_on_which_user_has_not_browse_permission_are_ignored() {
setUserProjectPermissions(USER, ISSUE_ADMIN);
ComponentDto anotherProject = db.components().insertPrivateProject();
ComponentDto anotherFile = db.components().insertComponent(newFileDto(anotherProject));
// User has not browse permission on these 2 issues
IssueDto notAuthorizedIssue1 = db.issues().insertIssue(newUnresolvedIssue(rule, anotherFile, anotherProject).setType(BUG));
IssueDto notAuthorizedIssue2 = db.issues().insertIssue(newUnresolvedIssue(rule, anotherFile, anotherProject).setType(BUG));
+ List<QGChangeEvent> qgChangeEvents = mockQGChangeEvents(VULNERABILITY, null);
BulkChangeWsResponse response = call(builder()
.setIssues(asList(authorizedIssue.getKey(), notAuthorizedIssue1.getKey(), notAuthorizedIssue2.getKey()))
tuple(notAuthorizedIssue1.getKey(), BUG.getDbConstant(), notAuthorizedIssue1.getUpdatedAt()),
tuple(notAuthorizedIssue2.getKey(), BUG.getDbConstant(), notAuthorizedIssue2.getUpdatedAt()));
- verifyIssueChangeWebhookCalled(VULNERABILITY, null, new String[] {file.uuid()}, authorizedIssue);
+ QGChangeEventFactory.IssueChangeData issueChangeData = verifyIssueChangeData(VULNERABILITY, null, new String[] {file.uuid()}, authorizedIssue);
+ verifyBroadcastOnIssueChange(issueChangeData, qgChangeEvents);
}
@Test
- public void does_not_update_type_when_no_issue_admin_permission() throws Exception {
+ public void does_not_update_type_when_no_issue_admin_permission() {
setUserProjectPermissions(USER, ISSUE_ADMIN);
ComponentDto anotherProject = db.components().insertPrivateProject();
ComponentDto anotherFile = db.components().insertComponent(newFileDto(anotherProject));
}
@Test
- public void does_not_update_severity_when_no_issue_admin_permission() throws Exception {
+ public void does_not_update_severity_when_no_issue_admin_permission() {
setUserProjectPermissions(USER, ISSUE_ADMIN);
ComponentDto anotherProject = db.components().insertPrivateProject();
ComponentDto anotherFile = db.components().insertComponent(newFileDto(anotherProject));
}
@Test
- public void fail_when_only_comment_action() throws Exception {
+ public void fail_when_only_comment_action() {
setUserProjectPermissions(USER);
IssueDto issueDto = db.issues().insertIssue(newUnresolvedIssue().setType(BUG));
expectedException.expectMessage("At least one action must be provided");
}
@Test
- public void fail_when_number_of_issues_is_more_than_500() throws Exception {
+ public void fail_when_number_of_issues_is_more_than_500() {
userSession.logIn("john");
expectedException.expectMessage("Number of issues is limited to 500");
expectedException.expect(IllegalArgumentException.class);
}
@Test
- public void fail_when_not_authenticated() throws Exception {
+ public void fail_when_not_authenticated() {
expectedException.expect(UnauthorizedException.class);
call(builder().setIssues(singletonList("ABCD")).build());
}
@Test
- public void test_definition() throws Exception {
+ public void test_definition() {
WebService.Action action = tester.getDef();
assertThat(action.key()).isEqualTo("bulk_change");
assertThat(action.isPost()).isTrue();
private void verifyIssueChangeWebhookCalled(@Nullable RuleType expectedRuleType, @Nullable String transitionKey,
String[] componentUUids,
IssueDto... issueDtos) {
- ArgumentCaptor<IssueChangeTrigger.IssueChangeData> issueChangeDataCaptor = ArgumentCaptor.forClass(IssueChangeTrigger.IssueChangeData.class);
- verify(issueChangeTrigger).onChange(
- issueChangeDataCaptor.capture(),
- eq(new IssueChangeTrigger.IssueChange(expectedRuleType, transitionKey)),
- eq(IssueChangeContext.createUser(new Date(NOW), userSession.getLogin())));
- IssueChangeTrigger.IssueChangeData issueChangeData = issueChangeDataCaptor.getValue();
+ QGChangeEventFactory.IssueChange issueChange = new QGChangeEventFactory.IssueChange(expectedRuleType, transitionKey);
+ IssueChangeContext user = IssueChangeContext.createUser(new Date(NOW), userSession.getLogin());
+ List<QGChangeEvent> changeEvents = Collections.singletonList(mock(QGChangeEvent.class));
+ when(qgChangeEventFactory.from(any(QGChangeEventFactory.IssueChangeData.class), eq(issueChange), eq(user))).thenReturn(changeEvents);
+
+ ArgumentCaptor<QGChangeEventFactory.IssueChangeData> issueChangeDataCaptor = ArgumentCaptor.forClass(QGChangeEventFactory.IssueChangeData.class);
+ verify(qgChangeEventFactory).from(issueChangeDataCaptor.capture(), eq(issueChange), eq(user));
+
+ QGChangeEventFactory.IssueChangeData issueChangeData = issueChangeDataCaptor.getValue();
+ assertThat(issueChangeData.getIssues())
+ .extracting(DefaultIssue::key)
+ .containsOnly(Arrays.stream(issueDtos).map(IssueDto::getKey).toArray(String[]::new));
+ assertThat(issueChangeData.getComponents())
+ .extracting(ComponentDto::uuid)
+ .containsOnly(componentUUids);
+
+ verify(qgChangeEventListeners).broadcastOnIssueChange(same(issueChangeData), same(changeEvents));
+ }
+
+ private List<QGChangeEvent> mockQGChangeEvents(@Nullable RuleType expectedRuleType, @Nullable String transitionKey) {
+ QGChangeEventFactory.IssueChange issueChange = new QGChangeEventFactory.IssueChange(expectedRuleType, transitionKey);
+ IssueChangeContext user = IssueChangeContext.createUser(new Date(NOW), userSession.getLogin());
+ List<QGChangeEvent> changeEvents = Collections.singletonList(mock(QGChangeEvent.class));
+ when(qgChangeEventFactory.from(any(QGChangeEventFactory.IssueChangeData.class), eq(issueChange), eq(user))).thenReturn(changeEvents);
+
+ return changeEvents;
+ }
+
+ private QGChangeEventFactory.IssueChangeData verifyIssueChangeData(@Nullable RuleType expectedRuleType, @Nullable String transitionKey,
+ String[] componentUUids,
+ IssueDto... issueDtos) {
+ QGChangeEventFactory.IssueChange issueChange = new QGChangeEventFactory.IssueChange(expectedRuleType, transitionKey);
+ IssueChangeContext user = IssueChangeContext.createUser(new Date(NOW), userSession.getLogin());
+
+ ArgumentCaptor<QGChangeEventFactory.IssueChangeData> issueChangeDataCaptor = ArgumentCaptor.forClass(QGChangeEventFactory.IssueChangeData.class);
+ verify(qgChangeEventFactory).from(issueChangeDataCaptor.capture(), eq(issueChange), eq(user));
+
+ QGChangeEventFactory.IssueChangeData issueChangeData = issueChangeDataCaptor.getValue();
assertThat(issueChangeData.getIssues())
.extracting(DefaultIssue::key)
.containsOnly(Arrays.stream(issueDtos).map(IssueDto::getKey).toArray(String[]::new));
assertThat(issueChangeData.getComponents())
.extracting(ComponentDto::uuid)
.containsOnly(componentUUids);
+ return issueChangeData;
+ }
+
+ private void verifyBroadcastOnIssueChange(QGChangeEventFactory.IssueChangeData issueChangeData, List<QGChangeEvent> changeEvents) {
+ verify(qgChangeEventListeners).broadcastOnIssueChange(same(issueChangeData), same(changeEvents));
}
private BulkChangeWsResponse call(BulkChangeRequest bulkChangeRequest) {
*/
package org.sonar.server.issue.ws;
+import java.util.Collections;
import java.util.Date;
+import java.util.List;
import javax.annotation.Nullable;
import org.junit.Before;
import org.junit.Rule;
import org.sonar.server.notification.NotificationManager;
import org.sonar.server.organization.DefaultOrganizationProvider;
import org.sonar.server.organization.TestDefaultOrganizationProvider;
-import org.sonar.server.qualitygate.changeevent.IssueChangeTrigger;
+import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
+import org.sonar.server.qualitygate.changeevent.QGChangeEventFactory;
+import org.sonar.server.qualitygate.changeevent.QGChangeEventListeners;
import org.sonar.server.rule.DefaultRuleFinder;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
public class DoTransitionActionTest {
+ private static final long NOW = 999_776_888L;
private System2 system2 = mock(System2.class);
@Rule
private ComponentDto project;
private ComponentDto file;
private ArgumentCaptor<SearchResponseData> preloadedSearchResponseDataCaptor = ArgumentCaptor.forClass(SearchResponseData.class);
- private IssueChangeTrigger issueChangeTrigger = mock(IssueChangeTrigger.class);
+ private QGChangeEventFactory qgChangeEventFactory = mock(QGChangeEventFactory.class);
+ private QGChangeEventListeners qgChangeEventListeners = mock(QGChangeEventListeners.class);
private WsAction underTest = new DoTransitionAction(dbClient, userSession, new IssueFinder(dbClient, userSession), issueUpdater, transitionService, responseWriter, system2,
- issueChangeTrigger);
+ qgChangeEventFactory, qgChangeEventListeners);
private WsActionTester tester = new WsActionTester(underTest);
@Before
}
@Test
- public void do_transition() throws Exception {
- long now = 999_776_888L;
- when(system2.now()).thenReturn(now);
+ public void do_transition() {
+ when(system2.now()).thenReturn(NOW);
IssueDto issueDto = issueDbTester.insertIssue(newIssue().setStatus(STATUS_OPEN).setResolution(null));
userSession.logIn("john").addProjectPermission(USER, project, file);
+ IssueChangeContext changeContext = IssueChangeContext.createUser(new Date(NOW), userSession.getLogin());
+ QGChangeEventFactory.IssueChange issueChange = new QGChangeEventFactory.IssueChange(null, "confirm");
+ List<QGChangeEvent> qgChangeEvents = Collections.singletonList(mock(QGChangeEvent.class));
+ when(qgChangeEventFactory.from(any(QGChangeEventFactory.IssueChangeData.class), eq(issueChange), eq(changeContext)))
+ .thenReturn(qgChangeEvents);
call(issueDto.getKey(), "confirm");
IssueDto issueReloaded = dbClient.issueDao().selectByKey(dbTester.getSession(), issueDto.getKey()).get();
assertThat(issueReloaded.getStatus()).isEqualTo(STATUS_CONFIRMED);
- ArgumentCaptor<IssueChangeTrigger.IssueChangeData> issueChangeDataCaptor = ArgumentCaptor.forClass(IssueChangeTrigger.IssueChangeData.class);
- verify(issueChangeTrigger).onChange(
- issueChangeDataCaptor.capture(),
- eq(new IssueChangeTrigger.IssueChange(null, "confirm")),
- eq(IssueChangeContext.createUser(new Date(now), userSession.getLogin())));
- IssueChangeTrigger.IssueChangeData issueChangeData = issueChangeDataCaptor.getValue();
+ ArgumentCaptor<QGChangeEventFactory.IssueChangeData> issueChangeDataCaptor = ArgumentCaptor.forClass(QGChangeEventFactory.IssueChangeData.class);
+ verify(qgChangeEventFactory).from(issueChangeDataCaptor.capture(), eq(issueChange), eq(changeContext));
+ QGChangeEventFactory.IssueChangeData issueChangeData = issueChangeDataCaptor.getValue();
assertThat(issueChangeData.getIssues())
.extracting(DefaultIssue::key)
.containsOnly(issueDto.getKey());
assertThat(issueChangeData.getComponents())
.extracting(ComponentDto::uuid)
.containsOnly(issueDto.getComponentUuid(), issueDto.getProjectUuid());
+ verify(qgChangeEventListeners).broadcastOnIssueChange(issueChangeData, qgChangeEvents);
}
@Test
- public void fail_if_issue_does_not_exist() throws Exception {
+ public void fail_if_issue_does_not_exist() {
userSession.logIn("john");
expectedException.expect(NotFoundException.class);
}
@Test
- public void fail_if_no_issue_param() throws Exception {
+ public void fail_if_no_issue_param() {
userSession.logIn("john");
expectedException.expect(IllegalArgumentException.class);
}
@Test
- public void fail_if_no_transition_param() throws Exception {
+ public void fail_if_no_transition_param() {
IssueDto issueDto = issueDbTester.insertIssue(newIssue().setStatus(STATUS_OPEN).setResolution(null));
userSession.logIn("john").addProjectPermission(USER, project, file);
}
@Test
- public void fail_if_not_enough_permission_to_access_issue() throws Exception {
+ public void fail_if_not_enough_permission_to_access_issue() {
IssueDto issueDto = issueDbTester.insertIssue(newIssue().setStatus(STATUS_OPEN).setResolution(null));
userSession.logIn("john").addProjectPermission(CODEVIEWER, project, file);
}
@Test
- public void fail_if_not_enough_permission_to_apply_transition() throws Exception {
+ public void fail_if_not_enough_permission_to_apply_transition() {
IssueDto issueDto = issueDbTester.insertIssue(newIssue().setStatus(STATUS_OPEN).setResolution(null));
userSession.logIn("john").addProjectPermission(USER, project, file);
}
@Test
- public void fail_if_not_authenticated() throws Exception {
+ public void fail_if_not_authenticated() {
expectedException.expect(UnauthorizedException.class);
call("ISSUE_KEY", "confirm");
}
*/
package org.sonar.server.issue.ws;
+import java.util.Collections;
import java.util.Date;
import java.util.List;
import javax.annotation.Nullable;
import org.sonar.server.notification.NotificationManager;
import org.sonar.server.organization.DefaultOrganizationProvider;
import org.sonar.server.organization.TestDefaultOrganizationProvider;
-import org.sonar.server.qualitygate.changeevent.IssueChangeTrigger;
+import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
+import org.sonar.server.qualitygate.changeevent.QGChangeEventFactory;
+import org.sonar.server.qualitygate.changeevent.QGChangeEventListeners;
import org.sonar.server.rule.DefaultRuleFinder;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
private ArgumentCaptor<SearchResponseData> preloadedSearchResponseDataCaptor = ArgumentCaptor.forClass(SearchResponseData.class);
private IssueIndexer issueIndexer = new IssueIndexer(esTester.client(), dbClient, new IssueIteratorFactory(dbClient));
- private IssueChangeTrigger issueChangeTrigger = mock(IssueChangeTrigger.class);
+ private QGChangeEventFactory qgChangeEventFactory = mock(QGChangeEventFactory.class);
+ private QGChangeEventListeners qgChangeEventListeners = mock(QGChangeEventListeners.class);
private WsActionTester tester = new WsActionTester(new SetTypeAction(userSession, dbClient, new IssueFinder(dbClient, userSession), new IssueFieldsSetter(),
new IssueUpdater(dbClient,
new ServerIssueStorage(system2, new DefaultRuleFinder(dbClient, defaultOrganizationProvider), dbClient, issueIndexer), mock(NotificationManager.class)),
- responseWriter, system2, issueChangeTrigger));
+ responseWriter, system2, qgChangeEventFactory, qgChangeEventListeners));
@Test
- public void set_type() throws Exception {
+ public void set_type() {
long now = 1_999_777_234L;
when(system2.now()).thenReturn(now);
IssueDto issueDto = issueDbTester.insertIssue(newIssue().setType(CODE_SMELL));
setUserWithBrowseAndAdministerIssuePermission(issueDto);
+ QGChangeEventFactory.IssueChange issueChange = new QGChangeEventFactory.IssueChange(BUG, null);
+ IssueChangeContext changeContext = IssueChangeContext.createUser(new Date(now), userSession.getLogin());
+ List<QGChangeEvent> qgChangeEvents = Collections.singletonList(mock(QGChangeEvent.class));
+ when(qgChangeEventFactory.from(any(QGChangeEventFactory.IssueChangeData.class), eq(issueChange), eq(changeContext)))
+ .thenReturn(qgChangeEvents);
call(issueDto.getKey(), BUG.name());
IssueDto issueReloaded = dbClient.issueDao().selectByKey(dbTester.getSession(), issueDto.getKey()).get();
assertThat(issueReloaded.getType()).isEqualTo(BUG.getDbConstant());
- ArgumentCaptor<IssueChangeTrigger.IssueChangeData> issueChangeDataCaptor = ArgumentCaptor.forClass(IssueChangeTrigger.IssueChangeData.class);
- verify(issueChangeTrigger).onChange(
- issueChangeDataCaptor.capture(),
- eq(new IssueChangeTrigger.IssueChange(BUG, null)),
- eq(IssueChangeContext.createUser(new Date(now), userSession.getLogin())));
- IssueChangeTrigger.IssueChangeData issueChangeData = issueChangeDataCaptor.getValue();
+ ArgumentCaptor<QGChangeEventFactory.IssueChangeData> issueChangeDataCaptor = ArgumentCaptor.forClass(QGChangeEventFactory.IssueChangeData.class);
+ verify(qgChangeEventFactory).from(issueChangeDataCaptor.capture(), eq(issueChange), eq(changeContext));
+ QGChangeEventFactory.IssueChangeData issueChangeData = issueChangeDataCaptor.getValue();
assertThat(issueChangeData.getIssues())
.extracting(DefaultIssue::key)
.containsOnly(issueDto.getKey());
assertThat(issueChangeData.getComponents())
.extracting(ComponentDto::uuid)
.containsOnly(issueDto.getComponentUuid(), issueDto.getProjectUuid());
+ verify(qgChangeEventListeners).broadcastOnIssueChange(issueChangeData, qgChangeEvents);
}
@Test
- public void insert_entry_in_changelog_when_setting_type() throws Exception {
+ public void insert_entry_in_changelog_when_setting_type() {
IssueDto issueDto = issueDbTester.insertIssue(newIssue().setType(CODE_SMELL));
setUserWithBrowseAndAdministerIssuePermission(issueDto);
}
@Test
- public void fail_when_not_authenticated() throws Exception {
+ public void fail_when_not_authenticated() {
expectedException.expect(UnauthorizedException.class);
call("ABCD", BUG.name());
}
@Test
- public void fail_when_missing_browse_permission() throws Exception {
+ public void fail_when_missing_browse_permission() {
IssueDto issueDto = issueDbTester.insertIssue();
String login = "john";
String permission = ISSUE_ADMIN;
}
@Test
- public void fail_when_missing_administer_issue_permission() throws Exception {
+ public void fail_when_missing_administer_issue_permission() {
IssueDto issueDto = issueDbTester.insertIssue();
logInAndAddProjectPermission("john", issueDto, USER);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.qualitygate.changeevent;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import java.util.stream.Stream;
-import javax.annotation.Nullable;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Matchers;
-import org.mockito.Mockito;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.issue.DefaultTransitions;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.rules.RuleType;
-import org.sonar.api.utils.System2;
-import org.sonar.core.issue.IssueChangeContext;
-import org.sonar.core.util.UuidFactoryFast;
-import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.AnalysisPropertyDto;
-import org.sonar.db.component.BranchDao;
-import org.sonar.db.component.BranchDto;
-import org.sonar.db.component.BranchType;
-import org.sonar.db.component.ComponentDao;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.component.SnapshotDao;
-import org.sonar.db.component.SnapshotDto;
-import org.sonar.db.issue.IssueDto;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.server.qualitygate.LiveQualityGateFactory;
-import org.sonar.server.qualitygate.changeevent.IssueChangeTrigger.IssueChange;
-import org.sonar.server.settings.ProjectConfigurationLoader;
-import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.webhook.WebhookPayloadFactory;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singleton;
-import static java.util.Collections.singletonList;
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.tuple;
-import static org.mockito.Mockito.mock;
-import static org.sonar.core.util.stream.MoreCollectors.toArrayList;
-import static org.sonar.db.component.ComponentTesting.newBranchDto;
-
-@RunWith(DataProviderRunner.class)
-public class IssueChangeTriggerImplTest {
- @Rule
- public DbTester dbTester = DbTester.create(System2.INSTANCE);
- @Rule
- public UserSessionRule userSessionRule = UserSessionRule.standalone();
-
- private DbClient dbClient = dbTester.getDbClient();
-
- private Random random = new Random();
- private RuleType randomRuleType = RuleType.values()[random.nextInt(RuleType.values().length)];
-
- private IssueChangeContext scanChangeContext = IssueChangeContext.createScan(new Date());
- private IssueChangeContext userChangeContext = IssueChangeContext.createUser(new Date(), "userLogin");
- private WebhookPayloadFactory webhookPayloadFactory = mock(WebhookPayloadFactory.class);
- private DbClient spiedOnDbClient = Mockito.spy(dbClient);
- private ProjectConfigurationLoader projectConfigurationLoader = mock(ProjectConfigurationLoader.class);
- private QGChangeEventListeners qgChangeEventListeners = mock(QGChangeEventListeners.class);
- private LiveQualityGateFactory liveQualityGateFactory = mock(LiveQualityGateFactory.class);
- private IssueChangeTriggerImpl underTest = new IssueChangeTriggerImpl(spiedOnDbClient, projectConfigurationLoader, qgChangeEventListeners, liveQualityGateFactory);
- private DbClient mockedDbClient = mock(DbClient.class);
- private IssueChangeTriggerImpl mockedUnderTest = new IssueChangeTriggerImpl(mockedDbClient, projectConfigurationLoader, qgChangeEventListeners, liveQualityGateFactory);
-
- @Test
- public void on_type_change_has_no_effect_if_SearchResponseData_has_no_issue() {
- mockedUnderTest.onChange(issueChangeData(), new IssueChange(randomRuleType), userChangeContext);
-
- Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
- }
-
- @Test
- public void on_type_change_has_no_effect_if_scan_IssueChangeContext() {
- mockedUnderTest.onChange(issueChangeData(), new IssueChange(randomRuleType), scanChangeContext);
-
- Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
- }
-
- @Test
- public void on_transition_change_has_no_effect_if_SearchResponseData_has_no_issue() {
- mockedUnderTest.onChange(issueChangeData(), new IssueChange(randomAlphanumeric(12)), userChangeContext);
-
- Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
- }
-
- @Test
- public void onTransition_has_no_effect_if_transition_key_is_empty() {
- on_transition_changeHasNoEffectForTransitionKey("");
- }
-
- @Test
- public void onTransition_has_no_effect_if_transition_key_is_random() {
- on_transition_changeHasNoEffectForTransitionKey(randomAlphanumeric(99));
- }
-
- @Test
- public void on_transition_change_has_no_effect_if_transition_key_is_ignored_default_transition_key() {
- Set<String> supportedDefaultTransitionKeys = ImmutableSet.of(
- DefaultTransitions.RESOLVE, DefaultTransitions.FALSE_POSITIVE, DefaultTransitions.WONT_FIX, DefaultTransitions.REOPEN);
- DefaultTransitions.ALL.stream()
- .filter(s -> !supportedDefaultTransitionKeys.contains(s))
- .forEach(this::on_transition_changeHasNoEffectForTransitionKey);
- }
-
- private void on_transition_changeHasNoEffectForTransitionKey(@Nullable String transitionKey) {
- Mockito.reset(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
-
- mockedUnderTest.onChange(issueChangeData(newIssueDto()), new IssueChange(transitionKey), userChangeContext);
-
- Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
- }
-
- @Test
- public void on_transition_change_has_no_effect_if_scan_IssueChangeContext() {
- mockedUnderTest.onChange(issueChangeData(newIssueDto()), new IssueChange(randomAlphanumeric(12)), scanChangeContext);
-
- Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
- }
-
- @Test
- public void on_type_and_transition_change_has_no_effect_if_SearchResponseData_has_no_issue() {
- mockedUnderTest.onChange(issueChangeData(), new IssueChange(randomRuleType, randomAlphanumeric(3)), userChangeContext);
-
- Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
- }
-
- @Test
- public void on_type_and_transition_change_has_no_effect_if_scan_IssueChangeContext() {
- mockedUnderTest.onChange(issueChangeData(), new IssueChange(randomRuleType, randomAlphanumeric(3)), scanChangeContext);
-
- Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
- }
-
- @Test
- public void on_type_and_transition_has_no_effect_if_transition_key_is_empty() {
- on_type_and_transition_changeHasNoEffectForTransitionKey("");
- }
-
- @Test
- public void on_type_and_transition_has_no_effect_if_transition_key_is_random() {
- on_type_and_transition_changeHasNoEffectForTransitionKey(randomAlphanumeric(66));
- }
-
- @Test
- public void on_type_and_transition_has_no_effect_if_transition_key_is_ignored_default_transition_key() {
- Set<String> supportedDefaultTransitionKeys = ImmutableSet.of(
- DefaultTransitions.RESOLVE, DefaultTransitions.FALSE_POSITIVE, DefaultTransitions.WONT_FIX, DefaultTransitions.REOPEN);
- DefaultTransitions.ALL.stream()
- .filter(s -> !supportedDefaultTransitionKeys.contains(s))
- .forEach(this::on_type_and_transition_changeHasNoEffectForTransitionKey);
- }
-
- private void on_type_and_transition_changeHasNoEffectForTransitionKey(@Nullable String transitionKey) {
- Mockito.reset(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
-
- mockedUnderTest.onChange(issueChangeData(newIssueDto()), new IssueChange(randomRuleType, transitionKey), userChangeContext);
-
- Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
- }
-
- @Test
- @UseDataProvider("validIssueChanges")
- public void broadcast_to_QGEventListeners_for_short_living_branch_of_issue(IssueChange issueChange) {
- OrganizationDto organization = dbTester.organizations().insert();
- ComponentDto project = dbTester.components().insertPublicProject(organization);
- ComponentAndBranch branch = insertProjectBranch(project, BranchType.SHORT, "foo");
- SnapshotDto analysis = insertAnalysisTask(branch);
- Configuration configuration = mockLoadProjectConfiguration(branch);
-
- Map<String, String> properties = new HashMap<>();
- properties.put("sonar.analysis.test1", randomAlphanumeric(50));
- properties.put("sonar.analysis.test2", randomAlphanumeric(5000));
- insertPropertiesFor(analysis.getUuid(), properties);
-
- underTest.onChange(issueChangeData(newIssueDto(branch)), issueChange, userChangeContext);
-
- Collection<QGChangeEvent> events = verifyListenersBroadcastedTo();
- assertThat(events).hasSize(1);
- QGChangeEvent event = events.iterator().next();
- assertThat(event.getProject()).isEqualTo(branch.component);
- assertThat(event.getBranch()).isEqualTo(branch.branch);
- assertThat(event.getAnalysis()).isEqualTo(analysis);
- assertThat(event.getProjectConfiguration()).isSameAs(configuration);
- }
-
- @Test
- public void do_not_load_project_configuration_nor_analysis_nor_call_webhook_if_there_are_no_short_branch() {
- OrganizationDto organization = dbTester.organizations().insert();
- ComponentDto project = dbTester.components().insertPublicProject(organization);
- ComponentAndBranch longBranch1 = insertProjectBranch(project, BranchType.LONG, "foo");
- ComponentAndBranch longBranch2 = insertProjectBranch(project, BranchType.LONG, "bar");
- ImmutableList<IssueDto> issueDtos = ImmutableList.of(newIssueDto(project), newIssueDto(longBranch1), newIssueDto(longBranch2));
-
- SnapshotDao snapshotDaoSpy = Mockito.spy(dbClient.snapshotDao());
- Mockito.when(spiedOnDbClient.snapshotDao()).thenReturn(snapshotDaoSpy);
- underTest.onChange(issueChangeData(issueDtos), new IssueChange(randomRuleType), userChangeContext);
-
- Mockito.verifyZeroInteractions(projectConfigurationLoader);
- Mockito.verify(snapshotDaoSpy, Mockito.times(0)).selectLastAnalysesByRootComponentUuids(Matchers.any(DbSession.class), Matchers.anyCollectionOf(String.class));
- }
-
- @Test
- public void creates_single_QGChangeEvent_per_short_branch_with_at_least_one_issue_in_SearchResponseData() {
- OrganizationDto organization = dbTester.organizations().insert();
- ComponentAndBranch branch1 = insertPrivateBranch(organization, BranchType.SHORT);
- ComponentAndBranch branch2 = insertPrivateBranch(organization, BranchType.SHORT);
- ComponentAndBranch branch3 = insertPrivateBranch(organization, BranchType.SHORT);
- SnapshotDto analysis1 = insertAnalysisTask(branch1);
- SnapshotDto analysis2 = insertAnalysisTask(branch2);
- SnapshotDto analysis3 = insertAnalysisTask(branch3);
- int issuesBranch1 = 2 + random.nextInt(10);
- int issuesBranch2 = 2 + random.nextInt(10);
- int issuesBranch3 = 2 + random.nextInt(10);
- List<IssueDto> issueDtos = Stream.of(
- IntStream.range(0, issuesBranch1).mapToObj(i -> newIssueDto(branch1.component)),
- IntStream.range(0, issuesBranch2).mapToObj(i -> newIssueDto(branch2.component)),
- IntStream.range(0, issuesBranch3).mapToObj(i -> newIssueDto(branch3.component)))
- .flatMap(s -> s)
- .collect(MoreCollectors.toList());
- Configuration configuration1 = mock(Configuration.class);
- Configuration configuration2 = mock(Configuration.class);
- Configuration configuration3 = mock(Configuration.class);
- mockLoadProjectConfigurations(
- branch1.component, configuration1,
- branch2.component, configuration2,
- branch3.component, configuration3);
-
- ComponentDao componentDaoSpy = Mockito.spy(dbClient.componentDao());
- BranchDao branchDaoSpy = Mockito.spy(dbClient.branchDao());
- SnapshotDao snapshotDaoSpy = Mockito.spy(dbClient.snapshotDao());
- Mockito.when(spiedOnDbClient.componentDao()).thenReturn(componentDaoSpy);
- Mockito.when(spiedOnDbClient.branchDao()).thenReturn(branchDaoSpy);
- Mockito.when(spiedOnDbClient.snapshotDao()).thenReturn(snapshotDaoSpy);
- underTest.onChange(issueChangeData(issueDtos), new IssueChange(randomRuleType), userChangeContext);
-
- Collection<QGChangeEvent> qgChangeEvents = verifyListenersBroadcastedTo();
- assertThat(qgChangeEvents)
- .hasSize(3)
- .extracting(QGChangeEvent::getBranch, QGChangeEvent::getProjectConfiguration, QGChangeEvent::getAnalysis)
- .containsOnly(
- tuple(branch1.branch, configuration1, analysis1),
- tuple(branch2.branch, configuration2, analysis2),
- tuple(branch3.branch, configuration3, analysis3));
-
- // verifyWebhookCalled(branch1, configuration1, analysis1);
- // verifyWebhookCalled(branch2, configuration2, analysis2);
- // verifyWebhookCalled(branch3, configuration3, analysis3);
- // extractPayloadFactoryArguments(3);
-
- Set<String> uuids = ImmutableSet.of(branch1.uuid(), branch2.uuid(), branch3.uuid());
- Mockito.verify(componentDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
- Mockito.verify(branchDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
- Mockito.verify(snapshotDaoSpy).selectLastAnalysesByRootComponentUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
- Mockito.verifyNoMoreInteractions(componentDaoSpy, branchDaoSpy, snapshotDaoSpy);
- }
-
- @Test
- public void create_QGChangeEvent_only_for_short_branches() {
- OrganizationDto organization = dbTester.organizations().insert();
- ComponentAndBranch shortBranch = insertPrivateBranch(organization, BranchType.SHORT);
- ComponentAndBranch longBranch = insertPrivateBranch(organization, BranchType.LONG);
- SnapshotDto analysis1 = insertAnalysisTask(shortBranch);
- SnapshotDto analysis2 = insertAnalysisTask(longBranch);
- Configuration configuration = mockLoadProjectConfiguration(shortBranch);
-
- ComponentDao componentDaoSpy = Mockito.spy(dbClient.componentDao());
- BranchDao branchDaoSpy = Mockito.spy(dbClient.branchDao());
- SnapshotDao snapshotDaoSpy = Mockito.spy(dbClient.snapshotDao());
- Mockito.when(spiedOnDbClient.componentDao()).thenReturn(componentDaoSpy);
- Mockito.when(spiedOnDbClient.branchDao()).thenReturn(branchDaoSpy);
- Mockito.when(spiedOnDbClient.snapshotDao()).thenReturn(snapshotDaoSpy);
- underTest.onChange(
- issueChangeData(asList(newIssueDto(shortBranch), newIssueDto(longBranch))),
- new IssueChange(randomRuleType),
- userChangeContext);
-
- Collection<QGChangeEvent> qgChangeEvents = verifyListenersBroadcastedTo();
- assertThat(qgChangeEvents)
- .hasSize(1)
- .extracting(QGChangeEvent::getBranch, QGChangeEvent::getProjectConfiguration, QGChangeEvent::getAnalysis)
- .containsOnly(tuple(shortBranch.branch, configuration, analysis1));
-
- Set<String> uuids = ImmutableSet.of(shortBranch.uuid(), longBranch.uuid());
- Mockito.verify(componentDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
- Mockito.verify(branchDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
- Mockito.verify(snapshotDaoSpy).selectLastAnalysesByRootComponentUuids(Matchers.any(DbSession.class), Matchers.eq(ImmutableSet.of(shortBranch.uuid())));
- Mockito.verifyNoMoreInteractions(componentDaoSpy, branchDaoSpy, snapshotDaoSpy);
- }
-
- @Test
- public void do_not_load_componentDto_from_DB_if_all_are_in_inputData() {
- OrganizationDto organization = dbTester.organizations().insert();
- ComponentAndBranch branch1 = insertPrivateBranch(organization, BranchType.SHORT);
- ComponentAndBranch branch2 = insertPrivateBranch(organization, BranchType.SHORT);
- ComponentAndBranch branch3 = insertPrivateBranch(organization, BranchType.SHORT);
- SnapshotDto analysis1 = insertAnalysisTask(branch1);
- SnapshotDto analysis2 = insertAnalysisTask(branch2);
- SnapshotDto analysis3 = insertAnalysisTask(branch3);
- List<IssueDto> issueDtos = asList(newIssueDto(branch1), newIssueDto(branch2), newIssueDto(branch3));
- Configuration configuration1 = mock(Configuration.class);
- Configuration configuration2 = mock(Configuration.class);
- Configuration configuration3 = mock(Configuration.class);
- mockLoadProjectConfigurations(
- branch1.component, configuration1,
- branch2.component, configuration2,
- branch3.component, configuration3);
-
- ComponentDao componentDaoSpy = Mockito.spy(dbClient.componentDao());
- Mockito.when(spiedOnDbClient.componentDao()).thenReturn(componentDaoSpy);
- underTest.onChange(
- issueChangeData(issueDtos, branch1, branch2, branch3),
- new IssueChange(randomRuleType),
- userChangeContext);
-
- Collection<QGChangeEvent> qgChangeEvents = verifyListenersBroadcastedTo();
- assertThat(qgChangeEvents)
- .hasSize(3)
- .extracting(QGChangeEvent::getBranch, QGChangeEvent::getProjectConfiguration, QGChangeEvent::getAnalysis)
- .containsOnly(
- tuple(branch1.branch, configuration1, analysis1),
- tuple(branch2.branch, configuration2, analysis2),
- tuple(branch3.branch, configuration3, analysis3));
-
- Mockito.verify(componentDaoSpy, Mockito.times(0)).selectByUuids(Matchers.any(DbSession.class), Matchers.anyCollectionOf(String.class));
- Mockito.verifyNoMoreInteractions(componentDaoSpy);
- }
-
- @Test
- public void call_db_only_for_componentDto_not_in_inputData() {
- OrganizationDto organization = dbTester.organizations().insert();
- ComponentAndBranch branch1 = insertPrivateBranch(organization, BranchType.SHORT);
- ComponentAndBranch branch2 = insertPrivateBranch(organization, BranchType.SHORT);
- ComponentAndBranch branch3 = insertPrivateBranch(organization, BranchType.SHORT);
- SnapshotDto analysis1 = insertAnalysisTask(branch1);
- SnapshotDto analysis2 = insertAnalysisTask(branch2);
- SnapshotDto analysis3 = insertAnalysisTask(branch3);
- List<IssueDto> issueDtos = asList(newIssueDto(branch1), newIssueDto(branch2), newIssueDto(branch3));
- Configuration configuration1 = mock(Configuration.class);
- Configuration configuration2 = mock(Configuration.class);
- Configuration configuration3 = mock(Configuration.class);
- mockLoadProjectConfigurations(
- branch1.component, configuration1,
- branch2.component, configuration2,
- branch3.component, configuration3);
-
- ComponentDao componentDaoSpy = Mockito.spy(dbClient.componentDao());
- BranchDao branchDaoSpy = Mockito.spy(dbClient.branchDao());
- SnapshotDao snapshotDaoSpy = Mockito.spy(dbClient.snapshotDao());
- Mockito.when(spiedOnDbClient.componentDao()).thenReturn(componentDaoSpy);
- Mockito.when(spiedOnDbClient.branchDao()).thenReturn(branchDaoSpy);
- Mockito.when(spiedOnDbClient.snapshotDao()).thenReturn(snapshotDaoSpy);
- underTest.onChange(
- issueChangeData(issueDtos, branch1, branch3),
- new IssueChange(randomRuleType),
- userChangeContext);
-
- assertThat(verifyListenersBroadcastedTo()).hasSize(3);
-
- Set<String> uuids = ImmutableSet.of(branch1.uuid(), branch2.uuid(), branch3.uuid());
- Mockito.verify(componentDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(ImmutableSet.of(branch2.uuid())));
- Mockito.verify(branchDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
- Mockito.verify(snapshotDaoSpy).selectLastAnalysesByRootComponentUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
- Mockito.verifyNoMoreInteractions(componentDaoSpy, branchDaoSpy, snapshotDaoSpy);
- }
-
- @Test
- public void supports_issues_on_files_and_filter_on_short_branches_asap_when_calling_db() {
- OrganizationDto organization = dbTester.organizations().insert();
- ComponentDto project = dbTester.components().insertPrivateProject(organization);
- ComponentDto file = dbTester.components().insertComponent(ComponentTesting.newFileDto(project));
- ComponentAndBranch shortBranch = insertProjectBranch(project, BranchType.SHORT, "foo");
- ComponentAndBranch longBranch = insertProjectBranch(project, BranchType.LONG, "bar");
- ComponentDto shortBranchFile = dbTester.components().insertComponent(ComponentTesting.newFileDto(shortBranch.component));
- ComponentDto longBranchFile = dbTester.components().insertComponent(ComponentTesting.newFileDto(longBranch.component));
- SnapshotDto analysis1 = insertAnalysisTask(project);
- SnapshotDto analysis2 = insertAnalysisTask(shortBranch);
- SnapshotDto analysis3 = insertAnalysisTask(longBranch);
- List<IssueDto> issueDtos = asList(
- newIssueDto(file, project),
- newIssueDto(shortBranchFile, shortBranch),
- newIssueDto(longBranchFile, longBranch));
- Configuration configuration = mockLoadProjectConfiguration(shortBranch);
-
- ComponentDao componentDaoSpy = Mockito.spy(dbClient.componentDao());
- BranchDao branchDaoSpy = Mockito.spy(dbClient.branchDao());
- SnapshotDao snapshotDaoSpy = Mockito.spy(dbClient.snapshotDao());
- Mockito.when(spiedOnDbClient.componentDao()).thenReturn(componentDaoSpy);
- Mockito.when(spiedOnDbClient.branchDao()).thenReturn(branchDaoSpy);
- Mockito.when(spiedOnDbClient.snapshotDao()).thenReturn(snapshotDaoSpy);
- underTest.onChange(issueChangeData(issueDtos), new IssueChange(randomRuleType), userChangeContext);
-
- Collection<QGChangeEvent> qgChangeEvents = verifyListenersBroadcastedTo();
- assertThat(qgChangeEvents)
- .hasSize(1)
- .extracting(QGChangeEvent::getBranch, QGChangeEvent::getProjectConfiguration, QGChangeEvent::getAnalysis)
- .containsOnly(tuple(shortBranch.branch, configuration, analysis2));
-
- Set<String> uuids = ImmutableSet.of(project.uuid(), shortBranch.uuid(), longBranch.uuid());
- Mockito.verify(componentDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
- Mockito.verify(branchDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(ImmutableSet.of(shortBranch.uuid(), longBranch.uuid())));
- Mockito.verify(snapshotDaoSpy).selectLastAnalysesByRootComponentUuids(Matchers.any(DbSession.class), Matchers.eq(ImmutableSet.of(shortBranch.uuid())));
- Mockito.verifyNoMoreInteractions(componentDaoSpy, branchDaoSpy, snapshotDaoSpy);
- }
-
- private Configuration mockLoadProjectConfiguration(ComponentAndBranch componentAndBranch) {
- Configuration configuration = mock(Configuration.class);
- Mockito.when(projectConfigurationLoader.loadProjectConfigurations(Matchers.any(DbSession.class), Matchers.eq(singleton(componentAndBranch.component))))
- .thenReturn(ImmutableMap.of(componentAndBranch.uuid(), configuration));
- return configuration;
- }
-
- private void mockLoadProjectConfigurations(Object... branchesAndConfiguration) {
- checkArgument(branchesAndConfiguration.length % 2 == 0);
- Set<ComponentDto> components = new HashSet<>();
- Map<String, Configuration> result = new HashMap<>();
- for (int i = 0; i < branchesAndConfiguration.length; i++) {
- ComponentDto component = (ComponentDto) branchesAndConfiguration[i++];
- Configuration configuration = (Configuration) branchesAndConfiguration[i];
- components.add(component);
- result.put(component.uuid(), configuration);
- }
- Mockito.when(projectConfigurationLoader.loadProjectConfigurations(Matchers.any(DbSession.class), Matchers.eq(components)))
- .thenReturn(result);
- }
-
- private ComponentAndBranch insertPrivateBranch(OrganizationDto organization, BranchType branchType) {
- ComponentDto project = dbTester.components().insertPrivateProject(organization);
- BranchDto branchDto = newBranchDto(project.projectUuid(), branchType)
- .setKey("foo");
- ComponentDto newComponent = dbTester.components().insertProjectBranch(project, branchDto);
- return new ComponentAndBranch(newComponent, branchDto);
- }
-
- public ComponentAndBranch insertProjectBranch(ComponentDto project, BranchType type, String branchKey) {
- BranchDto branchDto = newBranchDto(project.projectUuid(), type).setKey(branchKey);
- ComponentDto newComponent = dbTester.components().insertProjectBranch(project, branchDto);
- return new ComponentAndBranch(newComponent, branchDto);
- }
-
- private static class ComponentAndBranch {
- private final ComponentDto component;
-
- private final BranchDto branch;
-
- private ComponentAndBranch(ComponentDto component, BranchDto branch) {
- this.component = component;
- this.branch = branch;
- }
-
- public ComponentDto getComponent() {
- return component;
- }
-
- public BranchDto getBranch() {
- return branch;
- }
-
- public String uuid() {
- return component.uuid();
- }
-
- }
-
- private void insertPropertiesFor(String snapshotUuid, Map<String, String> properties) {
- List<AnalysisPropertyDto> analysisProperties = properties.entrySet().stream()
- .map(entry -> new AnalysisPropertyDto()
- .setUuid(UuidFactoryFast.getInstance().create())
- .setSnapshotUuid(snapshotUuid)
- .setKey(entry.getKey())
- .setValue(entry.getValue()))
- .collect(toArrayList(properties.size()));
- dbTester.getDbClient().analysisPropertiesDao().insert(dbTester.getSession(), analysisProperties);
- dbTester.getSession().commit();
- }
-
- private SnapshotDto insertAnalysisTask(ComponentAndBranch componentAndBranch) {
- return insertAnalysisTask(componentAndBranch.component);
- }
-
- private SnapshotDto insertAnalysisTask(ComponentDto component) {
- return dbTester.components().insertSnapshot(component);
- }
-
- private Collection<QGChangeEvent> verifyListenersBroadcastedTo() {
- Class<Collection<QGChangeEvent>> clazz = (Class<Collection<QGChangeEvent>>) (Class) Collection.class;
- ArgumentCaptor<Collection<QGChangeEvent>> supplierCaptor = ArgumentCaptor.forClass(clazz);
- Mockito.verify(qgChangeEventListeners).broadcast(
- Matchers.same(Trigger.ISSUE_CHANGE),
- supplierCaptor.capture());
- return supplierCaptor.getValue();
- }
-
- @DataProvider
- public static Object[][] validIssueChanges() {
- return new Object[][] {
- {new IssueChange(RuleType.BUG)},
- {new IssueChange(RuleType.VULNERABILITY)},
- {new IssueChange(RuleType.CODE_SMELL)},
- {new IssueChange(DefaultTransitions.RESOLVE)},
- {new IssueChange(RuleType.BUG, DefaultTransitions.RESOLVE)},
- {new IssueChange(RuleType.VULNERABILITY, DefaultTransitions.RESOLVE)},
- {new IssueChange(RuleType.CODE_SMELL, DefaultTransitions.RESOLVE)},
- {new IssueChange(DefaultTransitions.FALSE_POSITIVE)},
- {new IssueChange(RuleType.BUG, DefaultTransitions.FALSE_POSITIVE)},
- {new IssueChange(RuleType.VULNERABILITY, DefaultTransitions.FALSE_POSITIVE)},
- {new IssueChange(RuleType.CODE_SMELL, DefaultTransitions.FALSE_POSITIVE)},
- {new IssueChange(DefaultTransitions.WONT_FIX)},
- {new IssueChange(RuleType.BUG, DefaultTransitions.WONT_FIX)},
- {new IssueChange(RuleType.VULNERABILITY, DefaultTransitions.WONT_FIX)},
- {new IssueChange(RuleType.CODE_SMELL, DefaultTransitions.WONT_FIX)},
- {new IssueChange(DefaultTransitions.REOPEN)},
- {new IssueChange(RuleType.BUG, DefaultTransitions.REOPEN)},
- {new IssueChange(RuleType.VULNERABILITY, DefaultTransitions.REOPEN)},
- {new IssueChange(RuleType.CODE_SMELL, DefaultTransitions.REOPEN)}
- };
- }
-
- private IssueChangeTrigger.IssueChangeData issueChangeData() {
- return new IssueChangeTrigger.IssueChangeData(emptyList(), emptyList());
- }
-
- private IssueChangeTrigger.IssueChangeData issueChangeData(IssueDto issueDto) {
- return new IssueChangeTrigger.IssueChangeData(singletonList(issueDto.toDefaultIssue()), emptyList());
- }
-
- private IssueChangeTrigger.IssueChangeData issueChangeData(Collection<IssueDto> issueDtos, ComponentAndBranch... components) {
- return new IssueChangeTrigger.IssueChangeData(
- issueDtos.stream().map(IssueDto::toDefaultIssue).collect(Collectors.toList()),
- Arrays.stream(components).map(ComponentAndBranch::getComponent).collect(Collectors.toList()));
- }
-
- private IssueDto newIssueDto(@Nullable ComponentAndBranch projectAndBranch) {
- return projectAndBranch == null ? newIssueDto() : newIssueDto(projectAndBranch.component, projectAndBranch.component);
- }
-
- private IssueDto newIssueDto(ComponentDto componentDto) {
- return newIssueDto(componentDto, componentDto);
- }
-
- private IssueDto newIssueDto() {
- return newIssueDto((ComponentDto) null, (ComponentDto) null);
- }
-
- private IssueDto newIssueDto(@Nullable ComponentDto component, @Nullable ComponentAndBranch componentAndBranch) {
- return newIssueDto(component, componentAndBranch == null ? null : componentAndBranch.component);
- }
-
- private IssueDto newIssueDto(@Nullable ComponentDto component, @Nullable ComponentDto project) {
- RuleType randomRuleType = RuleType.values()[random.nextInt(RuleType.values().length)];
- String randomStatus = Issue.STATUSES.get(random.nextInt(Issue.STATUSES.size()));
- IssueDto res = new IssueDto()
- .setType(randomRuleType)
- .setStatus(randomStatus)
- .setRuleKey(randomAlphanumeric(3), randomAlphanumeric(4));
- if (component != null) {
- res.setComponent(component);
- }
- if (project != null) {
- res.setProject(project);
- }
- return res;
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.qualitygate.changeevent;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.api.rules.RuleType;
-
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class IssueChangeTriggerTest {
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
- @Test
- public void IssueChange_constructor_throws_IAE_if_both_args_are_null() {
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("At least one of ruleType and transitionKey must be non null");
-
- new IssueChangeTrigger.IssueChange(null, null);
- }
-
- @Test
- public void verify_IssueChange_getters() {
- IssueChangeTrigger.IssueChange transitionKeyOnly = new IssueChangeTriggerImpl.IssueChange("foo");
- assertThat(transitionKeyOnly.getTransitionKey()).contains("foo");
- assertThat(transitionKeyOnly.getRuleType()).isEmpty();
- IssueChangeTrigger.IssueChange ruleTypeOnly = new IssueChangeTriggerImpl.IssueChange(RuleType.BUG);
- assertThat(ruleTypeOnly.getTransitionKey()).isEmpty();
- assertThat(ruleTypeOnly.getRuleType()).contains(RuleType.BUG);
- IssueChangeTrigger.IssueChange transitionKeyAndRuleType = new IssueChangeTriggerImpl.IssueChange(RuleType.VULNERABILITY, "bar");
- assertThat(transitionKeyAndRuleType.getTransitionKey()).contains("bar");
- assertThat(transitionKeyAndRuleType.getRuleType()).contains(RuleType.VULNERABILITY);
- }
-
- @Test
- public void verify_IssueChange_equality() {
- IssueChangeTrigger.IssueChange underTest = new IssueChangeTrigger.IssueChange(RuleType.BUG);
-
- assertThat(underTest).isEqualTo(new IssueChangeTrigger.IssueChange(RuleType.BUG));
- assertThat(underTest).isEqualTo(new IssueChangeTrigger.IssueChange(RuleType.BUG, null));
-
- assertThat(underTest).isNotEqualTo(null);
- assertThat(underTest).isNotEqualTo(new IssueChangeTrigger.IssueChange(RuleType.BUG, randomAlphanumeric(10)));
- assertThat(underTest).isNotEqualTo(new IssueChangeTrigger.IssueChange(RuleType.CODE_SMELL));
- assertThat(underTest).isNotEqualTo(new IssueChangeTrigger.IssueChange(RuleType.CODE_SMELL, randomAlphanumeric(10)));
- assertThat(underTest).isNotEqualTo(new IssueChangeTrigger.IssueChange(RuleType.VULNERABILITY));
- assertThat(underTest).isNotEqualTo(new IssueChangeTrigger.IssueChange(RuleType.VULNERABILITY, randomAlphanumeric(10)));
- assertThat(underTest).isNotEqualTo(new IssueChangeTrigger.IssueChange(randomAlphanumeric(10)));
-
- String transitionKey = randomAlphanumeric(10);
- underTest = new IssueChangeTrigger.IssueChange(transitionKey);
-
- assertThat(underTest).isEqualTo(new IssueChangeTrigger.IssueChange(transitionKey));
- assertThat(underTest).isEqualTo(new IssueChangeTrigger.IssueChange(null, transitionKey));
-
- assertThat(underTest).isNotEqualTo(null);
- assertThat(underTest).isNotEqualTo(new IssueChangeTrigger.IssueChange(RuleType.BUG));
- assertThat(underTest).isNotEqualTo(new IssueChangeTrigger.IssueChange(RuleType.BUG, transitionKey));
- assertThat(underTest).isNotEqualTo(new IssueChangeTrigger.IssueChange(RuleType.BUG, randomAlphanumeric(10)));
- assertThat(underTest).isNotEqualTo(new IssueChangeTrigger.IssueChange(RuleType.CODE_SMELL));
- assertThat(underTest).isNotEqualTo(new IssueChangeTrigger.IssueChange(RuleType.CODE_SMELL, randomAlphanumeric(10)));
- assertThat(underTest).isNotEqualTo(new IssueChangeTrigger.IssueChange(RuleType.CODE_SMELL, transitionKey));
- assertThat(underTest).isNotEqualTo(new IssueChangeTrigger.IssueChange(RuleType.VULNERABILITY));
- assertThat(underTest).isNotEqualTo(new IssueChangeTrigger.IssueChange(RuleType.VULNERABILITY, randomAlphanumeric(10)));
- assertThat(underTest).isNotEqualTo(new IssueChangeTrigger.IssueChange(RuleType.VULNERABILITY, transitionKey));
- assertThat(underTest).isNotEqualTo(new IssueChangeTrigger.IssueChange(randomAlphanumeric(9)));
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.qualitygate.changeevent;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.issue.DefaultTransitions;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.rules.RuleType;
+import org.sonar.api.utils.System2;
+import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.core.util.UuidFactoryFast;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.AnalysisPropertyDto;
+import org.sonar.db.component.BranchDao;
+import org.sonar.db.component.BranchDto;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.ComponentDao;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.component.SnapshotDao;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.server.qualitygate.LiveQualityGateFactory;
+import org.sonar.server.qualitygate.changeevent.QGChangeEventFactory.IssueChange;
+import org.sonar.server.settings.ProjectConfigurationLoader;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.webhook.WebhookPayloadFactory;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singleton;
+import static java.util.Collections.singletonList;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.Mockito.mock;
+import static org.sonar.core.util.stream.MoreCollectors.toArrayList;
+import static org.sonar.db.component.ComponentTesting.newBranchDto;
+
+@RunWith(DataProviderRunner.class)
+public class QGChangeEventFactoryImplTest {
+ @Rule
+ public DbTester dbTester = DbTester.create(System2.INSTANCE);
+ @Rule
+ public UserSessionRule userSessionRule = UserSessionRule.standalone();
+
+ private DbClient dbClient = dbTester.getDbClient();
+
+ private Random random = new Random();
+ private RuleType randomRuleType = RuleType.values()[random.nextInt(RuleType.values().length)];
+
+ private IssueChangeContext scanChangeContext = IssueChangeContext.createScan(new Date());
+ private IssueChangeContext userChangeContext = IssueChangeContext.createUser(new Date(), "userLogin");
+ private WebhookPayloadFactory webhookPayloadFactory = mock(WebhookPayloadFactory.class);
+ private DbClient spiedOnDbClient = Mockito.spy(dbClient);
+ private ProjectConfigurationLoader projectConfigurationLoader = mock(ProjectConfigurationLoader.class);
+ private LiveQualityGateFactory liveQualityGateFactory = mock(LiveQualityGateFactory.class);
+ private QGChangeEventFactoryImpl underTest = new QGChangeEventFactoryImpl(spiedOnDbClient, projectConfigurationLoader, liveQualityGateFactory);
+ private DbClient mockedDbClient = mock(DbClient.class);
+ private QGChangeEventFactoryImpl mockedUnderTest = new QGChangeEventFactoryImpl(mockedDbClient, projectConfigurationLoader, liveQualityGateFactory);
+
+ @Test
+ public void on_type_change_has_no_effect_if_SearchResponseData_has_no_issue() {
+ mockedUnderTest.from(issueChangeData(), new IssueChange(randomRuleType), userChangeContext);
+
+ Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
+ }
+
+ @Test
+ public void on_type_change_has_no_effect_if_scan_IssueChangeContext() {
+ mockedUnderTest.from(issueChangeData(), new IssueChange(randomRuleType), scanChangeContext);
+
+ Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
+ }
+
+ @Test
+ public void on_transition_change_has_no_effect_if_SearchResponseData_has_no_issue() {
+ mockedUnderTest.from(issueChangeData(), new IssueChange(randomAlphanumeric(12)), userChangeContext);
+
+ Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
+ }
+
+ @Test
+ public void onTransition_has_no_effect_if_transition_key_is_empty() {
+ on_transition_changeHasNoEffectForTransitionKey("");
+ }
+
+ @Test
+ public void onTransition_has_no_effect_if_transition_key_is_random() {
+ on_transition_changeHasNoEffectForTransitionKey(randomAlphanumeric(99));
+ }
+
+ @Test
+ public void on_transition_change_has_no_effect_if_transition_key_is_ignored_default_transition_key() {
+ Set<String> supportedDefaultTransitionKeys = ImmutableSet.of(
+ DefaultTransitions.RESOLVE, DefaultTransitions.FALSE_POSITIVE, DefaultTransitions.WONT_FIX, DefaultTransitions.REOPEN);
+ DefaultTransitions.ALL.stream()
+ .filter(s -> !supportedDefaultTransitionKeys.contains(s))
+ .forEach(this::on_transition_changeHasNoEffectForTransitionKey);
+ }
+
+ private void on_transition_changeHasNoEffectForTransitionKey(@Nullable String transitionKey) {
+ Mockito.reset(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
+
+ mockedUnderTest.from(issueChangeData(newIssueDto()), new IssueChange(transitionKey), userChangeContext);
+
+ Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
+ }
+
+ @Test
+ public void on_transition_change_has_no_effect_if_scan_IssueChangeContext() {
+ mockedUnderTest.from(issueChangeData(newIssueDto()), new IssueChange(randomAlphanumeric(12)), scanChangeContext);
+
+ Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
+ }
+
+ @Test
+ public void on_type_and_transition_change_has_no_effect_if_SearchResponseData_has_no_issue() {
+ mockedUnderTest.from(issueChangeData(), new IssueChange(randomRuleType, randomAlphanumeric(3)), userChangeContext);
+
+ Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
+ }
+
+ @Test
+ public void on_type_and_transition_change_has_no_effect_if_scan_IssueChangeContext() {
+ mockedUnderTest.from(issueChangeData(), new IssueChange(randomRuleType, randomAlphanumeric(3)), scanChangeContext);
+
+ Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
+ }
+
+ @Test
+ public void on_type_and_transition_has_no_effect_if_transition_key_is_empty() {
+ on_type_and_transition_changeHasNoEffectForTransitionKey("");
+ }
+
+ @Test
+ public void on_type_and_transition_has_no_effect_if_transition_key_is_random() {
+ on_type_and_transition_changeHasNoEffectForTransitionKey(randomAlphanumeric(66));
+ }
+
+ @Test
+ public void on_type_and_transition_has_no_effect_if_transition_key_is_ignored_default_transition_key() {
+ Set<String> supportedDefaultTransitionKeys = ImmutableSet.of(
+ DefaultTransitions.RESOLVE, DefaultTransitions.FALSE_POSITIVE, DefaultTransitions.WONT_FIX, DefaultTransitions.REOPEN);
+ DefaultTransitions.ALL.stream()
+ .filter(s -> !supportedDefaultTransitionKeys.contains(s))
+ .forEach(this::on_type_and_transition_changeHasNoEffectForTransitionKey);
+ }
+
+ private void on_type_and_transition_changeHasNoEffectForTransitionKey(@Nullable String transitionKey) {
+ Mockito.reset(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
+
+ mockedUnderTest.from(issueChangeData(newIssueDto()), new IssueChange(randomRuleType, transitionKey), userChangeContext);
+
+ Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
+ }
+
+ @Test
+ @UseDataProvider("validIssueChanges")
+ public void broadcast_to_QGEventListeners_for_short_living_branch_of_issue(IssueChange issueChange) {
+ OrganizationDto organization = dbTester.organizations().insert();
+ ComponentDto project = dbTester.components().insertPublicProject(organization);
+ ComponentAndBranch branch = insertProjectBranch(project, BranchType.SHORT, "foo");
+ SnapshotDto analysis = insertAnalysisTask(branch);
+ Configuration configuration = mockLoadProjectConfiguration(branch);
+
+ Map<String, String> properties = new HashMap<>();
+ properties.put("sonar.analysis.test1", randomAlphanumeric(50));
+ properties.put("sonar.analysis.test2", randomAlphanumeric(5000));
+ insertPropertiesFor(analysis.getUuid(), properties);
+
+ Collection<QGChangeEvent> events = underTest.from(issueChangeData(newIssueDto(branch)), issueChange, userChangeContext);
+
+ assertThat(events).hasSize(1);
+ QGChangeEvent event = events.iterator().next();
+ assertThat(event.getProject()).isEqualTo(branch.component);
+ assertThat(event.getBranch()).isEqualTo(branch.branch);
+ assertThat(event.getAnalysis()).isEqualTo(analysis);
+ assertThat(event.getProjectConfiguration()).isSameAs(configuration);
+ }
+
+ @Test
+ public void do_not_load_project_configuration_nor_analysis_nor_call_webhook_if_there_are_no_short_branch() {
+ OrganizationDto organization = dbTester.organizations().insert();
+ ComponentDto project = dbTester.components().insertPublicProject(organization);
+ ComponentAndBranch longBranch1 = insertProjectBranch(project, BranchType.LONG, "foo");
+ ComponentAndBranch longBranch2 = insertProjectBranch(project, BranchType.LONG, "bar");
+ ImmutableList<IssueDto> issueDtos = ImmutableList.of(newIssueDto(project), newIssueDto(longBranch1), newIssueDto(longBranch2));
+
+ SnapshotDao snapshotDaoSpy = Mockito.spy(dbClient.snapshotDao());
+ Mockito.when(spiedOnDbClient.snapshotDao()).thenReturn(snapshotDaoSpy);
+ underTest.from(issueChangeData(issueDtos), new IssueChange(randomRuleType), userChangeContext);
+
+ Mockito.verifyZeroInteractions(projectConfigurationLoader);
+ Mockito.verify(snapshotDaoSpy, Mockito.times(0)).selectLastAnalysesByRootComponentUuids(Matchers.any(DbSession.class), Matchers.anyCollectionOf(String.class));
+ }
+
+ @Test
+ public void creates_single_QGChangeEvent_per_short_branch_with_at_least_one_issue_in_SearchResponseData() {
+ OrganizationDto organization = dbTester.organizations().insert();
+ ComponentAndBranch branch1 = insertPrivateBranch(organization, BranchType.SHORT);
+ ComponentAndBranch branch2 = insertPrivateBranch(organization, BranchType.SHORT);
+ ComponentAndBranch branch3 = insertPrivateBranch(organization, BranchType.SHORT);
+ SnapshotDto analysis1 = insertAnalysisTask(branch1);
+ SnapshotDto analysis2 = insertAnalysisTask(branch2);
+ SnapshotDto analysis3 = insertAnalysisTask(branch3);
+ int issuesBranch1 = 2 + random.nextInt(10);
+ int issuesBranch2 = 2 + random.nextInt(10);
+ int issuesBranch3 = 2 + random.nextInt(10);
+ List<IssueDto> issueDtos = Stream.of(
+ IntStream.range(0, issuesBranch1).mapToObj(i -> newIssueDto(branch1.component)),
+ IntStream.range(0, issuesBranch2).mapToObj(i -> newIssueDto(branch2.component)),
+ IntStream.range(0, issuesBranch3).mapToObj(i -> newIssueDto(branch3.component)))
+ .flatMap(s -> s)
+ .collect(MoreCollectors.toList());
+ Configuration configuration1 = mock(Configuration.class);
+ Configuration configuration2 = mock(Configuration.class);
+ Configuration configuration3 = mock(Configuration.class);
+ mockLoadProjectConfigurations(
+ branch1.component, configuration1,
+ branch2.component, configuration2,
+ branch3.component, configuration3);
+
+ ComponentDao componentDaoSpy = Mockito.spy(dbClient.componentDao());
+ BranchDao branchDaoSpy = Mockito.spy(dbClient.branchDao());
+ SnapshotDao snapshotDaoSpy = Mockito.spy(dbClient.snapshotDao());
+ Mockito.when(spiedOnDbClient.componentDao()).thenReturn(componentDaoSpy);
+ Mockito.when(spiedOnDbClient.branchDao()).thenReturn(branchDaoSpy);
+ Mockito.when(spiedOnDbClient.snapshotDao()).thenReturn(snapshotDaoSpy);
+ Collection<QGChangeEvent> qgChangeEvents = underTest.from(issueChangeData(issueDtos),
+ new IssueChange(randomRuleType), userChangeContext);
+
+ assertThat(qgChangeEvents)
+ .hasSize(3)
+ .extracting(QGChangeEvent::getBranch, QGChangeEvent::getProjectConfiguration, QGChangeEvent::getAnalysis)
+ .containsOnly(
+ tuple(branch1.branch, configuration1, analysis1),
+ tuple(branch2.branch, configuration2, analysis2),
+ tuple(branch3.branch, configuration3, analysis3));
+
+ // verifyWebhookCalled(branch1, configuration1, analysis1);
+ // verifyWebhookCalled(branch2, configuration2, analysis2);
+ // verifyWebhookCalled(branch3, configuration3, analysis3);
+ // extractPayloadFactoryArguments(3);
+
+ Set<String> uuids = ImmutableSet.of(branch1.uuid(), branch2.uuid(), branch3.uuid());
+ Mockito.verify(componentDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
+ Mockito.verify(branchDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
+ Mockito.verify(snapshotDaoSpy).selectLastAnalysesByRootComponentUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
+ Mockito.verifyNoMoreInteractions(componentDaoSpy, branchDaoSpy, snapshotDaoSpy);
+ }
+
+ @Test
+ public void create_QGChangeEvent_only_for_short_branches() {
+ OrganizationDto organization = dbTester.organizations().insert();
+ ComponentAndBranch shortBranch = insertPrivateBranch(organization, BranchType.SHORT);
+ ComponentAndBranch longBranch = insertPrivateBranch(organization, BranchType.LONG);
+ SnapshotDto analysis1 = insertAnalysisTask(shortBranch);
+ SnapshotDto analysis2 = insertAnalysisTask(longBranch);
+ Configuration configuration = mockLoadProjectConfiguration(shortBranch);
+
+ ComponentDao componentDaoSpy = Mockito.spy(dbClient.componentDao());
+ BranchDao branchDaoSpy = Mockito.spy(dbClient.branchDao());
+ SnapshotDao snapshotDaoSpy = Mockito.spy(dbClient.snapshotDao());
+ Mockito.when(spiedOnDbClient.componentDao()).thenReturn(componentDaoSpy);
+ Mockito.when(spiedOnDbClient.branchDao()).thenReturn(branchDaoSpy);
+ Mockito.when(spiedOnDbClient.snapshotDao()).thenReturn(snapshotDaoSpy);
+ Collection<QGChangeEvent> qgChangeEvents = underTest.from(
+ issueChangeData(asList(newIssueDto(shortBranch), newIssueDto(longBranch))),
+ new IssueChange(randomRuleType),
+ userChangeContext);
+
+ assertThat(qgChangeEvents)
+ .hasSize(1)
+ .extracting(QGChangeEvent::getBranch, QGChangeEvent::getProjectConfiguration, QGChangeEvent::getAnalysis)
+ .containsOnly(tuple(shortBranch.branch, configuration, analysis1));
+
+ Set<String> uuids = ImmutableSet.of(shortBranch.uuid(), longBranch.uuid());
+ Mockito.verify(componentDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
+ Mockito.verify(branchDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
+ Mockito.verify(snapshotDaoSpy).selectLastAnalysesByRootComponentUuids(Matchers.any(DbSession.class), Matchers.eq(ImmutableSet.of(shortBranch.uuid())));
+ Mockito.verifyNoMoreInteractions(componentDaoSpy, branchDaoSpy, snapshotDaoSpy);
+ }
+
+ @Test
+ public void do_not_load_componentDto_from_DB_if_all_are_in_inputData() {
+ OrganizationDto organization = dbTester.organizations().insert();
+ ComponentAndBranch branch1 = insertPrivateBranch(organization, BranchType.SHORT);
+ ComponentAndBranch branch2 = insertPrivateBranch(organization, BranchType.SHORT);
+ ComponentAndBranch branch3 = insertPrivateBranch(organization, BranchType.SHORT);
+ SnapshotDto analysis1 = insertAnalysisTask(branch1);
+ SnapshotDto analysis2 = insertAnalysisTask(branch2);
+ SnapshotDto analysis3 = insertAnalysisTask(branch3);
+ List<IssueDto> issueDtos = asList(newIssueDto(branch1), newIssueDto(branch2), newIssueDto(branch3));
+ Configuration configuration1 = mock(Configuration.class);
+ Configuration configuration2 = mock(Configuration.class);
+ Configuration configuration3 = mock(Configuration.class);
+ mockLoadProjectConfigurations(
+ branch1.component, configuration1,
+ branch2.component, configuration2,
+ branch3.component, configuration3);
+
+ ComponentDao componentDaoSpy = Mockito.spy(dbClient.componentDao());
+ Mockito.when(spiedOnDbClient.componentDao()).thenReturn(componentDaoSpy);
+ Collection<QGChangeEvent> qgChangeEvents = underTest.from(
+ issueChangeData(issueDtos, branch1, branch2, branch3),
+ new IssueChange(randomRuleType),
+ userChangeContext);
+
+ assertThat(qgChangeEvents)
+ .hasSize(3)
+ .extracting(QGChangeEvent::getBranch, QGChangeEvent::getProjectConfiguration, QGChangeEvent::getAnalysis)
+ .containsOnly(
+ tuple(branch1.branch, configuration1, analysis1),
+ tuple(branch2.branch, configuration2, analysis2),
+ tuple(branch3.branch, configuration3, analysis3));
+
+ Mockito.verify(componentDaoSpy, Mockito.times(0)).selectByUuids(Matchers.any(DbSession.class), Matchers.anyCollectionOf(String.class));
+ Mockito.verifyNoMoreInteractions(componentDaoSpy);
+ }
+
+ @Test
+ public void call_db_only_for_componentDto_not_in_inputData() {
+ OrganizationDto organization = dbTester.organizations().insert();
+ ComponentAndBranch branch1 = insertPrivateBranch(organization, BranchType.SHORT);
+ ComponentAndBranch branch2 = insertPrivateBranch(organization, BranchType.SHORT);
+ ComponentAndBranch branch3 = insertPrivateBranch(organization, BranchType.SHORT);
+ SnapshotDto analysis1 = insertAnalysisTask(branch1);
+ SnapshotDto analysis2 = insertAnalysisTask(branch2);
+ SnapshotDto analysis3 = insertAnalysisTask(branch3);
+ List<IssueDto> issueDtos = asList(newIssueDto(branch1), newIssueDto(branch2), newIssueDto(branch3));
+ Configuration configuration1 = mock(Configuration.class);
+ Configuration configuration2 = mock(Configuration.class);
+ Configuration configuration3 = mock(Configuration.class);
+ mockLoadProjectConfigurations(
+ branch1.component, configuration1,
+ branch2.component, configuration2,
+ branch3.component, configuration3);
+
+ ComponentDao componentDaoSpy = Mockito.spy(dbClient.componentDao());
+ BranchDao branchDaoSpy = Mockito.spy(dbClient.branchDao());
+ SnapshotDao snapshotDaoSpy = Mockito.spy(dbClient.snapshotDao());
+ Mockito.when(spiedOnDbClient.componentDao()).thenReturn(componentDaoSpy);
+ Mockito.when(spiedOnDbClient.branchDao()).thenReturn(branchDaoSpy);
+ Mockito.when(spiedOnDbClient.snapshotDao()).thenReturn(snapshotDaoSpy);
+ Collection<QGChangeEvent> qgChangeEvents = underTest.from(
+ issueChangeData(issueDtos, branch1, branch3),
+ new IssueChange(randomRuleType),
+ userChangeContext);
+
+ assertThat(qgChangeEvents).hasSize(3);
+
+ Set<String> uuids = ImmutableSet.of(branch1.uuid(), branch2.uuid(), branch3.uuid());
+ Mockito.verify(componentDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(ImmutableSet.of(branch2.uuid())));
+ Mockito.verify(branchDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
+ Mockito.verify(snapshotDaoSpy).selectLastAnalysesByRootComponentUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
+ Mockito.verifyNoMoreInteractions(componentDaoSpy, branchDaoSpy, snapshotDaoSpy);
+ }
+
+ @Test
+ public void supports_issues_on_files_and_filter_on_short_branches_asap_when_calling_db() {
+ OrganizationDto organization = dbTester.organizations().insert();
+ ComponentDto project = dbTester.components().insertPrivateProject(organization);
+ ComponentDto file = dbTester.components().insertComponent(ComponentTesting.newFileDto(project));
+ ComponentAndBranch shortBranch = insertProjectBranch(project, BranchType.SHORT, "foo");
+ ComponentAndBranch longBranch = insertProjectBranch(project, BranchType.LONG, "bar");
+ ComponentDto shortBranchFile = dbTester.components().insertComponent(ComponentTesting.newFileDto(shortBranch.component));
+ ComponentDto longBranchFile = dbTester.components().insertComponent(ComponentTesting.newFileDto(longBranch.component));
+ SnapshotDto analysis1 = insertAnalysisTask(project);
+ SnapshotDto analysis2 = insertAnalysisTask(shortBranch);
+ SnapshotDto analysis3 = insertAnalysisTask(longBranch);
+ List<IssueDto> issueDtos = asList(
+ newIssueDto(file, project),
+ newIssueDto(shortBranchFile, shortBranch),
+ newIssueDto(longBranchFile, longBranch));
+ Configuration configuration = mockLoadProjectConfiguration(shortBranch);
+
+ ComponentDao componentDaoSpy = Mockito.spy(dbClient.componentDao());
+ BranchDao branchDaoSpy = Mockito.spy(dbClient.branchDao());
+ SnapshotDao snapshotDaoSpy = Mockito.spy(dbClient.snapshotDao());
+ Mockito.when(spiedOnDbClient.componentDao()).thenReturn(componentDaoSpy);
+ Mockito.when(spiedOnDbClient.branchDao()).thenReturn(branchDaoSpy);
+ Mockito.when(spiedOnDbClient.snapshotDao()).thenReturn(snapshotDaoSpy);
+ Collection<QGChangeEvent> qgChangeEvents = underTest.from(issueChangeData(issueDtos),
+ new IssueChange(randomRuleType), userChangeContext);
+
+ assertThat(qgChangeEvents)
+ .hasSize(1)
+ .extracting(QGChangeEvent::getBranch, QGChangeEvent::getProjectConfiguration, QGChangeEvent::getAnalysis)
+ .containsOnly(tuple(shortBranch.branch, configuration, analysis2));
+
+ Set<String> uuids = ImmutableSet.of(project.uuid(), shortBranch.uuid(), longBranch.uuid());
+ Mockito.verify(componentDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
+ Mockito.verify(branchDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(ImmutableSet.of(shortBranch.uuid(), longBranch.uuid())));
+ Mockito.verify(snapshotDaoSpy).selectLastAnalysesByRootComponentUuids(Matchers.any(DbSession.class), Matchers.eq(ImmutableSet.of(shortBranch.uuid())));
+ Mockito.verifyNoMoreInteractions(componentDaoSpy, branchDaoSpy, snapshotDaoSpy);
+ }
+
+ private Configuration mockLoadProjectConfiguration(ComponentAndBranch componentAndBranch) {
+ Configuration configuration = mock(Configuration.class);
+ Mockito.when(projectConfigurationLoader.loadProjectConfigurations(Matchers.any(DbSession.class), Matchers.eq(singleton(componentAndBranch.component))))
+ .thenReturn(ImmutableMap.of(componentAndBranch.uuid(), configuration));
+ return configuration;
+ }
+
+ private void mockLoadProjectConfigurations(Object... branchesAndConfiguration) {
+ checkArgument(branchesAndConfiguration.length % 2 == 0);
+ Set<ComponentDto> components = new HashSet<>();
+ Map<String, Configuration> result = new HashMap<>();
+ for (int i = 0; i < branchesAndConfiguration.length; i++) {
+ ComponentDto component = (ComponentDto) branchesAndConfiguration[i++];
+ Configuration configuration = (Configuration) branchesAndConfiguration[i];
+ components.add(component);
+ result.put(component.uuid(), configuration);
+ }
+ Mockito.when(projectConfigurationLoader.loadProjectConfigurations(Matchers.any(DbSession.class), Matchers.eq(components)))
+ .thenReturn(result);
+ }
+
+ private ComponentAndBranch insertPrivateBranch(OrganizationDto organization, BranchType branchType) {
+ ComponentDto project = dbTester.components().insertPrivateProject(organization);
+ BranchDto branchDto = newBranchDto(project.projectUuid(), branchType)
+ .setKey("foo");
+ ComponentDto newComponent = dbTester.components().insertProjectBranch(project, branchDto);
+ return new ComponentAndBranch(newComponent, branchDto);
+ }
+
+ public ComponentAndBranch insertProjectBranch(ComponentDto project, BranchType type, String branchKey) {
+ BranchDto branchDto = newBranchDto(project.projectUuid(), type).setKey(branchKey);
+ ComponentDto newComponent = dbTester.components().insertProjectBranch(project, branchDto);
+ return new ComponentAndBranch(newComponent, branchDto);
+ }
+
+ private static class ComponentAndBranch {
+ private final ComponentDto component;
+
+ private final BranchDto branch;
+
+ private ComponentAndBranch(ComponentDto component, BranchDto branch) {
+ this.component = component;
+ this.branch = branch;
+ }
+
+ public ComponentDto getComponent() {
+ return component;
+ }
+
+ public BranchDto getBranch() {
+ return branch;
+ }
+
+ public String uuid() {
+ return component.uuid();
+ }
+
+ }
+
+ private void insertPropertiesFor(String snapshotUuid, Map<String, String> properties) {
+ List<AnalysisPropertyDto> analysisProperties = properties.entrySet().stream()
+ .map(entry -> new AnalysisPropertyDto()
+ .setUuid(UuidFactoryFast.getInstance().create())
+ .setSnapshotUuid(snapshotUuid)
+ .setKey(entry.getKey())
+ .setValue(entry.getValue()))
+ .collect(toArrayList(properties.size()));
+ dbTester.getDbClient().analysisPropertiesDao().insert(dbTester.getSession(), analysisProperties);
+ dbTester.getSession().commit();
+ }
+
+ private SnapshotDto insertAnalysisTask(ComponentAndBranch componentAndBranch) {
+ return insertAnalysisTask(componentAndBranch.component);
+ }
+
+ private SnapshotDto insertAnalysisTask(ComponentDto component) {
+ return dbTester.components().insertSnapshot(component);
+ }
+
+ @DataProvider
+ public static Object[][] validIssueChanges() {
+ return new Object[][] {
+ {new IssueChange(RuleType.BUG)},
+ {new IssueChange(RuleType.VULNERABILITY)},
+ {new IssueChange(RuleType.CODE_SMELL)},
+ {new IssueChange(DefaultTransitions.RESOLVE)},
+ {new IssueChange(RuleType.BUG, DefaultTransitions.RESOLVE)},
+ {new IssueChange(RuleType.VULNERABILITY, DefaultTransitions.RESOLVE)},
+ {new IssueChange(RuleType.CODE_SMELL, DefaultTransitions.RESOLVE)},
+ {new IssueChange(DefaultTransitions.FALSE_POSITIVE)},
+ {new IssueChange(RuleType.BUG, DefaultTransitions.FALSE_POSITIVE)},
+ {new IssueChange(RuleType.VULNERABILITY, DefaultTransitions.FALSE_POSITIVE)},
+ {new IssueChange(RuleType.CODE_SMELL, DefaultTransitions.FALSE_POSITIVE)},
+ {new IssueChange(DefaultTransitions.WONT_FIX)},
+ {new IssueChange(RuleType.BUG, DefaultTransitions.WONT_FIX)},
+ {new IssueChange(RuleType.VULNERABILITY, DefaultTransitions.WONT_FIX)},
+ {new IssueChange(RuleType.CODE_SMELL, DefaultTransitions.WONT_FIX)},
+ {new IssueChange(DefaultTransitions.REOPEN)},
+ {new IssueChange(RuleType.BUG, DefaultTransitions.REOPEN)},
+ {new IssueChange(RuleType.VULNERABILITY, DefaultTransitions.REOPEN)},
+ {new IssueChange(RuleType.CODE_SMELL, DefaultTransitions.REOPEN)}
+ };
+ }
+
+ private QGChangeEventFactory.IssueChangeData issueChangeData() {
+ return new QGChangeEventFactory.IssueChangeData(emptyList(), emptyList());
+ }
+
+ private QGChangeEventFactory.IssueChangeData issueChangeData(IssueDto issueDto) {
+ return new QGChangeEventFactory.IssueChangeData(singletonList(issueDto.toDefaultIssue()), emptyList());
+ }
+
+ private QGChangeEventFactory.IssueChangeData issueChangeData(Collection<IssueDto> issueDtos, ComponentAndBranch... components) {
+ return new QGChangeEventFactory.IssueChangeData(
+ issueDtos.stream().map(IssueDto::toDefaultIssue).collect(Collectors.toList()),
+ Arrays.stream(components).map(ComponentAndBranch::getComponent).collect(Collectors.toList()));
+ }
+
+ private IssueDto newIssueDto(@Nullable ComponentAndBranch projectAndBranch) {
+ return projectAndBranch == null ? newIssueDto() : newIssueDto(projectAndBranch.component, projectAndBranch.component);
+ }
+
+ private IssueDto newIssueDto(ComponentDto componentDto) {
+ return newIssueDto(componentDto, componentDto);
+ }
+
+ private IssueDto newIssueDto() {
+ return newIssueDto((ComponentDto) null, (ComponentDto) null);
+ }
+
+ private IssueDto newIssueDto(@Nullable ComponentDto component, @Nullable ComponentAndBranch componentAndBranch) {
+ return newIssueDto(component, componentAndBranch == null ? null : componentAndBranch.component);
+ }
+
+ private IssueDto newIssueDto(@Nullable ComponentDto component, @Nullable ComponentDto project) {
+ RuleType randomRuleType = RuleType.values()[random.nextInt(RuleType.values().length)];
+ String randomStatus = Issue.STATUSES.get(random.nextInt(Issue.STATUSES.size()));
+ IssueDto res = new IssueDto()
+ .setType(randomRuleType)
+ .setStatus(randomStatus)
+ .setRuleKey(randomAlphanumeric(3), randomAlphanumeric(4));
+ if (component != null) {
+ res.setComponent(component);
+ }
+ if (project != null) {
+ res.setProject(project);
+ }
+ return res;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.qualitygate.changeevent;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.rules.RuleType;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class QGChangeEventFactoryTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void IssueChange_constructor_throws_IAE_if_both_args_are_null() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("At least one of ruleType and transitionKey must be non null");
+
+ new QGChangeEventFactory.IssueChange(null, null);
+ }
+
+ @Test
+ public void verify_IssueChange_getters() {
+ QGChangeEventFactory.IssueChange transitionKeyOnly = new QGChangeEventFactoryImpl.IssueChange("foo");
+ assertThat(transitionKeyOnly.getTransitionKey()).contains("foo");
+ assertThat(transitionKeyOnly.getRuleType()).isEmpty();
+ QGChangeEventFactory.IssueChange ruleTypeOnly = new QGChangeEventFactoryImpl.IssueChange(RuleType.BUG);
+ assertThat(ruleTypeOnly.getTransitionKey()).isEmpty();
+ assertThat(ruleTypeOnly.getRuleType()).contains(RuleType.BUG);
+ QGChangeEventFactory.IssueChange transitionKeyAndRuleType = new QGChangeEventFactoryImpl.IssueChange(RuleType.VULNERABILITY, "bar");
+ assertThat(transitionKeyAndRuleType.getTransitionKey()).contains("bar");
+ assertThat(transitionKeyAndRuleType.getRuleType()).contains(RuleType.VULNERABILITY);
+ }
+
+ @Test
+ public void verify_IssueChange_equality() {
+ QGChangeEventFactory.IssueChange underTest = new QGChangeEventFactory.IssueChange(RuleType.BUG);
+
+ assertThat(underTest).isEqualTo(new QGChangeEventFactory.IssueChange(RuleType.BUG));
+ assertThat(underTest).isEqualTo(new QGChangeEventFactory.IssueChange(RuleType.BUG, null));
+
+ assertThat(underTest).isNotEqualTo(null);
+ assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.BUG, randomAlphanumeric(10)));
+ assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.CODE_SMELL));
+ assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.CODE_SMELL, randomAlphanumeric(10)));
+ assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.VULNERABILITY));
+ assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.VULNERABILITY, randomAlphanumeric(10)));
+ assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(randomAlphanumeric(10)));
+
+ String transitionKey = randomAlphanumeric(10);
+ underTest = new QGChangeEventFactory.IssueChange(transitionKey);
+
+ assertThat(underTest).isEqualTo(new QGChangeEventFactory.IssueChange(transitionKey));
+ assertThat(underTest).isEqualTo(new QGChangeEventFactory.IssueChange(null, transitionKey));
+
+ assertThat(underTest).isNotEqualTo(null);
+ assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.BUG));
+ assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.BUG, transitionKey));
+ assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.BUG, randomAlphanumeric(10)));
+ assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.CODE_SMELL));
+ assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.CODE_SMELL, randomAlphanumeric(10)));
+ assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.CODE_SMELL, transitionKey));
+ assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.VULNERABILITY));
+ assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.VULNERABILITY, randomAlphanumeric(10)));
+ assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.VULNERABILITY, transitionKey));
+ assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(randomAlphanumeric(9)));
+ }
+}
import org.mockito.Mockito;
import org.sonar.api.utils.log.LogTester;
import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.db.component.ComponentDto;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doThrow;
private QGChangeEventListener listener1 = mock(QGChangeEventListener.class);
private QGChangeEventListener listener2 = mock(QGChangeEventListener.class);
private QGChangeEventListener listener3 = mock(QGChangeEventListener.class);
+ private QGChangeEventFactory.IssueChangeData issueChangeData = new QGChangeEventFactory.IssueChangeData(singletonList(new DefaultIssue()), singletonList(new ComponentDto()));
private InOrder inOrder = Mockito.inOrder(listener1, listener2, listener3);
private List<QGChangeEvent> threeChangeEvents = Arrays.asList(mock(QGChangeEvent.class), mock(QGChangeEvent.class));
}
@Test
- public void no_effect_when_no_changeEvent() {
- underTest.broadcast(Trigger.ISSUE_CHANGE, Collections.emptySet());
+ public void broadcastOnIssueChange_has_no_effect_when_issueChangeData_is_empty() {
+ QGChangeEventFactory.IssueChangeData issueChangeData = new QGChangeEventFactory.IssueChangeData(emptyList(), emptyList());
+
+ underTest.broadcastOnIssueChange(issueChangeData, threeChangeEvents);
+
+ verifyZeroInteractions(listener1, listener2, listener3);
+ }
+
+ @Test
+ public void broadcastOnIssueChange_has_no_effect_when_issueChangeData_has_no_issue() {
+ QGChangeEventFactory.IssueChangeData issueChangeData = new QGChangeEventFactory.IssueChangeData(emptyList(), singletonList(new ComponentDto()));
+
+ underTest.broadcastOnIssueChange(issueChangeData, threeChangeEvents);
+
+ verifyZeroInteractions(listener1, listener2, listener3);
+ }
+
+ @Test
+ public void broadcastOnIssueChange_has_no_effect_when_issueChangeData_has_no_component() {
+ QGChangeEventFactory.IssueChangeData issueChangeData = new QGChangeEventFactory.IssueChangeData(singletonList(new DefaultIssue()), emptyList());
+
+ underTest.broadcastOnIssueChange(issueChangeData, threeChangeEvents);
+
+ verifyZeroInteractions(listener1, listener2, listener3);
+ }
+
+ @Test
+ public void broadcastOnIssueChange_has_no_effect_when_no_changeEvent() {
+
+ underTest.broadcastOnIssueChange(issueChangeData, Collections.emptySet());
verifyZeroInteractions(listener1, listener2, listener3);
}
@Test
- public void broadcast_passes_Trigger_and_collection_to_all_listeners_in_order_of_addition_to_constructor() {
- underTest.broadcast(Trigger.ISSUE_CHANGE, threeChangeEvents);
+ public void broadcastOnIssueChange_passes_Trigger_and_collection_to_all_listeners_in_order_of_addition_to_constructor() {
+ underTest.broadcastOnIssueChange(issueChangeData, threeChangeEvents);
inOrder.verify(listener1).onChanges(Trigger.ISSUE_CHANGE, threeChangeEvents);
inOrder.verify(listener2).onChanges(Trigger.ISSUE_CHANGE, threeChangeEvents);
}
@Test
- public void broadcast_calls_all_listeners_even_if_one_throws_an_exception() {
+ public void broadcastOnIssueChange_calls_all_listeners_even_if_one_throws_an_exception() {
QGChangeEventListener failingListener = new QGChangeEventListener[] {listener1, listener2, listener3}[new Random().nextInt(3)];
doThrow(new RuntimeException("Faking an exception thrown by onChanges"))
.when(failingListener)
.onChanges(Trigger.ISSUE_CHANGE, threeChangeEvents);
- underTest.broadcast(Trigger.ISSUE_CHANGE, threeChangeEvents);
+ underTest.broadcastOnIssueChange(issueChangeData, threeChangeEvents);
inOrder.verify(listener1).onChanges(Trigger.ISSUE_CHANGE, threeChangeEvents);
inOrder.verify(listener2).onChanges(Trigger.ISSUE_CHANGE, threeChangeEvents);
}
@Test
- public void broadcast_stops_calling_listeners_when_one_throws_an_ERROR() {
+ public void broadcastOnIssueChange_stops_calling_listeners_when_one_throws_an_ERROR() {
doThrow(new Error("Faking an error thrown by a listener"))
.when(listener2)
.onChanges(Trigger.ISSUE_CHANGE, threeChangeEvents);
- underTest.broadcast(Trigger.ISSUE_CHANGE, threeChangeEvents);
+ underTest.broadcastOnIssueChange(issueChangeData, threeChangeEvents);
inOrder.verify(listener1).onChanges(Trigger.ISSUE_CHANGE, threeChangeEvents);
inOrder.verify(listener2).onChanges(Trigger.ISSUE_CHANGE, threeChangeEvents);
}
@Test
- public void broadcast_logs_each_listener_call_at_TRACE_level() {
- underTest.broadcast(Trigger.ISSUE_CHANGE, threeChangeEvents);
+ public void broadcastOnIssueChange_logs_each_listener_call_at_TRACE_level() {
+ underTest.broadcastOnIssueChange(issueChangeData, threeChangeEvents);
assertThat(logTester.logs()).hasSize(3);
List<String> traceLogs = logTester.logs(LoggerLevel.TRACE);
}
@Test
- public void broadcast_passes_immutable_list_of_events() {
+ public void broadcastOnIssueChange_passes_immutable_list_of_events() {
QGChangeEventListenersImpl underTest = new QGChangeEventListenersImpl(new QGChangeEventListener[] {listener1});
- underTest.broadcast(Trigger.ISSUE_CHANGE, threeChangeEvents);
+ underTest.broadcastOnIssueChange(issueChangeData, threeChangeEvents);
ArgumentCaptor<Collection> collectionCaptor = ArgumentCaptor.forClass(Collection.class);
verify(listener1).onChanges(eq(Trigger.ISSUE_CHANGE), collectionCaptor.capture());
}
@Test
- public void no_effect_when_no_listener() {
+ public void broadcastOnIssueChange_has_no_effect_when_no_listener() {
QGChangeEventListenersImpl underTest = new QGChangeEventListenersImpl();
- underTest.broadcast(Trigger.ISSUE_CHANGE, Collections.emptySet());
+ underTest.broadcastOnIssueChange(issueChangeData, threeChangeEvents);
verifyZeroInteractions(listener1, listener2, listener3);
}